diff --git a/package-lock.json b/package-lock.json index acbb8d7..8fa7145 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,9 @@ "@1shotapi/ows-oid4": "^0.4.0", "@1shotapi/ows-provider": "^0.5.1", "@1shotapi/ows-signer": "^0.4.2", - "@1shotapi/ows-signer-utils": "^0.6.2", - "@1shotapi/ows-types": "^0.8.0", - "@1shotapi/ows-wallet-utils": "^0.5.1", + "@1shotapi/ows-signer-utils": "^0.6.4", + "@1shotapi/ows-types": "^0.11.0", + "@1shotapi/ows-wallet-utils": "^0.5.2", "@circle-fin/app-kit": "^1.15.2", "@fontsource-variable/geist": "^5.3.0", "@metamask/smart-accounts-kit": "^2.0.0", @@ -71,6 +71,17 @@ "wxt": "^0.21.4" } }, + "extension/node_modules/@1shotapi/ows-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@1shotapi/ows-types/-/ows-types-0.8.0.tgz", + "integrity": "sha512-uF2vpaeVO7KRek9IJcByWLXHGwM0b7XcRzi26w1nMHfDhPsLNgRD2e+80qBtf24x4XX5Zd54tX/zQmLsYE5XoA==", + "license": "MIT", + "dependencies": { + "@sd-jwt/core": "^0.20.0", + "@sd-jwt/sd-jwt-vc": "^0.20.0", + "ts-brand": "^0.2.0" + } + }, "host": { "name": "@1shotapi/oneshot-wallet-host", "version": "0.0.0", @@ -101,6 +112,17 @@ "vite": "^7.0.4" } }, + "host/node_modules/@1shotapi/ows-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@1shotapi/ows-types/-/ows-types-0.8.0.tgz", + "integrity": "sha512-uF2vpaeVO7KRek9IJcByWLXHGwM0b7XcRzi26w1nMHfDhPsLNgRD2e+80qBtf24x4XX5Zd54tX/zQmLsYE5XoA==", + "license": "MIT", + "dependencies": { + "@sd-jwt/core": "^0.20.0", + "@sd-jwt/sd-jwt-vc": "^0.20.0", + "ts-brand": "^0.2.0" + } + }, "host/node_modules/lucide-react": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.24.0.tgz", @@ -157,9 +179,9 @@ "license": "MIT" }, "node_modules/@1shotapi/ows-signer-utils": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@1shotapi/ows-signer-utils/-/ows-signer-utils-0.6.2.tgz", - "integrity": "sha512-/ZcOtonPdy8Sx1HlzZ+13dxEYEc1/3rQMky8z851zK1y1wRVVWnZXxAnDlkub4lmXlxzfnizgEHva6GNZ0wVLg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@1shotapi/ows-signer-utils/-/ows-signer-utils-0.6.4.tgz", + "integrity": "sha512-LwaiE1g4QO22C1CbXDUWpx/8Wk9B8U1uRtdf1HgdISWG6NsqDfKP6B9Ar7RYnAQhSr1C3Liv9RYeseNUNqbQ5Q==", "license": "MIT", "dependencies": { "@1shotapi/ows-types": "*", @@ -200,26 +222,38 @@ } }, "node_modules/@1shotapi/ows-types": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@1shotapi/ows-types/-/ows-types-0.8.0.tgz", - "integrity": "sha512-uF2vpaeVO7KRek9IJcByWLXHGwM0b7XcRzi26w1nMHfDhPsLNgRD2e+80qBtf24x4XX5Zd54tX/zQmLsYE5XoA==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@1shotapi/ows-types/-/ows-types-0.11.0.tgz", + "integrity": "sha512-5ReME3Bu0bMIoMV6R2L8+e6d+8vnugWMqvJldekHkA9pQ9NDzR9RLBwbGkIH/PkF+tYsKgxTqcPKG0lr7fij/g==", "license": "MIT", "dependencies": { + "@scure/base": "^2.4.0", "@sd-jwt/core": "^0.20.0", "@sd-jwt/sd-jwt-vc": "^0.20.0", - "ts-brand": "^0.2.0" + "ts-brand": "^0.2.0", + "viem": "^2.52.0", + "zod": "^4.6.5" + } + }, + "node_modules/@1shotapi/ows-types/node_modules/@scure/base": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.4.0.tgz", + "integrity": "sha512-thZ1TuJwFwBblOhgsjDKvvGirBxNp+wSvY/DR6tJBJOTDhdAAcHJ8Vbr2eFnqaxeca4+t0i9KBf+uHYGWwZORg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@1shotapi/ows-wallet-utils": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@1shotapi/ows-wallet-utils/-/ows-wallet-utils-0.5.1.tgz", - "integrity": "sha512-VpIuwecVHlHYjWtEaHhsvjsaU2LIGJJ7gLUZOTOWASiZ4SWPziS7hEHuNr5QhU9MfD664i1xqeoDs6lZxCeZ1A==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@1shotapi/ows-wallet-utils/-/ows-wallet-utils-0.5.2.tgz", + "integrity": "sha512-7uJ6iDju/j8gQOZB1Aql6Ydp94Qds7UBt9inhKnAlH9Ovevy6uZDKtkRaX0gZId4q4rS/cGKjBbYWIyZbhuGcQ==", "license": "MIT", "dependencies": { "@1shotapi/ows-types": "*", "@scure/base": "^2.4.0", "postmate": "^1.5.2", - "zod": "^4.4.0" + "zod": "^4.6.5" }, "peerDependencies": { "viem": "^2.0.0" @@ -16615,9 +16649,9 @@ "license": "MIT" }, "node_modules/zod": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", - "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 4bbe18d..aaec616 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,9 @@ "@1shotapi/ows-oid4": "^0.4.0", "@1shotapi/ows-provider": "^0.5.1", "@1shotapi/ows-signer": "^0.4.2", - "@1shotapi/ows-signer-utils": "^0.6.2", - "@1shotapi/ows-types": "^0.8.0", - "@1shotapi/ows-wallet-utils": "^0.5.1", + "@1shotapi/ows-signer-utils": "^0.6.4", + "@1shotapi/ows-types": "^0.11.0", + "@1shotapi/ows-wallet-utils": "^0.5.2", "@circle-fin/app-kit": "^1.15.2", "@fontsource-variable/geist": "^5.3.0", "@metamask/smart-accounts-kit": "^2.0.0", diff --git a/src/lib/implementations/data/CachedRelayerVaultRepository.ts b/src/lib/implementations/data/CachedRelayerVaultRepository.ts index d1fbcaf..fd9621a 100644 --- a/src/lib/implementations/data/CachedRelayerVaultRepository.ts +++ b/src/lib/implementations/data/CachedRelayerVaultRepository.ts @@ -1,5 +1,5 @@ import { - AES256CipherText, + AES256CipherTextEnvelope, DomainString, EVMAccountAddress, EVMChainId, @@ -337,7 +337,7 @@ export class CachedRelayerVaultRepository const signer = await this.owsProvider.getSigner(); const ciphertexts = remote.map((item) => - AES256CipherText(item.ciphertext), + AES256CipherTextEnvelope(item.ciphertext), ); const plaintexts = await signer.decryptAES256(ciphertexts); diff --git a/src/lib/implementations/data/EVMRepository.ts b/src/lib/implementations/data/EVMRepository.ts index 76e2925..e3519ff 100644 --- a/src/lib/implementations/data/EVMRepository.ts +++ b/src/lib/implementations/data/EVMRepository.ts @@ -96,8 +96,9 @@ export class EVMRepository implements IEVMRepository { } return { + // Interim local id: 0x-prefixed hex slice of the broadcast hash. relayerTransactionId: RelayerTransactionId( - `interim-${hash.slice(2, 18)}`, + `0x${hash.slice(2, 18)}` as `0x${string}`, ), transactionHash: EVMTransactionHash(hash as `0x${string}`), }; diff --git a/src/ows/registerAccountConnect.ts b/src/ows/registerAccountConnect.ts index a355166..fc0b82c 100644 --- a/src/ows/registerAccountConnect.ts +++ b/src/ows/registerAccountConnect.ts @@ -32,6 +32,7 @@ type WalletPermission = { type DisplayHandle = { hide: () => Promise; + release?: () => void; }; /** Host Inline (extension side panel) can miss a second displayReady; don't block connect UX. */ @@ -145,9 +146,10 @@ export function registerAccountConnect( } async function acquireDisplay(wallet: OWSWallet): Promise { + const pending = wallet.requestDisplay(); try { const session = await Promise.race([ - wallet.requestDisplay(), + pending, new Promise((resolve) => { setTimeout(() => resolve(null), DISPLAY_ACQUIRE_TIMEOUT_MS); }), @@ -156,8 +158,28 @@ async function acquireDisplay(wallet: OWSWallet): Promise { return session; } } catch { - // Fall through to a no-op handle — panel may already be Inline-visible. + // Fall through — panel may already be Inline-visible. } + + // Race lost or threw: still release a late-arriving session so host + // childDisplayId / branding displayDepth cannot leak into later RPCs (e.g. SIWE). + void pending.then( + (session) => { + try { + if ("release" in session && typeof session.release === "function") { + session.release(); + } else { + void session.hide(); + } + } catch { + // Best-effort cleanup. + } + }, + () => { + // requestDisplay rejected — nothing to release. + }, + ); + return { hide: async () => {}, }; diff --git a/src/ows/registerApprovalSigning.ts b/src/ows/registerApprovalSigning.ts index 1f67027..d74a5a0 100644 --- a/src/ows/registerApprovalSigning.ts +++ b/src/ows/registerApprovalSigning.ts @@ -16,7 +16,7 @@ export type RegisterApprovalSigningOptions = { * authenticates. Pair with {@link onAuthenticated}. */ ensureReady?: () => Promise; - /** Mark unlocked + refresh addresses after a successful message/typed-data ceremony. */ + /** Mark unlocked + refresh addresses after a successful signing ceremony. */ onAuthenticated?: () => void | Promise; chainRpc: SignHelperChainRpc; /** @@ -45,8 +45,10 @@ export type RegisterApprovalSigningOptions = { /** * Build SignHelper handlers and register them on the wallet (pre-`start()`). * - * SignHelper only adapts EIP-1193 ↔ `approveAndSign*`. Setup / unlock live here - * so branding owns the link to `OWSSigner`. + * SignHelper adapts EIP-1193 ↔ `approveAndSign*`. Setup (`ensureReady`) runs + * inside approve callbacks (while the display session is held). Unlock + * (`onAuthenticated`) is passed through to SignHelper so it runs after + * display release — post-sign address refresh must not keep the flyout open. */ export function registerApprovalSigning( wallet: OWSWallet, @@ -55,17 +57,16 @@ export function registerApprovalSigning( ): SignHelper { const helper = new SignHelper(signer, wallet, { getChainId: () => options.chainRpc.getChainId(), + // Runs after SignHelper releases the display session so address refresh + // cannot keep the flyout open after consent/passkey finishes. + onAuthenticated: options.onAuthenticated, approveAndSignPersonalMessage: async (request) => { await options.ensureReady?.(); - const signature = await options.approveAndSignPersonalMessage(request); - await options.onAuthenticated?.(); - return signature; + return options.approveAndSignPersonalMessage(request); }, approveAndSignTypedData: async (request) => { await options.ensureReady?.(); - const signature = await options.approveAndSignTypedData(request); - await options.onAuthenticated?.(); - return signature; + return options.approveAndSignTypedData(request); }, approveAndSignTransaction: async (request) => { await options.ensureReady?.(); diff --git a/src/wallet/WalletProvider.tsx b/src/wallet/WalletProvider.tsx index 9a8a132..b9fb422 100644 --- a/src/wallet/WalletProvider.tsx +++ b/src/wallet/WalletProvider.tsx @@ -536,6 +536,16 @@ export function WalletProvider({ children }: { children: ReactNode }) { const previous = rpc.getChainId(); try { await rpc.switchChain(next); + // RpcHelper no-ops when already on `next` (e.g. session was Bitcoin while + // the helper stayed on Arc). Sync session + notify when the event path + // did not run — avoid double-emit when onChainChanged already updated. + const session = useWalletSessionStore.getState(); + if ( + String(session.chainId).toLowerCase() !== String(next).toLowerCase() + ) { + session.setChainId(next); + walletRef.current?.providerEvents.emit("chainChanged", next); + } } catch (error: unknown) { useWalletSessionStore.getState().setChainId(previous); console.error("[oneshot-wallet] chain switch failed", error); diff --git a/src/wallet/registerSwitchChain.ts b/src/wallet/registerSwitchChain.ts index b09de1d..2b39b04 100644 --- a/src/wallet/registerSwitchChain.ts +++ b/src/wallet/registerSwitchChain.ts @@ -64,7 +64,16 @@ export function registerSwitchChainRpc( } await rpcHelper.switchChain(String(chainId)); - useWalletSessionStore.getState().setChainId(chainId); + // RpcHelper no-ops when already on `chainId` (e.g. session was Bitcoin + // while the helper stayed on that EVM). Sync + emit only when session + // still differs — onChainChanged already handled a real RpcHelper change. + const session = useWalletSessionStore.getState(); + if ( + String(session.chainId).toLowerCase() !== String(chainId).toLowerCase() + ) { + session.setChainId(chainId); + wallet.providerEvents.emit("chainChanged", chainId); + } return { ok: true as const, chainId, diff --git a/src/wallet/useWalletBoot.ts b/src/wallet/useWalletBoot.ts index 7ef3808..12e108d 100644 --- a/src/wallet/useWalletBoot.ts +++ b/src/wallet/useWalletBoot.ts @@ -900,7 +900,6 @@ export function useWalletBoot({ ); } - await onSigningAuthenticated(); return hash; }, {