Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 51 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions src/lib/implementations/data/CachedRelayerVaultRepository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
AES256CipherText,
AES256CipherTextEnvelope,
DomainString,
EVMAccountAddress,
EVMChainId,
Expand Down Expand Up @@ -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);

Expand Down
3 changes: 2 additions & 1 deletion src/lib/implementations/data/EVMRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`),
};
Expand Down
26 changes: 24 additions & 2 deletions src/ows/registerAccountConnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type WalletPermission = {

type DisplayHandle = {
hide: () => Promise<void>;
release?: () => void;
};

/** Host Inline (extension side panel) can miss a second displayReady; don't block connect UX. */
Expand Down Expand Up @@ -145,9 +146,10 @@ export function registerAccountConnect(
}

async function acquireDisplay(wallet: OWSWallet): Promise<DisplayHandle> {
const pending = wallet.requestDisplay();
try {
const session = await Promise.race([
wallet.requestDisplay(),
pending,
new Promise<null>((resolve) => {
setTimeout(() => resolve(null), DISPLAY_ACQUIRE_TIMEOUT_MS);
}),
Expand All @@ -156,8 +158,28 @@ async function acquireDisplay(wallet: OWSWallet): Promise<DisplayHandle> {
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 () => {},
};
Expand Down
19 changes: 10 additions & 9 deletions src/ows/registerApprovalSigning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type RegisterApprovalSigningOptions = {
* authenticates. Pair with {@link onAuthenticated}.
*/
ensureReady?: () => Promise<void>;
/** Mark unlocked + refresh addresses after a successful message/typed-data ceremony. */
/** Mark unlocked + refresh addresses after a successful signing ceremony. */
onAuthenticated?: () => void | Promise<void>;
chainRpc: SignHelperChainRpc;
/**
Expand Down Expand Up @@ -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,
Expand All @@ -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?.();
Expand Down
10 changes: 10 additions & 0 deletions src/wallet/WalletProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
11 changes: 10 additions & 1 deletion src/wallet/registerSwitchChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 0 additions & 1 deletion src/wallet/useWalletBoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -900,7 +900,6 @@ export function useWalletBoot({
);
}

await onSigningAuthenticated();
return hash;
},
{
Expand Down
Loading