> For the complete documentation index, see [llms.txt](https://docs.startrail.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.startrail.io/startrail-sdk-js/change-logs/latest.md).

# v2.3.0

Redesigned white-label login modal with light/dark mode, session restore on fresh instances, login({ forceLogin }) and a hosted playground

## [npmjs](https://www.npmjs.com/package/@startbahn/startrail-sdk-js/v/2.3.0)

{% hint style="success" %}
**Highlight —** the SDK now ships its **own login modal**: brand-neutral by default, fully white-label (colours, logo placement, radii, dark mode, wording), accessible and mobile-ready. A fresh SDK instance **restores the wallet session** instead of prompting, `login({ forceLogin: true })` lets a user switch accounts in one call, and every method can be tried live at [**sdk.startrail.io**](https://sdk.startrail.io/).
{% endhint %}

## 🎮 Try it before you install anything

{% embed url="<https://sdk.startrail.io/>" %}
The SDK playground — always runs the latest npm release
{% endembed %}

<figure><img src="https://3244648189-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOu6aN3RW264zdJsOQMJ2%2Fuploads%2Fgit-blob-854e5df3cbf1925634ec1f1753a7f6b2ea1c3104%2Fsdk-playground-overview.webp?alt=media" alt="The Startrail SDK playground: version badge, environment switch, session pill and one card per SDK method"><figcaption><p>Every SDK method as a card: fill the form, press Run, read the exact outcome and the code you would write.</p></figcaption></figure>

* Loads the **latest release on npm** at run time (the version is in the top bar); `?sdk=2.2.0` pins an older release for comparison.
* A **brand studio** to preview the login modal with your colours, logo and wording — then copy the `customUi` config.
* A **sign-in panel** for every provider combination, a **switch-user scenario**, and an activity log you can export for a bug report.

## ✨ What's new

### 1. A redesigned login modal

The provider picker that opens when `loginProvider` offers a choice (or `email_passwordless` is used without a `loginHint`) has been rebuilt from scratch and is rendered by the SDK itself, in a Shadow DOM, so your page's CSS never affects it and its CSS never leaks out.

<figure><img src="https://3244648189-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOu6aN3RW264zdJsOQMJ2%2Fuploads%2Fgit-blob-c6fec297915325ef4941efd5b24168c6db8a89fb%2Fsdk-login-modal-default-light-dark.webp?alt=media" alt="The default login modal in light and dark mode"><figcaption><p>Brand-neutral default: near-black accent on white, inverted on the dark card.</p></figcaption></figure>

* **Light, dark and auto** (follows `prefers-color-scheme`).
* **States that explain themselves:** inline email validation, "Waiting for {provider}…", a friendly notice when the user closes the provider window, and an inline error with retry.
* **Accessible:** focus trap, Escape to close, screen-reader labels, reduced-motion support.
* **Mobile:** a bottom sheet under 480 px with safe-area padding.
* **Localised:** English and Japanese from `lang`.

<details>

<summary>More screenshots — mobile, Japanese, states</summary>

<figure><img src="https://3244648189-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOu6aN3RW264zdJsOQMJ2%2Fuploads%2Fgit-blob-38c879d1d9e54104ec5a6cd95622a93d52c557af%2Fsdk-login-modal-mobile-light-dark.webp?alt=media" alt="The modal as a bottom sheet on a phone, light and dark"><figcaption><p>Bottom sheet on phones.</p></figcaption></figure>

<figure><img src="https://3244648189-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOu6aN3RW264zdJsOQMJ2%2Fuploads%2Fgit-blob-aac98371f2227c3b6f42d5a0bc61398e46f600aa%2Fsdk-login-modal-japanese.webp?alt=media" alt="The modal in Japanese"><figcaption><p><code>lang: 'ja'</code></p></figcaption></figure>

<figure><img src="https://3244648189-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOu6aN3RW264zdJsOQMJ2%2Fuploads%2Fgit-blob-72b4e2cc0d016dbcf98e51f89d0048531e1cb9a2%2Fsdk-login-modal-flow-waiting-cancelled-error.webp?alt=media" alt="Waiting, cancelled and error states"><figcaption><p>Waiting → cancelled → error, all inside the modal.</p></figcaption></figure>

</details>

### 2. White-label it — `customUi.theme`

One `customUi` block brands the modal **and** the wallet UI it hands off to.

<figure><img src="https://3244648189-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOu6aN3RW264zdJsOQMJ2%2Fuploads%2Fgit-blob-188f449f373a7981b49cd1263ca8e8233c71da0d%2Fsdk-login-modal-brand-port-light-dark.webp?alt=media" alt="The modal themed for PORT: purple accent, pill-shaped filled buttons, light and dark"><figcaption><p>PORT: <code>accent</code>, pill-shaped <code>controlRadius: '50%'</code>, <code>buttonStyle: 'filled'</code>.</p></figcaption></figure>

<figure><img src="https://3244648189-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOu6aN3RW264zdJsOQMJ2%2Fuploads%2Fgit-blob-0274c2a4efbb5d0ee64cb7be860b498702d377fc%2Fsdk-login-modal-brand-acme-light-dark.webp?alt=media" alt="The modal themed for Acme Gallery: orange accent, circular logo inline beside a left-aligned title"><figcaption><p>Acme Gallery: <code>logoPlacement: 'inline'</code>, <code>logoShape: 'circle'</code>, <code>align: 'left'</code>, custom font and wording.</p></figcaption></figure>

{% code title="customUi.theme" overflow="wrap" %}

```typescript
const sdk = new Startrail({
  customUi: {
    serviceName: 'Acme Gallery',                         // "to continue to Acme Gallery"
    logoUrl: 'https://acme.example/logo.png',            // light backgrounds
    logoWhiteUrl: 'https://acme.example/logo-white.png', // dark mode
    theme: {
      mode: 'auto',              // 'light' (default) | 'dark' | 'auto'
      accent: '#ff5a1f',         // buttons, links, focus rings — text colour is picked for contrast
      accentDark: '#ffb48f',     // optional dark-mode accent
      fontFamily: "'Avenir Next', 'Segoe UI', system-ui, sans-serif",
      borderRadius: 10,          // dialog corners, px (0–40)
      controlRadius: '50%',      // buttons and inputs: px (0–24) or '50%' for pills
      logoPlacement: 'inline',   // 'top' (default) | 'inline' | 'none'
      logoSize: 40,              // px (24–120)
      logoShape: 'circle',       // 'rounded' (default) | 'circle' | 'square' | 'plain'
      buttonStyle: 'filled',     // provider buttons: 'outline' (default) | 'filled' | 'accent'
      align: 'left',             // 'center' (default) | 'left'
    },
    words: {
      en: { modal: { title: 'Welcome to Acme', subtitle: 'Sign in to manage your collection', privacyPolicy: 'Privacy notice' } },
      ja: { modal: { title: 'Acme へようこそ', subtitle: 'ログインしてコレクションを管理' } },
    },
  },
})
```

{% endcode %}

{% hint style="info" %}
Text on the accent colour is chosen automatically by WCAG luminance, and a near-black accent inverts to a light neutral in dark mode, so **one colour is enough** to start. Per-site overrides are also possible through CSS custom properties (`--startrail-modal-accent`, `--startrail-modal-radius`, …) on the modal container.
{% endhint %}

{% content-ref url="/pages/ArKSzO1J1eosVczBoEWW" %}
[Whitelabeling/Customizing](/startrail-sdk-js/login-providers/whitelabeling.md)
{% endcontent-ref %}

### 3. Sessions are restored, not re-prompted

A Startrail-wallet session lives in the wallet iframe, which needs a moment to rehydrate after a page load. Previously a **fresh SDK instance** could open the picker for a user who was in fact signed in, and then ignore their choice once the session landed.

From 2.3.0 the SDK records a timestamp hint (no addresses) on your origin after each successful login — `localStorage['startrail-sdk:torus-session']`, removed on `logout()` — and a new instance **waits for the restore** before ever showing the picker. If the modal has to open in the meantime it starts in a *"Checking for an existing session…"* state and closes itself when the session is back.

{% hint style="success" %}
Nothing to change in your code: `login()`, `signMessage()` and every write action on a new instance simply resume the session.
{% endhint %}

### 4. Switch user with `login({ forceLogin: true })`

Because a session is resumed silently, `login({ loginProvider: ['google'] })` while another user is signed in returned *their* accounts and never showed Google. Pass `forceLogin: true` to end the current session first — the same as `logout()` then `login()`, in one call, with the requested provider and `loginHint` honoured.

{% code title="Switch account" overflow="wrap" %}

```typescript
// Sign out whoever is logged in, then sign in with Google
const eoas = await sdk.login({ loginProvider: ['google'], forceLogin: true })
```

{% endcode %}

Per-call overrides that omit `loginProvider` now **keep the providers you configured** (they used to fall back to the full default list).

### 5. Sign confirmations stay inside the wallet iframe

`confirmationStrategy` defaults to `'modal'`: confirmations render inside the wallet iframe, immune to browser popup blockers — signs triggered right after `login()` are not in a user gesture and a separate popup window was blocked intermittently. `'default'` now means exactly that SDK default; only an explicit `'popup'` opts into a separate window.

### 6. Deprecated and removed

| Option                         | Status                                                                                                                                 |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `withModal`                    | **Deprecated, no effect.** The SDK's modal opens whenever `loginProvider` offers a choice. Still accepted so existing configs compile. |
| `auth0ClientId`, `auth0Domain` | **Never read by the SDK** and not part of its config type — remove them from your configs. `auth0TorusConfigKey` is still honoured.    |
| `mfaLevel`                     | Deprecated since 2.2.0 (no MFA level in the underlying wallet).                                                                        |

## ⬆️ Upgrade from 2.2.0

{% stepper %}
{% step %}

### Install

```bash
npm install @startbahn/startrail-sdk-js@2.3.0   # or: yarn add / pnpm add
```

No breaking API changes: `login()` still returns `EOA[] | false`, all method signatures are unchanged.
{% endstep %}

{% step %}

### Clean the config

Drop `withModal`, `auth0ClientId` and `auth0Domain` if you pass them. Keep `auth0TorusConfigKey` only if Startbahn gave you one.
{% endstep %}

{% step %}

### Brand the modal (optional)

Open the [playground's brand studio](https://sdk.startrail.io/#brand), pick a preset or your colours, press **Preview**, then copy the generated `customUi` block into your constructor config.
{% endstep %}

{% step %}

### Honour account switches (optional)

On your login page, if the user explicitly picks a provider or wallet that differs from their previous sign-in, call `login({ ...choice, forceLogin: true })`. Keep the plain `login()` for the same provider, page reloads and the passwordless "complete" step, so the restored session is reused.
{% endstep %}
{% endstepper %}

## 🛠️ Internal

* Wallet stack moved to `@toruslabs/torus-embed` 7 (ws-embed 6 / `@web3auth/auth` 11) — see the note below.
* Toolchain: pnpm 12, refreshed dependencies; the README and this SDK's [`llms.txt`](https://www.npmjs.com/package/@startbahn/startrail-sdk-js) were rewritten to cover every option above.

{% content-ref url="/pages/m3qEo4N0sRD0jyzWutu8" %}
[Interface](/startrail-sdk-js/login-providers/startrail-powered-by-web3auth-torus.md)
{% endcontent-ref %}

{% content-ref url="/pages/PCMRbvcYYEapFskUvxvI" %}
[Wallet Methods](/startrail-sdk-js/wallet-methods.md)
{% endcontent-ref %}

{% content-ref url="/pages/frSJRNrPXDXGs0OwRaIS" %}
[URL per environment](/readme/url-per-environment.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.startrail.io/startrail-sdk-js/change-logs/latest.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
