Skip to content

Migration plan — adopt @swarm-ai-labs/turnkey-extensions in ai-exchange-web

Goal: make ai-exchange-web depend on only @swarm-ai-labs/turnkey-extensions for crypto — delete the duplicated integration code and drop every chain SDK from the app's package.json, importing it all from the package instead.

This is a mechanical, domain-by-domain migration. Each step compiles and is independently verifiable; nothing is behind a feature flag, so revert = git revert of that step's commit.


0. Decide the dependency boundary (do this first)

"Remove all packages except ours" forces one design decision: who owns the chain SDKs at install time. The package currently declares them as optional peerDependencies (the app would still have to install them). To let the app drop them, reclassify them in the package:

Package depNowChange toWhy
@solana/web3.js, @near-js/*, near-api-js, tronweb, @ton/*, @mysten/sui, @nktkas/hyperliquid, @polymarket/clob-client-v2, @turnkey/solana, @turnkey/viemoptional peerdependenciesApp no longer imports them directly → must install transitively via us
@scure/*, @noble/curves, borshdependencydependency (unchanged)small, already bundled by us
viemdependencypeerDependencyApp uses viem widely; both sides must share one instance (types + instanceof)
react, react-dom, @turnkey/react-wallet-kitoptional peerpeer (unchanged)App uses these directly (TurnkeyProvider, useTurnkey)
@aurora-is-near/intents-swap-widget*optional peerpeer (unchanged)Widget JSX stays in the app

Trade-off: bundling the chain SDKs as dependencies means the app can't pin their versions independently and may double-install if it ever re-adds one. That is the explicit cost of "one package." viem is the one exception kept as a peer because a duplicated viem instance breaks type identity.

After this change the app's removable set (step 6) installs transitively through us.


Local dev (mirrors the existing pieui workflow in the app's scripts):

sh
# in turnkey-extensions
bun run build && bun link
# in ai-exchange-web
bun link @swarm-ai-labs/turnkey-extensions && bun install

For CI/prod, pin the tarball attached to the GitHub Release — this package is not published to any registry:

json
"@swarm-ai-labs/turnkey-extensions": "https://github.com/Swarm-AI-Labs/turnkey-extensions/releases/download/v0.4.0/swarm.ing-turnkey-extensions-0.4.0.tgz"

Do not pin git+ssh://…#vX.Y.Z. dist/ is no longer committed, so a git pin makes the app build the package itself — a DTS rollup peaking near 4.4 GB, which OOMs in a memory-capped CI container.


2. Configure at app boot

The package reads endpoints from configure() instead of NEXT_PUBLIC_*. Add one module imported once (e.g. in instrumentation-client.ts or a root layout):

ts
import { configure } from "@swarm-ai-labs/turnkey-extensions/config";

configure({
  rpc: {
    sol: process.env.NEXT_PUBLIC_SOL_RPC_URL,
    evm: {
      1: process.env.NEXT_PUBLIC_ETH_RPC_URL,
      11155111: process.env.NEXT_PUBLIC_SEPOLIA_RPC_URL,
    },
  },
  apiBases: {
    inventory: "/api/inventory", // keep the existing Next proxy route
    indexer: "", // "" ⇒ same-origin /api/price-graph
  },
});

This preserves today's behaviour: balances/senders use the same RPCs, and the inventory/indexer browser clients keep hitting the app's existing proxy routes.


3. Rewrite imports (domain by domain)

Replace each app module with the package subpath. Delete the app source after its consumers compile against the package.

App file(s) to deleteReplace imports with
lib/format.ts@swarm-ai-labs/turnkey-extensions/format
lib/validateAddress.ts@swarm-ai-labs/turnkey-extensions/validate
lib/catalog/cryptoNetworks.ts@swarm-ai-labs/turnkey-extensions/catalog
lib/near/implicitAccount.ts@swarm-ai-labs/turnkey-extensions/signers
components/turnkeyEip1193.ts…/signers (createTurnkeyEip1193Provider)
components/turnkeySolanaProvider.ts…/signers (createTurnkeySolanaProvider)
piecomponents/TurnkeyWalletCard/utils/prepareTurnkeyEthSend.ts, prepareTurnkeyEvmTransaction.ts…/signers (prepareTurnkey*)
piecomponents/WithdrawCard/senders/*@swarm-ai-labs/turnkey-extensions/senders
lib/holdings/balances.ts@swarm-ai-labs/turnkey-extensions/balances
lib/hyperliquid/*@swarm-ai-labs/turnkey-extensions/hyperliquid
lib/inventory/{types,client}.ts@swarm-ai-labs/turnkey-extensions/inventory
lib/indexer/{types,priceGraph,searchTickers}.ts@swarm-ai-labs/turnkey-extensions/indexer
lib/swap/quoteRefresh.ts@swarm-ai-labs/turnkey-extensions/swap
…/useTurnkeyAccounts.ts, useTurnkeyViemAccount.ts, useHyperliquid{Exchange,Address}.ts, chainFromAddressFormat@swarm-ai-labs/turnkey-extensions/react
lib/indexer/useTickerSearch.ts, useTokenPriceGraph.ts@swarm-ai-labs/turnkey-extensions/react

Keep in the app (not crypto-integration code):

  • components/TurnkeyProvider.tsx, turnkey/*Gate.tsx — kit setup/auth UI.
  • lib/inventory/server.ts + app/api/inventory/*, app/api/price-graph/* — these are the proxy backends our browser clients call; leave them.
  • components/AuroraWidget*.tsx — widget JSX. Swap its provider wiring to buildAuroraTurnkeyProviders from …/swap.
  • lib/inventory/types.ts re-exports: if app code imports inventory types from the old path, re-point to …/inventory.

Tip: do it as a codemod per row — find/replace the import specifier, run bun run typecheck, commit. One green commit per domain.


4. Map app-local types

The app passed WalletAccount, DepositChainId, ChainKey, HandleSendTransactionParams around. The package owns equivalents:

  • WalletAccount, HandleSendTransactionParams — re-exported from the kit by us; import from @swarm-ai-labs/turnkey-extensions/types / …/senders.
  • DepositChainId = string (unchanged); ChainKey, EvmCaip2 from …/types.
  • ChainAccount (the hook result) — from …/react.

Delete the app's duplicate piecomponents/DepositCard/types chain aliases that now live in the package; keep app-specific UI types.


5. Wire the swap bridge

In AuroraWidgetTurnkeyBridge.tsx, replace the hand-rolled provider creation with:

ts
import { buildAuroraTurnkeyProviders } from "@swarm-ai-labs/turnkey-extensions/swap";

const { evm, sol } = buildAuroraTurnkeyProviders({
  client: httpClient,
  organizationId,
  evmAddress,
  solanaAddress,
  initialChainId,
});
// pass evm/sol into the widget's `providers` config

6. Remove packages from ai-exchange-web/package.json

Delete these dependencies (now provided transitively by the package):

@mysten/sui
@near-js/crypto  @near-js/providers  @near-js/transactions  near-api-js
@nktkas/hyperliquid
@polymarket/clob-client-v2
@scure/btc-signer
@solana/web3.js
@ton/core  @ton/crypto  @ton/ton
@turnkey/solana  @turnkey/viem
borsh

Keep (app uses them directly, or they're not crypto-signing): @swarm-ai-labs/turnkey-extensions (new), viem, @turnkey/react-wallet-kit, @aurora-is-near/intents-swap-widget*, @tonconnect/ui-react, @web3icons/react, @ledgerhq/crypto-icons, algosdk (logos + swap-symbol generation only — not a Turnkey integration), plus all framework/UI deps.

Then bun install and confirm the lockfile resolves a single viem.


7. Verify & roll out

Per-domain commits, then a final pass:

  1. bun run typecheck — clean after each domain.
  2. bun run build (opennextjs-cloudflare build) — bundles resolve.
  3. bun run build-storybook — component stories still render.
  4. Manual smoke per chain (testnet): receive-address derivation, a small withdrawal (EVM + one ed25519 chain + one secp256k1 chain), a swap quote + sign, a Hyperliquid market read, the prediction balance.
  5. Watch the bundle: confirm tree-shaking kept unused chains out (no TON code on an EVM-only route).

Rollback: each step is an isolated commit; git revert restores the app's local module and its import sites. Because nothing is feature-flagged, a revert is total and immediate.


8. Risks & notes

  • Single viem instance — the one hard requirement. Keep viem a peer of the package; if the app and package resolve different majors, instanceof/type checks (e.g. isAddress, account objects) break subtly. Verify with bun pm ls viem → exactly one.
  • @turnkey/react-wallet-kit stays app-owned — the package only types against it; the app provides the actual provider + useTurnkey().
  • Proxy routes stay — the inventory/indexer browser clients are CORS-safe only via the app's /api/* routes; configure({ apiBases }) points at them.
  • algosdk is not ours — it's used for network logos and the generated swap symbol list, not Turnkey signing. It stays in the app.
  • Polymarket — collateral/CLOB are forward-looking; the app's prediction UI is still stubbed, so this is a drop-in for when its order backend lands.

Released under the MIT License.