diff --git a/Browser~/blockmaker-txnlab-wallet.mjs b/Browser~/blockmaker-txnlab-wallet.mjs index a658e01..65ddc4f 100644 --- a/Browser~/blockmaker-txnlab-wallet.mjs +++ b/Browser~/blockmaker-txnlab-wallet.mjs @@ -75882,17 +75882,20 @@ function E6(e) { icon: t.metadata.icon, providerId: r, networkGenesisId: o6 - }), o = null, s = !1, c = null, l = async (e) => { + }), o = null, s = null, c = null, l = async (e) => { if (C6(t), s) throw Error(`A signing request is already waiting in ${n}.`); if (r === l6 && !t.web3auth?.connected) { let e = /* @__PURE__ */ Error("Your email wallet session expired. Sign out and sign in again before reviewing another transaction."); throw Object.assign(e, { code: "SESSION_EXPIRED" }), e; } - s = !0; + let i, a = new Promise((e, t) => { + i = t; + }), o = { cancel: () => i(Object.assign(/* @__PURE__ */ Error("The player cancelled the wallet request."), { code: "PLAYER_CANCELLED" })) }; + s = o; try { - return await e(); + return await Promise.race([e(), a]); } finally { - s = !1; + s === o && (s = null); } }; return Object.freeze({ @@ -75902,8 +75905,10 @@ function E6(e) { networkGenesisId: o6, ...r === u6 || r === l6 ? { beginUnityPresentation: (e) => { if (c) throw Error("A Unity wallet presentation is already active."); - let n = r === u6 ? m(t, e) : p(t, e), i = n.dispose; - return n.dispose = () => { + let n = r === u6 ? m(t, e) : p(t, e), i = n.dispose, a = n.cancel; + return n.cancel = async () => { + s?.cancel(), await a(); + }, n.dispose = () => { i(), c === n && (c = null); }, c = n, n; } } : {}, @@ -75917,7 +75922,9 @@ function E6(e) { o = null; }), o)), resumeSession: async () => (C6(t), await t.resumeSession(), C6(t), S6(t)), - disconnect: () => t.disconnect(), + disconnect: async () => { + s?.cancel(), await t.disconnect(); + }, signTransactions: (e, n) => l(() => t.signTransactions(T6(e, i), n?.slice())), transactionSigner: (e, n) => l(async () => (await t.signTransactions(T6(e, i), n.slice())).reduce((e, t) => (t !== null && e.push(t), e), [])), canSignData: t.canSignData, @@ -76004,7 +76011,11 @@ async function O6(e) { algorandWallets: m, algosdk: i, resumeError: u, - disconnectAll: () => e.exposeExternalWallets ? o.disconnect() : s.disconnect() + disconnectAll: async () => { + let t = (await Promise.allSettled(m.filter((e) => (e.wallet.accounts?.length ?? 0) > 0).map((e) => e.wallet.disconnect()))).find((e) => e.status === "rejected"); + if (t?.status === "rejected") throw t.reason; + e.exposeExternalWallets && await o.disconnect(); + } }); } async function k6(e) { diff --git a/Browser~/blockmaker.js b/Browser~/blockmaker.js index 30fa665..836c4a0 100644 --- a/Browser~/blockmaker.js +++ b/Browser~/blockmaker.js @@ -2611,7 +2611,7 @@ export function createBlockmaker(options = {}) { } const completePreparedAlgorandWalletLogin = prepared => { - if (!preparedAlgorandWalletLogins.has(prepared)) { + if (!preparedAlgorandWalletLogins.has(prepared) || prepared.attemptId !== loginAttemptSequence) { return Promise.reject(new BlockmakerError('This wallet sign-in approval is no longer current.', { code: 'AUTH_SUPERSEDED', })) @@ -2629,6 +2629,8 @@ export function createBlockmaker(options = {}) { return Promise.reject(error) } return Promise.resolve(signing).then(async signed => { + if (prepared.attemptId !== loginAttemptSequence) + throw new BlockmakerError('This wallet sign-in was cancelled.', { code: 'AUTH_SUPERSEDED' }) const signedTxn = Array.isArray(signed) ? signed.find(value => value instanceof Uint8Array && value.byteLength > 0) : null @@ -8387,34 +8389,37 @@ export function createBlockmaker(options = {}) { const logoutUnitySession = async value => { requireUnityWalletCleanupIdle() - const target = packageSession(value) + const target = value == null ? null : packageSession(value) + if (!target && (activeOpen || pendingHandoff || session || acknowledgedUnitySigner || preparedTransactionGroup)) + throw new BlockmakerError('Finish cancelling the current request before disconnecting.', { code: 'REQUEST_ALREADY_PENDING' }) beginUnityWalletCleanup() try { - await rawRequest('/v1/auth/logout', { + if (target) await rawRequest('/v1/auth/logout', { method: 'POST', auth: false, keepalive: true, body: { refreshToken: target.refreshToken, gameId }, }) await walletSetup.disconnectAll() } catch (error) { - if (session?.refreshToken === target.refreshToken) storeSession(null) - if (pendingHandoff?.refreshToken === target.refreshToken) { + if (session?.refreshToken === target?.refreshToken) storeSession(null) + if (pendingHandoff?.refreshToken === target?.refreshToken) { pendingHandoff = null clearPendingHandoffPageHide() } - if (rememberedAlgorandSigner?.walletAddress === target.walletAddress) + if (rememberedAlgorandSigner?.walletAddress === target?.walletAddress) rememberedAlgorandSigner = null latchUnityWalletPackageCleanup() throw error } confirmUnityWalletCleanup() - if (session?.refreshToken === target.refreshToken) storeSession(null) - if (pendingHandoff?.refreshToken === target.refreshToken) { + invalidateLoginAttempts() + if (session?.refreshToken === target?.refreshToken) storeSession(null) + if (pendingHandoff?.refreshToken === target?.refreshToken) { pendingHandoff = null clearPendingHandoffPageHide() } - if (rememberedAlgorandSigner?.walletAddress === target.walletAddress) + if (rememberedAlgorandSigner?.walletAddress === target?.walletAddress) rememberedAlgorandSigner = null - if (acknowledgedUnitySigner?.walletAddress === target.walletAddress) + if (acknowledgedUnitySigner?.walletAddress === target?.walletAddress) acknowledgedUnitySigner = null if (preparedTransactionGroup) { if (preparedTransactionGroup.state === 'signing') preparedTransactionGroup.state = 'abandoned' diff --git a/CHANGELOG.md b/CHANGELOG.md index ebd0823..15a5641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [2.0.1] - 2026-09-17 + +- Email-first sign-in, a Wallet submenu, and Pera with its original reusable logo. +- Configurable labels and logo; browsing/back/cancel keeps the existing wallet manager. +- Cancel incomplete sign-in and disconnect remembered connections with Use another account. +- Retain exact session revocation and discard late cancelled approvals. +- Keep Cancel and account switching visible on short screens. Size the demo + using the canvas's visible dimensions, including high-DPI and phone screens. + ## [2.0.0] - 2026-09-16 - Make the current WebGL Pera/optional email SDK the root Unity Package Manager diff --git a/Documentation~/integration.md b/Documentation~/integration.md index f163585..6262ede 100644 --- a/Documentation~/integration.md +++ b/Documentation~/integration.md @@ -33,7 +33,7 @@ These are integration fragments; the complete working component is keep the receiving GameObject's name unique, and dispose the view at teardown. `RuntimeSupported` lets the game show a friendly message in Editor/native builds. A blank public email client ID selects `pera_lute`; a configured ID selects -`pera_lute_txnlab_web3auth`. The Unity presentation shows Pera and optional email; +`pera_lute_txnlab_web3auth`. The Unity presentation shows Email first when enabled, then Wallet → Pera; `OpenAccount` is available for the broader account UI. The older `web3auth_avm_email` authentication-only broker is separate and remains restricted. @@ -105,3 +105,22 @@ this package locally without claiming provider or game qualification. Commit scene/template changes and Unity metadata. Use complete tagged releases, not individual vendor-file patches. Required setup and attended acceptance remain in [email setup](email-setup.md) and [validation](validation.md). + +The original Pera logo is bundled in the shared presentation. Set `PeraLogo` in +`BlockmakerUnityWalletAppearance` to provide your own texture, or call +`BlockmakerUnityWalletIcons.CreatePeraLogo()` for a caller-owned copy. Destroy +that texture when finished. Pera branding remains its owner's mark. + +Use **Use another account** in the chooser to cancel sign-in and disconnect a +remembered connection, then choose Email or Wallet again. `Logout` also handles +remembered connections when no game session has been acknowledged. Wait for its +confirmed result; retain your game's economic recovery records when changing accounts. + +## Phone and high-DPI layout + +The demo clones its panel settings at runtime and sizes the UI in visible canvas +pixels. Your authored settings remain intact. In a custom integration, supply a +full-screen UI Toolkit root with the same visible-pixel sizing; a gameplay panel +scaled from a 1920×1080 design can make wallet controls too small on phones. +The wallet view keeps Cancel and account switching outside its scrolling area. +Use a short landscape phone viewport when checking your integration. diff --git a/Documentation~/upgrading.md b/Documentation~/upgrading.md index 2244702..e22b06d 100644 --- a/Documentation~/upgrading.md +++ b/Documentation~/upgrading.md @@ -14,7 +14,7 @@ current installation route. the client, wallet facade and presentation shown in the [integration guide](integration.md). Remove obsolete scene objects after wiring their replacements. Existing API callers will need changes; the package does not rewrite gameplay code. -3. Install `https://github.com/blockmaker-ai/blockmaker-unity.git#v2.0.0` through +3. Follow the [current installation instructions](../README.md) in Package Manager. Keep one wallet manager. Old Reown/Nethereum dependencies can be removed through Package Manager only if no other project code uses them. 4. Confirm the same owner wallet/profile, cancellation and pending-operation @@ -22,8 +22,9 @@ current installation route. ## From the September Assets/ZIP package -The nine wallet runtime files are unchanged. The new package manages their paths -and WebGL build inclusion. Close Unity and back up the complete existing SDK +The version 2 package manages the nine wallet runtime files and their WebGL +build inclusion. The local 2.0.1 candidate changes only the presentation file; +the other eight runtime files retain their 2.0.0 contents. Close Unity and back up the complete existing SDK files and their `.meta` files outside Assets before removing their old copies from compilation. Preserve your own game UI, configuration and recovery data. diff --git a/Documentation~/validation.md b/Documentation~/validation.md index a8b6cfb..682d271 100644 --- a/Documentation~/validation.md +++ b/Documentation~/validation.md @@ -1,58 +1,42 @@ -# Validation — Unity Package Manager SDK 2.0.0 - -The package contains canonical wallet runtime -`unity-webgl-package-sha256-cicBnjQf1cPzH5Sz3Gt7wX8seHXG9Cj1VTf6eRIAFlQ`, from -runtime revision `226bd9136b268cd1c7005efdb3bccf23be50e607`. All nine runtime files -are unchanged from the package used by NFTURBO build `20260915-6f70eb687b39`. -Version 2.0.0 describes the public UPM distribution and setup tools; the canonical -runtime manifest retains backend release version `0.2.5`. - -## Checked for this distribution - -- The root package resolves from its public Git revision in Unity 6000.3.15f1 - using Unity Package Manager. It compiles without Reown or separately installed - wallet dependencies. The imported sample scene retains its document, panel, - theme and camera references, with no game ID or email client ID prefilled. -- The setup menu creates a scene with a UI document, panel, camera and wallet - demo. Repeating setup does not create a second demo. The `.jslib` plugin is - enabled for WebGL and disabled for Editor. -- A small actual WebGL build succeeded with zero errors. Its five browser files - match the canonical hashes, and no vendor copies were written into the - project's Assets/StreamingAssets folder. The full NFTURBO game was not rebuilt. -- A headless browser loaded the demo and all three executable wallet modules - from the built StreamingAssets with HTTP 200. The fullscreen template entered - document-root fullscreen; Pera's in-game QR and cancellation were checked. -- Five focused checks cover package/dependency layout, the full canonical - runtime hashes, Unity metadata/plugin settings, removal of the old active - runtime, documentation links and sample/template presence. - -The same unchanged core previously completed NFTURBO's desktop, ASTC and ETC2 -builds and focused authentication/session/recovery-boundary tests. Its email -form opening/focus, close/retry and fullscreen behavior were checked in that -integration. These earlier checks do not prove every game or device works. - -## Still requires attended player testing - -Actual email/OTP and Pera wallet approval, C# acknowledgment after a real login, -returning login/recovery and stable identity across two actual game origins -remain player acceptance checks. The packaging test did not enter an email or -create a wallet. Opening a provider form is not proof of a completed login. - -No live transaction was signed or submitted for this release. Shop delivery, -ASA opt-ins, Garage groups, Loadout ownership and Marketplace operations need -their own game-specific acceptance under existing backend policy. The sample -makes no economic requests. Preserve ambiguous-operation recovery and never -retry a submission automatically. - -Physical mobile devices, provider popups and the complete verification/recovery -screens still need checks. The package supports Unity WebGL only; native exports -and unrelated email-provider configurations have not been implemented here. +# Validation — Unity Package Manager SDK 2.0.1 + +Canonical runtime: `unity-webgl-package-sha256-ABEU4Xw6yrr_x5QxD0RBoncEqP2IStnSSnbbfQhI-5k`. +Source revision: `4e56ec769561f54b561cea28a181995883e3983d`. +The UPM distribution is version 2.0.1; the canonical manifest retains backend +release version 0.2.5. The complete nine-file runtime is included unchanged. + +## Checks + +- Thirty-four existing canonical package/manifest checks passed. +- Nineteen Unity UI checks passed: email-first choices, Pera logo, Back, + cancellation once, Pera-only configuration and duplicate-open protection. +- The dialogs were rendered at desktop, tablet, phone landscape and phone + portrait sizes. Cancel and account switching remain outside scrolling content. +- The package distribution checks verify runtime hashes, Unity GUIDs, plugin + platforms, dependencies, installer, sample, template and documentation links. + +- A minimal Unity 6000.3.15f1 WebGL consumer built with zero errors and warnings. +- The actual demo loaded all three wallet modules with HTTP 200. At a desktop + size and an 844×390 phone viewport with a 1688×780 render target, its controls + stayed readable. Pera QR, Cancel, reopening and Use another account worked; + document-root fullscreen remained active throughout those wallet actions. + +These checks cover the shared component and demo. They do not establish a +completed wallet login or transaction in a consuming game. + +## Player checks still needed + +The NFTURBO owner reported successful email login on 16 September 2026 using the +previous presentation. Automated checks did not enter an email, OTP or recovery +material and did not sign or submit a live transaction. + +Returning login/recovery, identity across actual game origins, physical mobile +browsers and each game's economic operations require attended checks. The demo +reads profiles and makes no economic requests. Native exports are unsupported. ## Recheck locally -Use **Blockmaker → Verify Installed Package** in Unity to check the nine runtime -hashes. Build with the included template and inspect its StreamingAssets output. -For contributors, `node --test 'Tools~/package.test.mjs'` runs the lightweight -packaging checks; Node.js is not required to install or use the SDK in Unity. -Installation and file verification do not activate a provider, qualify a game or -prove completed wallet/economic flows. +Use **Blockmaker → Verify Installed Package** for the nine runtime hashes. +Build using the included WebGL template. For contributors, run +`node --test 'Tools~/package.test.mjs'`; Node.js is not needed to install or use +the SDK through Unity Package Manager. diff --git a/Editor/BlockmakerWebGLBuild.cs b/Editor/BlockmakerWebGLBuild.cs index 3a040be..c87e6b6 100644 --- a/Editor/BlockmakerWebGLBuild.cs +++ b/Editor/BlockmakerWebGLBuild.cs @@ -12,7 +12,7 @@ namespace Blockmaker.Editor /// Adds the package's exact browser payload without modifying Assets. public sealed class BlockmakerWebGLBuild : BuildPlayerProcessor { - public const string CanonicalPackageId = "unity-webgl-package-sha256-cicBnjQf1cPzH5Sz3Gt7wX8seHXG9Cj1VTf6eRIAFlQ"; + public const string CanonicalPackageId = "unity-webgl-package-sha256-ABEU4Xw6yrr_x5QxD0RBoncEqP2IStnSSnbbfQhI-5k"; public override int callbackOrder => 0; private static readonly Dictionary Paths = new Dictionary { { "BlockmakerClient.cs", "Runtime/BlockmakerClient.cs" }, diff --git a/Installer~/BlockmakerInstaller.cs b/Installer~/BlockmakerInstaller.cs index 2af6951..a4eb438 100644 --- a/Installer~/BlockmakerInstaller.cs +++ b/Installer~/BlockmakerInstaller.cs @@ -9,7 +9,7 @@ [InitializeOnLoad] public static class BlockmakerInstaller { - private const string PackageUrl = "https://github.com/blockmaker-ai/blockmaker-unity.git#v2.0.0"; + private const string PackageUrl = "https://github.com/blockmaker-ai/blockmaker-unity.git#v2.0.1"; private static AddRequest request; static BlockmakerInstaller() { diff --git a/Plugins/WebGL/BlockmakerDemoViewport.jslib b/Plugins/WebGL/BlockmakerDemoViewport.jslib new file mode 100644 index 0000000..6fa5fae --- /dev/null +++ b/Plugins/WebGL/BlockmakerDemoViewport.jslib @@ -0,0 +1,11 @@ +// Demo layout only. No wallet state or private material crosses this boundary. +mergeInto(LibraryManager.library, { + BlockmakerDemoViewportWidth: function () { + var box = Module.canvas && Module.canvas.getBoundingClientRect(); + return Math.max(1, Math.round(box && box.width || window.innerWidth)); + }, + BlockmakerDemoViewportHeight: function () { + var box = Module.canvas && Module.canvas.getBoundingClientRect(); + return Math.max(1, Math.round(box && box.height || window.innerHeight)); + } +}); diff --git a/Plugins/WebGL/BlockmakerDemoViewport.jslib.meta b/Plugins/WebGL/BlockmakerDemoViewport.jslib.meta new file mode 100644 index 0000000..7344724 --- /dev/null +++ b/Plugins/WebGL/BlockmakerDemoViewport.jslib.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: d9a777cc0fc907d99e44d043161e2479 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Plugins/WebGL/BlockmakerWalletPackageWebGL.jslib b/Plugins/WebGL/BlockmakerWalletPackageWebGL.jslib index 5afbe41..0418d06 100644 --- a/Plugins/WebGL/BlockmakerWalletPackageWebGL.jslib +++ b/Plugins/WebGL/BlockmakerWalletPackageWebGL.jslib @@ -252,6 +252,7 @@ mergeInto(LibraryManager.library, { }, setOwner: function (owner) { if (Object.prototype.hasOwnProperty.call(root, ownerKey)) return false; + owner.released = new Promise(function (resolve) { owner.release = resolve; }); try { Object.defineProperty(root, ownerKey, { value: owner, @@ -265,7 +266,9 @@ mergeInto(LibraryManager.library, { clearOwner: function (owner) { if (root[ownerKey] !== owner) return false; try { delete root[ownerKey]; } catch (_) { return false; } - return root[ownerKey] !== owner; + var cleared = root[ownerKey] !== owner; + if (cleared && owner.release) owner.release(); + return cleared; }, finishCancelledTransaction: function (owner) { if (root[ownerKey] !== owner || owner.kind !== 'sign_transaction_group' @@ -1122,13 +1125,21 @@ mergeInto(LibraryManager.library, { code: confirmed === true ? null : 'LOGOUT_UNCONFIRMED' }); }; + var emptySession = Object.keys(session).every(function (key) { return session[key] === ''; }); if (!runtime || !runtime.ready || runtime.cleanupRequired() || !runtime.validOperation(operationId) || !runtime.validTarget(target) - || !runtime.validLifecycle(lifecycleId) || !runtime.safeSession(session) + || !runtime.validLifecycle(lifecycleId) || (!emptySession && !runtime.safeSession(session)) || runtime.facade !== root.__blockmakerUnityWebGlWalletPackageV1) { send(false); return; } + var previous = root[ownerKey]; + var drain = previous && previous.kind === 'open_account' + && previous.lifecycleId === lifecycleId && previous.target === target + && (previous.state === 'cancelling' || previous.state === 'rejecting') + ? previous.released : null; + var startLogout = function () { + if (runtime.cleanupRequired()) { send(false); return; } var owner = { lifecycleId: lifecycleId, operationId: operationId, @@ -1141,7 +1152,7 @@ mergeInto(LibraryManager.library, { return; } var confirmation; - try { confirmation = runtime.facade.logout(Object.freeze(session)); } + try { confirmation = runtime.facade.logout(emptySession ? null : Object.freeze(session)); } catch (_) { confirmation = Promise.reject(new Error('logout failed')); } Promise.resolve(confirmation).then(function (value) { if (root[ownerKey] !== owner) return; @@ -1154,6 +1165,9 @@ mergeInto(LibraryManager.library, { runtime.clearOwner(owner); send(false); }); + }; + if (drain) Promise.resolve(drain).then(startLogout).catch(function () { send(false); }); + else startLogout(); }, BlockmakerWalletPackageWebGL_OpenFunding: function ( diff --git a/README.md b/README.md index 553a69a..d1b87ed 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Algorand MainNet. Email uses TxnLab with MetaMask Embedded Wallets. Players can use an email wallet for the same supported game actions as a Pera wallet, subject to balances, asset ownership and the game's rules. -**Version 2.0.0 is the current SDK.** It replaces the previous integration. +**Version 2.0.1 is the current SDK.** This package supports **Unity WebGL**, including compatible desktop and mobile browsers. It does not provide native Windows, macOS, Android or iOS wallet exports. @@ -19,7 +19,7 @@ Requires **Unity 6** with Web Build Support and **Git** installed. 3. Paste this URL and select **Install**: ```text - https://github.com/blockmaker-ai/blockmaker-unity.git#v2.0.0 + https://github.com/blockmaker-ai/blockmaker-unity.git#v2.0.1 ``` Unity installs the SDK and its required Unity modules. No Node.js, separate @@ -30,8 +30,8 @@ Prefer the previous drag-in method? [Download BlockmakerInstaller.cs](https://ra place it in `Assets/Editor`, and choose **Install**. It installs the same version through Package Manager. You can delete that installer afterward. -A [`.tgz` download](https://github.com/blockmaker-ai/blockmaker-unity/releases/tag/v2.0.0) -is also available for Package Manager's **Install package from tarball** option. +The release also provides a [`.tgz` download](https://github.com/blockmaker-ai/blockmaker-unity/releases/tag/v2.0.1) +for Package Manager's **Install package from tarball** option. Projects using an earlier SDK should read [upgrading](Documentation~/upgrading.md) before installing. The SDK never deletes your existing integration or player data. @@ -49,7 +49,8 @@ before installing. The SDK never deletes your existing integration or player dat it to your Web build profile and build. Host it on the exact origin registered for your game and, for email, allowed by the provider project. 5. Open the browser build and select **Sign in**. With email configured, the - game offers **Continue with Pera** and **Continue with email**. + game offers **Email** first and **Wallet** second. Choose **Wallet → Pera** + to connect Pera; its original logo is included. Unity Editor is useful for scene/UI checks. Wallet sign-in runs in the **WebGL browser build**, not Editor Play mode. The demo performs no transactions. @@ -68,7 +69,7 @@ adding a second demo/manager. An importable demo is also in Package Manager's review, submission/recovery and its own gameplay rules. The wallet manager also supports Lute through its lower-level account path; -the included in-game picker presents Pera and optional email. +the included in-game picker presents Email and Wallet → Pera. Registered usernames and profile pictures follow the connected **owner wallet**. Game sessions and game progress stay scoped to the game. Using the same email in unrelated provider projects does **not** guarantee the same wallet; see diff --git a/Runtime/BlockmakerUnityPresentation.cs b/Runtime/BlockmakerUnityPresentation.cs index 22d5a5d..d9a9682 100644 --- a/Runtime/BlockmakerUnityPresentation.cs +++ b/Runtime/BlockmakerUnityPresentation.cs @@ -630,7 +630,22 @@ private static Texture2D CreatePlaceholder(int size) } namespace Blockmaker { - /// Game-owned branding. No game navigation or assets are built into the package. + /// Reusable wallet marks. Returned textures are owned and destroyed by the caller. + public static class BlockmakerUnityWalletIcons + { + // Original Pera mark from the previous Unity SDK (e86c1ba); not game branding. + // Pera's logo remains its owner's mark. Keep it unmodified when identifying Pera. + private const string PeraImage = "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"; + public static Texture2D CreatePeraLogo() + { + var icon = new Texture2D(2, 2, TextureFormat.RGBA32, false) { name = "Pera logo" }; + if (ImageConversion.LoadImage(icon, Convert.FromBase64String(PeraImage), true)) return icon; + UnityEngine.Object.Destroy(icon); + return null; + } + } + + /// Game-owned branding and wording; game navigation stays outside the package. [Serializable] public sealed class BlockmakerUnityWalletAppearance { @@ -640,21 +655,28 @@ public sealed class BlockmakerUnityWalletAppearance public Color Accent = new Color(.93f, .96f, .3f); public Color Text = Color.white; public string ChooseTitle = "Sign in"; - public string ChooseMessage = "Choose your wallet. Sign-in approves a zero-ALGO proof; no payment is sent."; - public string PeraLabel = "Continue with Pera"; - public string EmailLabel = "Continue with email"; + public string ChooseMessage = "How would you like to sign in?"; + public string PeraLabel = "Pera"; + public Texture2D PeraLogo; + public string WalletLabel = "Wallet"; + public string WalletsTitle = "Choose a wallet"; + public string WalletsMessage = "Select the wallet you use."; + public string BackLabel = "Back"; + public string EmailLabel = "Email"; public string EmailTitle = "Your email wallet"; - public string EmailMessage = "Complete email verification or recovery in the wallet window. Return here when finished."; + public string EmailMessage = "Follow the email steps to sign in."; public string ConnectTitle = "Connect Pera Wallet"; public string ConnectMessage = "Scan this code with Pera to connect your wallet."; public string ApprovalTitle = "One more approval"; - public string ApprovalMessage = "Approve the sign-in request in Pera. This signs a zero-ALGO proof; it does not send a payment."; + public string ApprovalMessage = "Approve the sign-in request in Pera. There is no charge."; public string ConnectingMessage = "Opening Pera…"; public string VerifyingMessage = "Checking your sign-in…"; public string ConnectStep = "STEP 1 OF 2"; public string ApprovalStep = "STEP 2 OF 2"; public string CancelLabel = "Cancel"; - public string OpenWalletLabel = "Open Pera"; + public string OpenWalletLabel = "Confirm"; + public string ChangeAccountLabel = "Use another account"; + public string DisconnectingMessage = "Disconnecting…"; } /// @@ -668,7 +690,12 @@ public sealed class BlockmakerUnityWalletView : IDisposable private readonly BlockmakerUnityWalletAppearance appearance; private readonly VisualElement overlay, qr; private readonly Label title, message, step; - private readonly Button openWallet, peraChoice, emailChoice; + private readonly Button openWallet, peraChoice, emailChoice, walletChoice, backChoice, changeAccount, cancel; + private Texture2D ownedPeraLogo; + private bool opened, resetting; + private Action completion; + public bool IsOpen => !disposed; + public void Focus() { if (!disposed) overlay.BringToFront(); } private Action choiceCompletion; #if UNITY_WEBGL && !UNITY_EDITOR private bool keyboardReleased; @@ -694,27 +721,30 @@ public BlockmakerUnityWalletView(VisualElement parent, overlay.style.color = appearance.Text; if (appearance.Font != null) overlay.style.unityFontDefinition = FontDefinition.FromFont(appearance.Font); var scroll = new ScrollView(ScrollViewMode.Vertical); - scroll.style.width = Length.Percent(94); - scroll.style.maxWidth = 520; - scroll.style.maxHeight = Length.Percent(96); + scroll.style.width = Length.Percent(100); scroll.style.flexShrink = 1; + scroll.contentContainer.style.alignItems = Align.Center; var card = new VisualElement(); + card.style.width = Length.Percent(94); + card.style.maxWidth = 520; + card.style.maxHeight = Length.Percent(96); + card.style.flexShrink = 1; card.style.backgroundColor = appearance.Surface; - card.style.paddingLeft = card.style.paddingRight = 24; - card.style.paddingTop = card.style.paddingBottom = 20; + card.style.paddingLeft = card.style.paddingRight = 16; + card.style.paddingTop = card.style.paddingBottom = 16; card.style.borderTopLeftRadius = card.style.borderTopRightRadius = card.style.borderBottomLeftRadius = card.style.borderBottomRightRadius = 20; card.style.alignItems = Align.Center; - var brand = Text(appearance.AppName, 20); + var brand = Text(appearance.AppName, 16); brand.style.color = appearance.Accent; - title = Text(appearance.ConnectTitle, 30); + title = Text(appearance.ConnectTitle, 24); step = Text(appearance.ConnectStep, 16); qr = new VisualElement { name = "blockmaker-wallet-qr" }; qr.style.width = qr.style.height = 240; qr.style.flexShrink = 0; qr.style.backgroundColor = Color.white; qr.style.display = DisplayStyle.None; - message = Text(appearance.ConnectingMessage, 19); + message = Text(appearance.ConnectingMessage, 18); openWallet = Button(appearance.OpenWalletLabel, () => { if (disposed) return; // Explicit player action; no automatic app switch or fullscreen request. @@ -725,45 +755,147 @@ public BlockmakerUnityWalletView(VisualElement parent, Application.OpenURL(android ? hinted : "perawallet-wc://wc?uri=" + Uri.EscapeDataString(hinted)); }); openWallet.style.display = Application.isMobilePlatform ? DisplayStyle.Flex : DisplayStyle.None; - peraChoice = Button(appearance.PeraLabel, () => Choose("pera")); + peraChoice = Button("", () => Choose("pera")); + peraChoice.name = "blockmaker-pera-choice"; + peraChoice.style.flexDirection = FlexDirection.Row; + peraChoice.style.alignItems = Align.Center; + peraChoice.style.justifyContent = Justify.Center; + var logo = appearance.PeraLogo; + if (logo == null) logo = ownedPeraLogo = BlockmakerUnityWalletIcons.CreatePeraLogo(); + var logoImage = new Image { image = logo, pickingMode = PickingMode.Ignore }; + logoImage.style.width = logoImage.style.height = 32; + logoImage.style.marginRight = 12; + peraChoice.Add(logoImage); + peraChoice.Add(new Label(appearance.PeraLabel) { pickingMode = PickingMode.Ignore }); emailChoice = Button(appearance.EmailLabel, () => Choose(BlockmakerWalletPackageWebGL.TxnLabWeb3AuthProvider)); - peraChoice.style.display = emailChoice.style.display = DisplayStyle.None; - var cancel = Button(appearance.CancelLabel, () => { + emailChoice.name = "blockmaker-email-choice"; + walletChoice = Button(appearance.WalletLabel, ShowWalletChoices); + walletChoice.name = "blockmaker-wallet-choice"; + backChoice = Button(appearance.BackLabel, ShowSignInChoices); + backChoice.name = "blockmaker-wallet-back"; + peraChoice.style.display = emailChoice.style.display = walletChoice.style.display = backChoice.style.display = DisplayStyle.None; + changeAccount = Button(appearance.ChangeAccountLabel, ChangeAccount); + changeAccount.name = "blockmaker-change-account"; + changeAccount.style.backgroundColor = new Color(.22f, .23f, .28f); + changeAccount.style.color = appearance.Text; + cancel = Button(appearance.CancelLabel, () => { if (disposed) return; if (choiceCompletion != null) { var done = choiceCompletion; choiceCompletion = null; Dispose(); done(BlockmakerWalletPackageWebGLResult.Failed("PLAYER_CANCELLED")); } else package.Cancel(); }); - card.Add(brand); card.Add(title); card.Add(step); card.Add(qr); - card.Add(message); card.Add(peraChoice); card.Add(emailChoice); card.Add(openWallet); card.Add(cancel); - scroll.Add(card); overlay.Add(scroll); parent.Add(overlay); + scroll.Add(brand); scroll.Add(title); scroll.Add(step); scroll.Add(qr); + scroll.Add(message); scroll.Add(emailChoice); scroll.Add(walletChoice); scroll.Add(peraChoice); scroll.Add(backChoice); + // Keep cancellation and account switching reachable when the QR or + // instructions need to scroll on a short phone viewport. + var actions = new VisualElement { name = "blockmaker-wallet-actions" }; + actions.style.width = Length.Percent(100); + actions.style.flexDirection = FlexDirection.Row; + actions.style.flexWrap = Wrap.Wrap; + actions.style.flexShrink = 0; + foreach (var button in new[] { openWallet, changeAccount, cancel }) { + button.style.width = 0; + button.style.minWidth = 140; + button.style.flexGrow = 1; + button.style.marginLeft = button.style.marginRight = 4; + button.style.whiteSpace = WhiteSpace.Normal; + actions.Add(button); + } + changeAccount.style.fontSize = 16; + card.Add(scroll); card.Add(actions); overlay.Add(card); parent.Add(overlay); overlay.BringToFront(); package.PresentationChanged += Progress; + overlay.RegisterCallback(e => { + // Editor hierarchy previews temporarily detach/re-attach documents. + if (disposed || e.target != overlay || !Application.isPlaying) return; + // A scene leaving must not strand a hidden provider request. + package.Cancel(); + Finish(BlockmakerWalletPackageWebGLResult.Failed("PLAYER_CANCELLED")); + }); } - /// Show optional email alongside Pera using the supplied game styling. + /// Offer Email first when configured, then Wallet and its supported choices. public void OpenAccount(Action done) { - if (disposed || choiceCompletion != null) throw new InvalidOperationException("Wallet choices are already closed or open."); - choiceCompletion = done ?? (_ => { }); + if (disposed || opened) throw new InvalidOperationException("Wallet choices are already closed or open."); + opened = true; + completion = done ?? (_ => { }); + choiceCompletion = Finish; package.UseUnityPresentation = true; - if (!package.EmailEnabled) { Choose("pera"); return; } + if (package.EmailEnabled) ShowSignInChoices(); else ShowWalletChoices(); + } + + private void Finish(BlockmakerWalletPackageWebGLResult result) + { + if (resetting) return; + var done = completion; completion = null; + choiceCompletion = null; + Dispose(); + done?.Invoke(result); + } + + private void ChangeAccount() + { + if (disposed || resetting) return; + resetting = true; + choiceCompletion = null; + changeAccount.SetEnabled(false); + cancel.SetEnabled(false); + peraChoice.style.display = emailChoice.style.display = walletChoice.style.display = + backChoice.style.display = openWallet.style.display = qr.style.display = DisplayStyle.None; + title.text = appearance.ChooseTitle; + step.text = ""; + step.style.display = DisplayStyle.None; + message.text = appearance.DisconnectingMessage; + try { + package.Logout(result => { + if (disposed) return; + resetting = false; + changeAccount.SetEnabled(true); + cancel.SetEnabled(true); + if (!result.Success) { Finish(result); return; } + choiceCompletion = Finish; + if (package.EmailEnabled) ShowSignInChoices(); else ShowWalletChoices(); + }); + } catch { + resetting = false; + Finish(BlockmakerWalletPackageWebGLResult.Failed("PROVIDER_CLEANUP_REQUIRED")); + } + } + + private void ShowSignInChoices() + { + if (disposed || choiceCompletion == null) return; title.text = appearance.ChooseTitle; message.text = appearance.ChooseMessage; step.text = ""; - openWallet.style.display = DisplayStyle.None; - peraChoice.style.display = emailChoice.style.display = DisplayStyle.Flex; + step.style.display = DisplayStyle.None; + openWallet.style.display = qr.style.display = peraChoice.style.display = backChoice.style.display = DisplayStyle.None; + emailChoice.style.display = walletChoice.style.display = DisplayStyle.Flex; + } + + private void ShowWalletChoices() + { + if (disposed || choiceCompletion == null) return; + title.text = appearance.WalletsTitle; + message.text = appearance.WalletsMessage; + step.text = ""; + step.style.display = DisplayStyle.None; + openWallet.style.display = qr.style.display = emailChoice.style.display = walletChoice.style.display = DisplayStyle.None; + peraChoice.style.display = DisplayStyle.Flex; + backChoice.style.display = package.EmailEnabled ? DisplayStyle.Flex : DisplayStyle.None; } private void Choose(string providerId) { if (disposed || choiceCompletion == null) return; var done = choiceCompletion; choiceCompletion = null; - peraChoice.style.display = emailChoice.style.display = DisplayStyle.None; + peraChoice.style.display = emailChoice.style.display = walletChoice.style.display = backChoice.style.display = DisplayStyle.None; title.text = providerId == "pera" ? appearance.ConnectTitle : appearance.EmailTitle; message.text = providerId == "pera" ? appearance.ConnectingMessage : appearance.EmailMessage; step.text = appearance.ConnectStep; + step.style.display = string.IsNullOrEmpty(step.text) ? DisplayStyle.None : DisplayStyle.Flex; openWallet.style.display = providerId == "pera" && Application.isMobilePlatform ? DisplayStyle.Flex : DisplayStyle.None; try { package.OpenAccount(providerId, done); } catch { Dispose(); done(BlockmakerWalletPackageWebGLResult.Failed("PROVIDER_UNAVAILABLE")); } @@ -775,7 +907,7 @@ private Label Text(string value, int size) label.style.fontSize = size; label.style.whiteSpace = WhiteSpace.Normal; label.style.unityTextAlign = TextAnchor.MiddleCenter; - label.style.marginTop = label.style.marginBottom = 8; + label.style.marginTop = label.style.marginBottom = 4; label.style.flexShrink = 0; return label; } @@ -785,7 +917,8 @@ private Button Button(string value, Action clicked) var button = new Button(clicked) { text = value }; button.style.minHeight = 48; button.style.width = Length.Percent(100); - button.style.marginTop = 10; + button.style.marginTop = 8; + button.style.flexShrink = 0; button.style.fontSize = 20; button.style.backgroundColor = appearance.Accent; button.style.color = Color.black; @@ -794,7 +927,7 @@ private Button Button(string value, Action clicked) private void Progress(BlockmakerUnityWalletProgress value) { - if (disposed || value == null) return; + if (disposed || resetting || value == null) return; if (value.phase == "authenticated" || value.phase == "cancelled" || value.phase == "error") { Dispose(); return; } @@ -811,7 +944,7 @@ private void Progress(BlockmakerUnityWalletProgress value) #endif } else if (value.phase == "qr") { uri = value.walletConnectUri; - if (texture != null) UnityEngine.Object.Destroy(texture); + if (texture != null) ReleaseTexture(texture); texture = UnityPeraQRTextureGenerator.Generate(uri, 512); qr.style.backgroundImage = new StyleBackground(texture); qr.style.display = DisplayStyle.Flex; @@ -820,12 +953,19 @@ private void Progress(BlockmakerUnityWalletProgress value) uri = null; qr.style.display = DisplayStyle.None; step.text = appearance.ApprovalStep; + step.style.display = string.IsNullOrEmpty(step.text) ? DisplayStyle.None : DisplayStyle.Flex; title.text = appearance.ApprovalTitle; message.text = value.phase == "verifying" || value.providerId == BlockmakerWalletPackageWebGL.TxnLabWeb3AuthProvider ? appearance.VerifyingMessage : appearance.ApprovalMessage; } } + private static void ReleaseTexture(Texture2D value) + { + if (Application.isPlaying) UnityEngine.Object.Destroy(value); + else UnityEngine.Object.DestroyImmediate(value); + } + public void Dispose() { if (disposed) return; @@ -837,8 +977,10 @@ public void Dispose() pending?.Invoke(BlockmakerWalletPackageWebGLResult.Failed("PLAYER_CANCELLED")); package.PresentationChanged -= Progress; overlay.RemoveFromHierarchy(); - if (texture != null) UnityEngine.Object.Destroy(texture); + if (texture != null) ReleaseTexture(texture); texture = null; + if (ownedPeraLogo != null) ReleaseTexture(ownedPeraLogo); + ownedPeraLogo = null; uri = null; } } diff --git a/Runtime/BlockmakerWalletDemo.cs b/Runtime/BlockmakerWalletDemo.cs index 4a828ec..a574e71 100644 --- a/Runtime/BlockmakerWalletDemo.cs +++ b/Runtime/BlockmakerWalletDemo.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Runtime.InteropServices; using UnityEngine.Networking; using Blockmaker; using UnityEngine; @@ -23,11 +24,34 @@ public sealed class BlockmakerWalletDemo : MonoBehaviour private Image picture; private Texture2D pictureTexture; private int profileRead; + private PanelSettings originalPanel, displayPanel; +#if UNITY_WEBGL && !UNITY_EDITOR + [DllImport("__Internal")] private static extern int BlockmakerDemoViewportWidth(); + [DllImport("__Internal")] private static extern int BlockmakerDemoViewportHeight(); +#endif + + private void Update() + { + if (displayPanel == null) return; + int width = Screen.width, height = Screen.height; +#if UNITY_WEBGL && !UNITY_EDITOR + width = BlockmakerDemoViewportWidth(); height = BlockmakerDemoViewportHeight(); +#endif + var size = new Vector2Int(Mathf.Max(1, width), Mathf.Max(1, height)); + if (displayPanel.referenceResolution != size) displayPanel.referenceResolution = size; + } private void Start() { if (Document == null) Document = GetComponent(); if (Document == null) { Debug.LogError("Assign a UIDocument to the wallet demo."); return; } + originalPanel = Document.panelSettings; + if (originalPanel == null) { Debug.LogError("Assign PanelSettings to the wallet demo document."); return; } + displayPanel = Instantiate(originalPanel); + displayPanel.scaleMode = PanelScaleMode.ScaleWithScreenSize; + displayPanel.scale = 1; + Document.panelSettings = displayPanel; + Update(); gameObject.name = "Blockmaker wallet demo receiver"; var root = Document.rootVisualElement; root.style.paddingTop = root.style.paddingLeft = root.style.paddingRight = 24; @@ -42,6 +66,9 @@ private void Start() connect.SetEnabled(result.Success); profile.SetEnabled(!result.Success); logout.SetEnabled(!result.Success); })) { text = "Sign out" }; root.Add(logout); logout.SetEnabled(false); profile = new Button(LoadProfile) { text = "Refresh shared profile" }; root.Add(profile); profile.SetEnabled(false); + foreach (var button in new[] { connect, logout, profile }) { + button.style.minHeight = 48; button.style.fontSize = 18; button.style.marginTop = 8; + } if (string.IsNullOrWhiteSpace(GameId)) { status.text = "Set your public Game ID in the Wallet Demo Inspector."; return; } client = new BlockmakerClient(ApiOrigin, GameId); package = gameObject.AddComponent(); @@ -93,5 +120,8 @@ private IEnumerator LoadPicture(string source, string wallet, int read) } } private void ClearPicture() { if (picture != null) { picture.image = null; picture.style.display = DisplayStyle.None; } if (pictureTexture != null) Destroy(pictureTexture); pictureTexture = null; } - private void OnDestroy() { profileRead++; view?.Dispose(); ClearPicture(); if (package != null && package.IsReady) package.Cancel(); } + private void OnDestroy() { profileRead++; view?.Dispose(); ClearPicture(); if (package != null && package.IsReady) package.Cancel(); + if (Document != null && Document.panelSettings == displayPanel) Document.panelSettings = originalPanel; + if (displayPanel != null) Destroy(displayPanel); + } } diff --git a/Runtime/BlockmakerWalletPackageWebGL.cs b/Runtime/BlockmakerWalletPackageWebGL.cs index 57d7f16..4cba29a 100644 --- a/Runtime/BlockmakerWalletPackageWebGL.cs +++ b/Runtime/BlockmakerWalletPackageWebGL.cs @@ -654,15 +654,14 @@ public void Logout(Action done = null) if (!IsReady) throw new InvalidOperationException( "PACKAGE_NOT_READY: Wait for wallet-package readiness before signing out."); + // Explicit sign-out also cancels an incomplete account attempt. + // Economic requests retain their own cancellation/recovery boundary. + if (accountPending && !accountHandoffInstalled && !accountRejecting) Cancel(); if (HasActiveOperation()) throw new InvalidOperationException( "REQUEST_ALREADY_PENDING: Close the active wallet-package window before signing out."); var snapshot = SnapshotClientSession(); - if (snapshot == null) { - done?.Invoke(BlockmakerWalletPackageWebGLResult.Completed()); - return; - } - if (!ValidSession(snapshot)) + if (snapshot != null && !ValidSession(snapshot)) throw new InvalidOperationException( "SESSION_CONFLICT: The current player session is not owned by this wallet package."); #if UNITY_WEBGL && !UNITY_EDITOR @@ -672,9 +671,9 @@ public void Logout(Action done = null) logoutPending = true; BlockmakerWalletPackageWebGL_Logout( gameObject.name, lifecycleId, operationId, - snapshot.SessionToken, snapshot.RefreshToken, - snapshot.WalletAddress, snapshot.AccountKind, - snapshot.AuthProvider); + snapshot?.SessionToken ?? "", snapshot?.RefreshToken ?? "", + snapshot?.WalletAddress ?? "", snapshot?.AccountKind ?? "", + snapshot?.AuthProvider ?? ""); #else throw new PlatformNotSupportedException(NativeUnavailableCode); #endif diff --git a/Tools~/package.test.mjs b/Tools~/package.test.mjs index 62a933a..e77a59d 100644 --- a/Tools~/package.test.mjs +++ b/Tools~/package.test.mjs @@ -20,7 +20,7 @@ async function allFiles(path = '') { test('UPM root is version 2 with only Unity module dependencies and current installer', async () => { const pkg = await json('package.json') - assert.equal(pkg.name, 'com.blockmaker.sdk'); assert.equal(pkg.version, '2.0.0') + assert.equal(pkg.name, 'com.blockmaker.sdk'); assert.equal(pkg.version, '2.0.1') assert.ok(Object.keys(pkg.dependencies).every(name => name.startsWith('com.unity.modules.'))) const runtime = await json('Runtime/Blockmaker.asmdef') assert.equal(runtime.name, 'Blockmaker'); assert.deepEqual(runtime.references, []) @@ -28,13 +28,13 @@ test('UPM root is version 2 with only Unity module dependencies and current inst assert.deepEqual(editor.includePlatforms, ['Editor']); assert.ok(editor.references.includes('Blockmaker')) const installer = await text('Installer~/BlockmakerInstaller.cs') assert.ok(installer.includes('Client.Add(PackageUrl)')) - assert.ok(installer.includes('https://github.com/blockmaker-ai/blockmaker-unity.git#v2.0.0')) + assert.ok(installer.includes('https://github.com/blockmaker-ai/blockmaker-unity.git#v2.0.1')) }) test('UPM carries the complete unmodified canonical runtime and matching build map', async () => { const value = await json('package-manifest.json') const manifest = parseWalletPackageManifest(value, { apiOrigin: new URL(value.members[0].url).origin, target: 'unity_webgl' }) - assert.equal(manifest.packageId, 'unity-webgl-package-sha256-cicBnjQf1cPzH5Sz3Gt7wX8seHXG9Cj1VTf6eRIAFlQ') + assert.equal(manifest.packageId, 'unity-webgl-package-sha256-ABEU4Xw6yrr_x5QxD0RBoncEqP2IStnSSnbbfQhI-5k') assert.equal(manifest.members.length, 9) const hook = await text('Editor/BlockmakerWebGLBuild.cs') for (const member of manifest.members) { diff --git a/package-manifest.json b/package-manifest.json index d25090e..7b29b9e 100644 --- a/package-manifest.json +++ b/package-manifest.json @@ -1,6 +1,6 @@ { "schemaVersion": "blockmaker-unity-webgl-package/v3", - "packageId": "unity-webgl-package-sha256-cicBnjQf1cPzH5Sz3Gt7wX8seHXG9Cj1VTf6eRIAFlQ", + "packageId": "unity-webgl-package-sha256-ABEU4Xw6yrr_x5QxD0RBoncEqP2IStnSSnbbfQhI-5k", "releaseVersion": "0.2.5", "target": "unity_webgl", "clientKind": "unity_webgl", @@ -146,12 +146,12 @@ "file": "blockmaker.js", "installPath": "Assets/StreamingAssets/Blockmaker/blockmaker.js", "role": "browser_runtime", - "url": "https://blockmaker.polaris.city/sdk/sha256-e5RS4bdQnySWxUMeksBqL35Jhsn9Ti9ta3e4vI5EheE/blockmaker.js", - "release": "sha256-e5RS4bdQnySWxUMeksBqL35Jhsn9Ti9ta3e4vI5EheE", - "sha256": "e5RS4bdQnySWxUMeksBqL35Jhsn9Ti9ta3e4vI5EheE", - "integrity": "sha256-e5RS4bdQnySWxUMeksBqL35Jhsn9Ti9ta3e4vI5EheE=", - "rawBytes": 476539, - "gzipBytes": 104260, + "url": "https://blockmaker.polaris.city/sdk/sha256-hyaT-gH-xfXHGrNyree6vzXdvLELylp3D1qVCExMGj0/blockmaker.js", + "release": "sha256-hyaT-gH-xfXHGrNyree6vzXdvLELylp3D1qVCExMGj0", + "sha256": "hyaT-gH-xfXHGrNyree6vzXdvLELylp3D1qVCExMGj0", + "integrity": "sha256-hyaT+gH+xfXHGrNyree6vzXdvLELylp3D1qVCExMGj0=", + "rawBytes": 477069, + "gzipBytes": 104355, "mediaType": "text/javascript; charset=utf-8" }, { @@ -170,12 +170,12 @@ "file": "blockmaker-txnlab-wallet.mjs", "installPath": "Assets/StreamingAssets/Blockmaker/blockmaker-txnlab-wallet.mjs", "role": "wallet_runtime", - "url": "https://blockmaker.polaris.city/sdk/sha256-3mZBZ6PO-tOCIYkGSaFjOY6ew2GeonepYn9QvdDmqjg/blockmaker-txnlab-wallet.mjs", - "release": "sha256-3mZBZ6PO-tOCIYkGSaFjOY6ew2GeonepYn9QvdDmqjg", - "sha256": "3mZBZ6PO-tOCIYkGSaFjOY6ew2GeonepYn9QvdDmqjg", - "integrity": "sha256-3mZBZ6PO+tOCIYkGSaFjOY6ew2GeonepYn9QvdDmqjg=", - "rawBytes": 2915457, - "gzipBytes": 689363, + "url": "https://blockmaker.polaris.city/sdk/sha256-qwFN8owcNbX9clc36uJj0mvymWnxq2nc8WlRGWM5r6Q/blockmaker-txnlab-wallet.mjs", + "release": "sha256-qwFN8owcNbX9clc36uJj0mvymWnxq2nc8WlRGWM5r6Q", + "sha256": "qwFN8owcNbX9clc36uJj0mvymWnxq2nc8WlRGWM5r6Q", + "integrity": "sha256-qwFN8owcNbX9clc36uJj0mvymWnxq2nc8WlRGWM5r6Q=", + "rawBytes": 2916010, + "gzipBytes": 689528, "mediaType": "text/javascript; charset=utf-8" }, { @@ -206,24 +206,24 @@ "file": "BlockmakerWalletPackageWebGL.cs", "installPath": "Assets/Blockmaker/Runtime/BlockmakerWalletPackageWebGL.cs", "role": "unity_wallet_facade", - "url": "https://blockmaker.polaris.city/sdk/sha256-IiRm2a95WYu2HfdelY_xoseTGyygNb9eHg7A3Yl6tjg/BlockmakerWalletPackageWebGL.cs", - "release": "sha256-IiRm2a95WYu2HfdelY_xoseTGyygNb9eHg7A3Yl6tjg", - "sha256": "IiRm2a95WYu2HfdelY_xoseTGyygNb9eHg7A3Yl6tjg", - "integrity": "sha256-IiRm2a95WYu2HfdelY/xoseTGyygNb9eHg7A3Yl6tjg=", - "rawBytes": 69767, - "gzipBytes": 10691, + "url": "https://blockmaker.polaris.city/sdk/sha256-IOqVNi79ssyWCl4s_ug0a9jNQSRRj6mpr8xwx6fsHoI/BlockmakerWalletPackageWebGL.cs", + "release": "sha256-IOqVNi79ssyWCl4s_ug0a9jNQSRRj6mpr8xwx6fsHoI", + "sha256": "IOqVNi79ssyWCl4s_ug0a9jNQSRRj6mpr8xwx6fsHoI", + "integrity": "sha256-IOqVNi79ssyWCl4s/ug0a9jNQSRRj6mpr8xwx6fsHoI=", + "rawBytes": 69920, + "gzipBytes": 10804, "mediaType": "text/plain; charset=utf-8" }, { "file": "BlockmakerWalletPackageWebGL.jslib", "installPath": "Assets/Plugins/WebGL/BlockmakerWalletPackageWebGL.jslib", "role": "unity_webgl_bridge", - "url": "https://blockmaker.polaris.city/sdk/sha256-3YSgAc9yn1g2KydfaZq2LWBmYdyw53ookdkGVPJhDKE/BlockmakerWalletPackageWebGL.jslib", - "release": "sha256-3YSgAc9yn1g2KydfaZq2LWBmYdyw53ookdkGVPJhDKE", - "sha256": "3YSgAc9yn1g2KydfaZq2LWBmYdyw53ookdkGVPJhDKE", - "integrity": "sha256-3YSgAc9yn1g2KydfaZq2LWBmYdyw53ookdkGVPJhDKE=", - "rawBytes": 53756, - "gzipBytes": 9437, + "url": "https://blockmaker.polaris.city/sdk/sha256-DXWoqlGvBiMTTd05SbNWd4-Qg-x5DxsQMoCjzG6RKfA/BlockmakerWalletPackageWebGL.jslib", + "release": "sha256-DXWoqlGvBiMTTd05SbNWd4-Qg-x5DxsQMoCjzG6RKfA", + "sha256": "DXWoqlGvBiMTTd05SbNWd4-Qg-x5DxsQMoCjzG6RKfA", + "integrity": "sha256-DXWoqlGvBiMTTd05SbNWd4+Qg+x5DxsQMoCjzG6RKfA=", + "rawBytes": 54580, + "gzipBytes": 9666, "mediaType": "text/plain; charset=utf-8" }, { @@ -242,17 +242,17 @@ "file": "BlockmakerUnityPresentation.cs", "installPath": "Assets/Blockmaker/Runtime/BlockmakerUnityPresentation.cs", "role": "unity_presentation", - "url": "https://blockmaker.polaris.city/sdk/sha256-o3V6YFGJnthppEacqS4h1mCYYPQhwTGZarTzyh1o25E/BlockmakerUnityPresentation.cs", - "release": "sha256-o3V6YFGJnthppEacqS4h1mCYYPQhwTGZarTzyh1o25E", - "sha256": "o3V6YFGJnthppEacqS4h1mCYYPQhwTGZarTzyh1o25E", - "integrity": "sha256-o3V6YFGJnthppEacqS4h1mCYYPQhwTGZarTzyh1o25E=", - "rawBytes": 37963, - "gzipBytes": 8304, + "url": "https://blockmaker.polaris.city/sdk/sha256-ApN2mHPa-U_8Z0pM-yuknXqz3wWFyeMq0GimX809f9c/BlockmakerUnityPresentation.cs", + "release": "sha256-ApN2mHPa-U_8Z0pM-yuknXqz3wWFyeMq0GimX809f9c", + "sha256": "ApN2mHPa-U_8Z0pM-yuknXqz3wWFyeMq0GimX809f9c", + "integrity": "sha256-ApN2mHPa+U/8Z0pM+yuknXqz3wWFyeMq0GimX809f9c=", + "rawBytes": 58000, + "gzipBytes": 19209, "mediaType": "text/plain; charset=utf-8" } ], "provenance": { - "sourceCommit": "226bd9136b268cd1c7005efdb3bccf23be50e607", + "sourceCommit": "4e56ec769561f54b561cea28a181995883e3983d", "contentAddressing": "sha256", "allMembersVerifiedBeforePublication": true } diff --git a/package.json b/package.json index 2f5f8c5..19e7234 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.blockmaker.sdk", - "version": "2.0.0", + "version": "2.0.1", "displayName": "Blockmaker Wallets", "description": "Unity WebGL wallets on Algorand MainNet: Pera, Lute and optional passwordless email via TxnLab/MetaMask Embedded Wallets. Includes in-game wallet UI and shared player profiles.", "unity": "6000.0",