Skip to content

Conversation

@petar-omni
Copy link
Collaborator

No description provided.

@vercel
Copy link

vercel bot commented Jan 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
perps-widget Ready Ready Preview, Comment Jan 25, 2026 4:46pm

Request Review

@socket-security
Copy link

socket-security bot commented Jan 23, 2026

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Ledger Live (embedded dApp browser) wallet support by abstracting signing behind a Signer interface and adapting the app’s wallet/runtime flow to work with either a browser wallet (Reown/AppKit + wagmi) or Ledger Live APIs.

Changes:

  • Introduces Signer abstraction with separate Browser and Ledger implementations and updates WalletService to use it.
  • Updates wallet/domain models and UI/atoms to support multi-account switching and to key portfolio/token state by wallet address.
  • Adds Ledger Live manifest + dependency updates and refines a few dialog trigger patterns and numeric formatting.

Reviewed changes

Copilot reviewed 40 out of 42 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/services/wallet/wallet-service.ts Refactors signing flow to use Signer and emits wallet state from signer account stream.
src/services/wallet/signer.ts Adds Signer context contract (accounts stream, sign tx, switch account).
src/services/wallet/ledger-signer/utils.ts Ledger Live helper utilities (accounts/currencies filtering + embed detection).
src/services/wallet/ledger-signer/index.ts Implements Ledger Live signer layer using @ledgerhq/wallet-api-client.
src/services/wallet/browser-signer.ts Implements browser signer layer with AppKit + wagmi actions.
src/services/runtime.ts Chooses signer implementation at runtime and wires layers together.
src/services/config.ts Makes VITE_REOWN_PROJECT_ID optional via Config.option.
src/lib/utils.ts Adds truncateNumber helper to avoid rounding up in UI amounts.
src/domain/wallet.ts Updates wallet/account domain types for browser vs ledger and adds switchAccount.
src/domain/transactions.ts Reworks transaction schemas and adds TransactionHash branding.
src/domain/chains/ledger.ts Adds Ledger family/currency mapping for supported EVM chains.
src/domain/chains/evm.ts Narrows supported EVM chain list used by the app.
src/context/appkit.tsx Gates wagmi provider usage based on wallet type.
src/components/molecules/tp-sl-dialog.tsx Adjusts dialog trigger usage.
src/components/molecules/address-switcher.tsx Replaces AppKit account UI with in-app account switch + copy/disconnect actions.
src/components/molecules/account-value-display.tsx Keys provider-balance selection by wallet address.
src/components/modules/Root/PreloadAtoms.tsx Preloads connected-state atoms using wallet address keys.
src/components/modules/PositionDetails/Overview/index.tsx Keys positions atom by wallet address.
src/components/modules/PositionDetails/Overview/Position/index.tsx Keys positions/orders atoms by wallet address.
src/components/modules/PositionDetails/Overview/Orders/index.tsx Updates cancel order flow to pass wallet address.
src/components/modules/PositionDetails/Close/state.tsx Refactors close-position state to use wallet address (not wallet object).
src/components/modules/PositionDetails/Close/index.tsx Updates hook usage to pass wallet address.
src/components/modules/Order/Overview/state.tsx Uses wallet address for balance atoms; switches to truncateNumber.
src/components/modules/Order/Overview/order-type-dialog.tsx Adjusts dialog trigger usage.
src/components/modules/Order/Overview/limit-price-dialog.tsx Adjusts dialog trigger usage.
src/components/modules/Order/Overview/leverage-dialog.tsx Adjusts dialog trigger usage.
src/components/modules/Home/index.tsx Adjusts connect/address-switcher behavior for browser vs ledger wallet types.
src/components/modules/Home/Positions/index.tsx Keys portfolio atoms by wallet address.
src/components/modules/Account/balance.tsx Keys providers balances atom by wallet address.
src/components/modules/Account/Withdraw/state.tsx Keys selected balances by address; switches to truncateNumber.
src/components/modules/Account/Deposit/state.tsx Refactors deposit atoms/hooks to be keyed by wallet address; uses truncateNumber.
src/components/modules/Account/Deposit/index.tsx Updates deposit UI hooks to pass wallet address.
src/atoms/wallet-atom.ts Simplifies wallet atom to consume wallet stream and adds switchAccount atom.
src/atoms/tokens-atoms.ts Updates Moralis token balances atom to be keyed by wallet address type.
src/atoms/position-pending-actions-atom.ts Updates pending position action atoms to accept wallet address.
src/atoms/portfolio-atoms.ts Refactors portfolio atoms to be keyed by wallet address (not wallet object).
src/atoms/orders-pending-actions-atom.ts Updates cancel order atom to accept wallet address.
src/atoms/actions-atoms.ts Refactors signing machine atoms and changes post-action invalidation behavior.
pnpm-lock.yaml Adds Ledger Wallet API Client dependency tree.
package.json Adds @ledgerhq/wallet-api-client.
ledger-manifest/manifest.dev.json Adds Ledger Live dApp manifest for local dev embedding.
.gitignore Ignores opensrc/ directory.
src/components/molecules/tp-sl-dialog.tsx Adjusts trigger rendering to children-based usage.
src/components/molecules/address-switcher.tsx Adds account switching UI and disconnect/copy actions.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (2)

src/services/wallet/wallet-service.ts:29

  • WalletService declares dependencies: [ApiClientService.Default], but the runtime layer (src/services/runtime.ts) also merges ApiClientService.Default. This is redundant and makes it unclear which layer composition is intended.

For clarity, prefer one approach: either (a) remove the dependency here and provide ApiClientService.Default from runtime.ts, or (b) keep the dependency here and stop merging ApiClientService.Default separately in runtime.ts.
src/context/appkit.tsx:26

  • QueryClientProvider is only rendered when wallet.type === "browser". In ledger mode (or any non-browser wallet), children is returned without a React Query provider, which will break any @tanstack/react-query hooks used in the app.

Consider always wrapping children with QueryClientProvider, and only conditionally wrapping with WagmiProvider when a browser wallet (wagmi config) is available.

  if (Result.isSuccess(result) && result.value.wallet.type === "browser") {
    return (
      <WagmiProvider config={result.value.wallet.wagmiAdapter.wagmiConfig}>
        <QueryClientProvider client={qc}>{children}</QueryClientProvider>
      </WagmiProvider>
    );
  }

  return children;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +189 to +199
const switchAccount = Effect.fn(
function* ({ account }: { account: WalletAccount }) {
const connection = yield* Option.fromNullable(
wagmiAdapter.wagmiConfig.state.connections.get(account.id),
);

yield* Effect.tryPromise(() =>
switchConnection(wagmiAdapter.wagmiConfig, {
connector: connection.connector,
}),
);
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switchAccount looks up wagmiConfig.state.connections using account.id, but accountsStateRef sets WalletAccount.id to the account address (from connection.value.accounts). connections is keyed by currentConnectionId (see nextState.current), so this lookup will always miss and account switching will fail.

Store the connection id alongside the account (or map from selected address back to the active connection) and use that key when calling switchConnection.

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +44
Stream.onDone(() =>
Reactivity.pipe(
Effect.andThen((reactivity) =>
reactivity.invalidate(portfolioReactivityKeysArray),
),
),
Stream.runForEach((val) =>
Effect.sync(() => ctx.setSelf(Result.success(val))),
),
Effect.runFork,
);

return state;
}),
),
),
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After signing completes, the code now invalidates only portfolioReactivityKeysArray. Actions include fund/withdraw (see ActionDtoAction in src/services/api-client/api-schemas.ts), which also affect token balances (Moralis) and token pricing state.

Re-add invalidation for tokensReactivityKeysArray (and any other relevant reactivity keys) so UI balances refresh immediately after these actions.

Copilot uses AI. Check for mistakes.
Comment on lines +110 to +112
export const isBrowserWallet = (
wallet: Wallet | null,
): wallet is WalletConnected & BrowserWallet => wallet?.type === "browser";
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isBrowserWallet only checks wallet?.type === "browser" but its return type narrows to WalletConnected & BrowserWallet. This is unsound: a disconnected browser wallet will satisfy the predicate.

Either (a) also check wallet?.status === "connected", or (b) widen the return type to include disconnected browser wallets (e.g. WalletBase & BrowserWallet).

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +76
export const BrowserSignerLayer = Effect.gen(function* () {
const projectId = yield* ConfigService.pipe(
Effect.andThen((config) => config.reownProjectId),
);

const networks: NonEmptyArray<
AppKitNetwork & { skChainName: SupportedSKChains }
> = [
{ ...mainnet, skChainName: EvmNetworks.Ethereum },
{ ...base, skChainName: EvmNetworks.Base },
{ ...arbitrum, skChainName: EvmNetworks.Arbitrum },
{ ...optimism, skChainName: EvmNetworks.Optimism },
{ ...monad, skChainName: EvmNetworks.Monad },
{ ...hyperLiquidL1, skChainName: EvmNetworks.HyperEVM },
];

const chainsMap = Record.fromIterableBy(networks, (network) =>
network.id.toString(),
);

const wagmiAdapter = new WagmiAdapter({
networks,
projectId,
multiInjectedProviderDiscovery: true,
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConfigService.reownProjectId is now optional (Config.option), but BrowserSignerLayer uses it directly as projectId for WagmiAdapter/createAppKit. This will pass an Option/optional value where a string is expected and can break wallet initialization.

Unwrap/validate the project id here (fail fast with a clear error when missing), or keep VITE_REOWN_PROJECT_ID required for the browser signer path.

Copilot uses AI. Check for mistakes.
Comment on lines +172 to +183
export const isLedgerDappBrowserProvider = Option.fromNullable(window).pipe(
Option.map((w) => {
try {
const params = new URLSearchParams(w.self.location.search);

return !!params.get("embed");
} catch (_error) {
return false;
}
}),
Option.getOrElse(() => false),
);
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isLedgerDappBrowserProvider references window at module top-level. In non-browser contexts (SSR, tests, build tooling) this can throw a ReferenceError during import.

Guard with typeof window !== "undefined" (or compute lazily inside a function) before accessing window/location.

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +8
export const EvmTx = Schema.Struct({
from: Schema.String,
to: HexString,
data: HexString,
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EvmTx is now an exact Schema.Struct with a fixed set of fields and no index signature. However the backend exposes TransactionDto.signablePayload as Record<string, unknown> (see src/services/api-client/api-schemas.ts:572), so additional transaction fields will cause decoding to fail and the signing flow will die.

Consider restoring an index signature (allow unknown extra props) or modeling the actual payload shape more permissively.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +13
value: Schema.optional(Schema.BigInt),
maxFeePerGas: Schema.optional(Schema.BigInt),
maxPriorityFeePerGas: Schema.optional(Schema.BigInt),
gasLimit: Schema.BigInt,
nonce: Schema.Number,
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EvmTx uses Schema.BigInt for gasLimit/fees/value. Since signablePayload comes from the API as JSON (Record<string, unknown>), these numeric fields will typically arrive as strings or numbers (JSON can’t encode bigint).

Ensure the schema decodes from the JSON representation (e.g., BigInt-from-string/number), otherwise Schema.decodeUnknown(Transaction) may reject valid payloads.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants