Repository navigation
perf(sdk): load on-chain program clients and eth contracts lazily - #14661
Merged
Merged
Conversation
createSdkWithServices now builds lazy facades for ClaimableTokensClient, RewardManagerClient, PaymentRouterClient and EthereumService. Each loads its implementation with a dynamic import on first use, so creating the SDK no longer pulls in @audius/spl, @coral-xyz/anchor, buffer-layout or the @audius/eth ABIs. Interfaces are unchanged; RewardManagerClient's lookupTable and each contract's address stay synchronous. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lets bundlers drop SDK modules whose exports are unused, so the barrel's re-exports of the program clients no longer keep anchor and @audius/spl in the consumer's entry chunk. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 1e5e238 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
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 |
|
Dependency limit exceeded — report not shown. This pull request scan exceeded the 10,000-dependency limit applied to this scan, so the results are incomplete and may be inaccurate. To avoid reporting false positives, Socket has not posted a report. Upgrade your plan to raise the dependency limit and get complete reports, or view the partial scan in the dashboard. Socket is always free for open source. If this is a non-commercial open source project, contact us to request a free Team account. |
This was referenced Oct 12, 2026
dylanjeffers
added a commit
that referenced
this pull request
Oct 13, 2026
Part of moving crypto code off the app's startup path. This is the common/web/mobile half; #14661 (SDK), #14662 (hedgehog), #14663 (crypto shim) and #14660 (boot work) are the others. ## Problem Crypto libraries were statically reachable from the app root through shared barrels, not through sagas: - `local-storage.ts` → `@audius/common/services` → `AudiusBackend.ts`/`solana.ts` pulled in web3.js, spl-token, viem and Jupiter. - `reducers.ts` → the `buy-usdc`/`purchase-content`/`ui` index files, which re-export their sagas. - `AppProviders` → `WagmiProvider`. - `registerNiceModals.ts` statically registered the purchase, withdraw, wallet and rewards modals. ## Changes 1. **Solana, Jupiter and viem load on demand (common).** - AudiusBackend's Solana methods moved to `solanaBackend.ts`, which loads on first call. `findAssociatedTokenAddress` is now async; its 3 callers are updated. - `audius-backend/solana` is no longer re-exported from the services barrel. - WalletClient, the hedgehog Solana wallet service and the wallet/Jupiter/coin query hooks import web3.js, spl-token, tweetnacl, viem and Jupiter inside the async code that uses them. - `isValidSolAddress` now checks for a 32-byte base58 key instead of constructing a `PublicKey`. 2. **Crypto sagas start on demand (web + mobile).** - A new `lazySagas` helper waits for a feature's first trigger action, dynamic-imports its sagas, spawns them, and replays the buffered actions. - Replays go to the saga middleware's channel, so reducers don't run twice. - Converted: - common: purchase-content, withdraw-USDC, Stripe onramp, buy-USDC (triggered by `onrampOpened`/`startRecoveryIfNecessary`) - mobile: token dashboard and wallet-connect 3. **No app-wide `WagmiProvider` (web).** - `useConnectExternalWallets` was the only wagmi-context consumer; it now passes AppKit's config explicitly. - `ReownAppKitModal` calls `reconnect()` when it loads. - `auth.ts` and sign-out import `@wagmi/core` dynamically. 4. **14 purchase/wallet/rewards modals registered lazily (web)**, using the existing `registerLazyModal`. - `CoinflowPurchaseProtection` now mounts when the browser is idle, in its own Suspense boundary. It used to share a boundary with `<Outlet />`, so page content waited on the Coinflow chunk. ## Impact (web entry chunk) - **This PR alone:** 5,627 → 5,285 KB minified, 1,526 → 1,430 KB gzipped. wagmi, `@wagmi/core` and `@jup-ag/api` are out, and so is most crypto feature code. - **With #14660–#14663 merged together:** 5,627 → 4,213 KB minified (−25%), 1,526 → 1,126 KB gzipped (−26%). Crypto libraries in the entry drop from 2,447 to 1,368 KB pre-minify. What's left is viem/noble (needed for signing) and `@solana/web3.js` via the SDK's `SolanaRelay`/`SolanaClient`; that's the next step. ## Test plan - [x] Typecheck: common, web, mobile, sdk (also checked combined with the other 4 branches) - [x] common vitest (the 7 `upload/selectors.test.ts` failures also happen on main), web vitest, mobile jest 34/34 - [x] New `lazySagas` tests, including no double reducer run on replay - [x] iOS production Metro bundle builds (dynamic imports resolve) - [x] Prod build against prod API, signed out: `/trending` and `/signin` render with no app errors; a lazy modal opens; a dispatched trigger loads its sagas and handles the replayed action - [ ] Buy a premium track with USDC balance, with card (Stripe and Coinflow), and as a guest - [ ] Withdraw USDC to a Solana address and through Coinflow - [ ] External wallet sign-in; reload while connected (reconnects?); add/remove a linked wallet; sign out (disconnects?) - [ ] Fan club connect-wallet and the launchpad; send tokens; buy/sell swap; payout wallet modal - [ ] Claim rewards and vested coins - [ ] Mobile: token dashboard wallets, Phantom connect and sign 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
dylanjeffers
added a commit
that referenced
this pull request
Oct 13, 2026
## Impact | | min | gzip | |---|---|---| | Web entry, base | 2,479,377 | 716,551 | | Web entry, this PR | **2,203,062** | **637,628** | - Crypto in the web entry drops from 1,367 to 682 KB pre-minify. `@solana/web3.js`, bn.js, buffer-layout, borsh, superstruct, rpc-websockets and jayson are gone; `@noble/curves` drops from 193 to 81 KB because web3.js's ed25519 copy went with it. - What's left is viem/noble/scure, which signing and login need. - Mobile bundle size is unchanged (Metro has no splitting), but web3.js is no longer evaluated when the SDK is created. ## Changes - **`LazySolanaClient`** follows the `Lazy*` facade pattern from #14661. Program clients build their PublicKey-based default config only when they load. - **New `solanaClient.getConnection()`.** Every `solanaClient.connection` read in common, web, the SDK program clients, TracksApi/AlbumsApi and the lookup-table script now uses it. Sagas use `yield* call([client, client.getConnection])`. - **`SolanaRelay` and `PublicKeySchema`** import web3.js inside their async paths. - **`SolanaAddressSchema`** validates 32 base58 bytes with `@scure/base`. A test checks it accepts and rejects exactly the same as `new PublicKey()` across about 2,060 inputs: valid keys, off-curve PDAs, 31/33-byte keys, leading zeros, invalid characters, empty strings and random strings. - **`createSdk` and `DeveloperAppsApi`** no longer import values through the services barrel. - **`PaymentRouterClient/index.ts`** uses named re-exports so Metro keeps them lazy. - **`SolanaWallet`** uses `@scure/base` instead of bs58. ##⚠️ Breaking SDK changes (changeset: **major**) - On the default client, `sdk.services.solanaClient.connection` throws a clear error until the client has loaded, meaning after any awaited call such as `getConnection()`. Use `await solanaClient.getConnection()`. - `PublicKeySchema` and `MintSchema` only parse asynchronously (`parseAsync`/`safeParseAsync`). The SDK's own methods already did. - The default `rewardManagerClient.lookupTable` is available only after that client loads, unless it's passed in config. - Default clients aren't `instanceof` their classes. - A `solanaClient` passed in through `services` is used as-is. ## Test plan - [x] tsc: sdk, common, web, mobile. sdk vitest (238), web vitest (188), mobile jest (49). New tests check that creating the SDK doesn't load web3.js, plus the `LazySolanaClient` and lookup-table behavior. - [x] Prod build smoke test: `/trending` and `/signin` work - [ ] Buy USDC (Coinflow, Stripe); buy a track and an album with USDC; withdraw USDC; payout wallet modal - [ ] Jupiter/artist-coin swaps from the internal and an external wallet; claim rewards; wallet and coin balances; mint recovery - [ ] Mobile: Coinflow onramp and withdraw 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of moving crypto code off the app's startup path.
Problem
createSdkWithServiceseagerly constructedClaimableTokensClient,RewardManagerClient,PaymentRouterClientandEthereumService. That statically pulled@audius/spl→@coral-xyz/anchor, buffer-layout, borsh and all 9@audius/ethABIs into the web entry chunk for every visitor, including signed-out ones.Change
LazyClaimableTokensClient,LazyRewardManagerClient,LazyPaymentRouterClientandLazyEthereumServiceimplement each client's public interface. The compiler enforces this throughimplements PublicInterface<Client>.RewardManagerClient.lookupTableand each eth contract'saddresscome from config, andwatchEventreturns its unwatch function synchronously.sideEffectsfield inpackages/sdk/package.json. It lists onlysdkBrowserDist.tsanddist/sdk.min.js. This was required: without it, theexport * from './services're-exports kept anchor in the bundle even with the stand-ins.instanceoftheir client classes (nothing in the monorepo checks this);ethereum.<contract>.abithrows until that contract has been used (nothing in the monorepo reads it);Apps that pass their own clients via
config.servicesare unaffected.SolanaClientand the viem eth clients are unchanged.Impact (web entry chunk)
5,762 → 5,489 kB minified (−273 kB), 1,567 → 1,510 kB gzip. anchor,
@audius/spl, buffer-layout,@coral-xyz/borshand the@audius/ethABIs are no longer in the entry chunk.Follow-ups
solanaClient.connectionis read synchronously in about 30 places across common and web, which keeps@solana/web3.jstied to the SDK. Next step: add an asyncgetConnection(), then makeSolanaClientlazy.SolanaAddress/PublicKeySchemavalidation usesnew PublicKey(). A base58 32-byte check would do the same job without it.Test plan
npm run buildethereumservice🤖 Generated with Claude Code