Skip to content

fix(sdk): preserve client accessors through extend() - #488

Open
chybisov wants to merge 2 commits into
mainfrom
fix/extend-preserves-accessors
Open

chybisov wants to merge 2 commits into
mainfrom
fix/extend-preserves-accessors

Conversation

@chybisov

Copy link
Copy Markdown
Member

Summary

client.extend() built the extended object with a shallow spread:

const extended = { ...base, ...extensions }

config and providers are accessor properties. A spread evaluates them and writes their current values as plain data properties, so the extension stops tracking the client. Because setProviders reassigns _providers rather than mutating it, an extended client is pinned to whatever the provider list was at extend time.

Verified against the published @lifi/sdk@4.8.0 dist:

base.providers      : [{"type":"EVM"}]
extended.providers  : []
extended.getProvider: undefined
providers is getter on extended? false

So a host that extends before registering its wallet providers gets a client whose getProvider always returns undefined — execution can never run. Chain and RPC storage were unaffected, because setChains/getChains/getRpcUrls close over _storage rather than going through an accessor.

Changes

  • createClient.ts: copy property descriptors instead of spreading, so the base and every extension share one live view of config and providers.
  • createClient.unit.spec.ts: two failing-first tests — providers registered after the extension is created, and base/extension sharing one list.
  • getRoutes.unit.spec.ts: lock request-level attribution precedence. Once a host and a widget share a client, the client carries the host's integrator, referrer and fee, so every caller that needs its own must send them with the request. These pin that contract.

Testing steps

pnpm test — 249 tests pass. biome, tsc, circular-deps and knip all clean via the pre-commit gate.

Why did I implement it this way?

Descriptor copying is the minimal change that keeps extend's existing shape and semantics. The alternative — making setProviders mutate the array in place — would fix the symptom but leave config still copied by value.

Context

Found while investigating JUM-1527 (deduplicating the SDK bootstrap between the Jumper app and the widget). The extend-based approach was the obvious way to share a client between a host and a widget, and this is why it did not work.

extend() built the extended object with a shallow spread. A spread evaluates
getters and writes their current values as plain data properties, so the
config and providers accessors stopped tracking the client. Because
setProviders reassigns the backing array rather than mutating it, an extended
client was pinned to whatever the provider list was at extend time — a host
that extended before registering its wallet providers got a client whose
getProvider always returned undefined, and execution could never run.

Copy property descriptors instead, so the base and every extension share one
live view of config and providers.
A host application and a widget can share one SDK client, and the client then
carries the host's integrator, referrer and fee. Any caller that needs its own
has to send them with the request, so pin the precedence: options on the
request win over client config, and the client is only a fallback.
@chybisov chybisov added the release-preview Publish a 0.0.0-preview-<sha> build of this PR to npm label Sep 22, 2026
@changeset-bot

changeset-bot Bot commented Sep 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 479fb19

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 7 packages
Name Type
@lifi/sdk Patch
@lifi/sdk-provider-bitcoin Patch
@lifi/sdk-provider-ethereum Patch
@lifi/sdk-provider-solana Patch
@lifi/sdk-provider-stellar Patch
@lifi/sdk-provider-sui Patch
@lifi/sdk-provider-tron Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

📦 Preview published under the preview dist-tag.

Install the exact version(s) — @preview moves with the newest preview across PRs:

npm i @lifi/sdk-provider-bitcoin@0.0.0-preview-479fb196
npm i @lifi/sdk-provider-ethereum@0.0.0-preview-479fb196
npm i @lifi/sdk-provider-solana@0.0.0-preview-479fb196
npm i @lifi/sdk-provider-stellar@0.0.0-preview-479fb196
npm i @lifi/sdk-provider-sui@0.0.0-preview-479fb196
npm i @lifi/sdk-provider-tron@0.0.0-preview-479fb196
npm i @lifi/sdk@0.0.0-preview-479fb196

@github-actions github-actions Bot removed the release-preview Publish a 0.0.0-preview-<sha> build of this PR to npm label Sep 22, 2026
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.

1 participant