> ## Documentation Index
> Fetch the complete documentation index at: https://spiceflow-docs.spicenet.io/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Refactor & Migration

> Adopt shared inventory and action controllers, and validate SDK upgrades

<Warning>
  Version scope, checked September 14, 2026: the consistency refactor is merged in the SDK repository,
  and published as `4.9.6-consistency.16` under the `consistency` tag. npm `latest` is still `4.9.5`.
  The controller examples in these docs target that exact prerelease. A merge does not publish a
  package or upgrade a client. Check current registry metadata before selecting a version.
</Warning>

## What changed

Account, Deposit, Withdraw, Supply, Pay and Lock now acquire balances and evaluate asset eligibility
through shared SDK inventory and policy. Supply, Pay, Lock and custom action presentations use
`useSpiceSupply` for planning and execution. `useSpiceLock` and the existing `useSpiceAction` are thin
adapters. Deposit and rollup Withdraw retain their distinct execution semantics; Account wallet
withdrawal uses the shared fee-only action path.

| Concern        | SDK owns                                                                          | Client owns                                                     |
| -------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| Assets         | Acquisition, refresh, position identity, visibility and eligibility               | Supported chains and token policy; additional token metadata    |
| Actions        | Routing, conversion, Max, fees, accepted batches, funding, signing and settlement | Destination contract calls and intended beneficiary             |
| Presentation   | Controlled state, readiness, errors and recovery actions                          | Existing modal, layout, inputs, branding and navigation         |
| Completion     | Settlement and retryable application-finalization state                           | Awaited, repeat-safe application updates after settlement       |
| Infrastructure | Using the configured RPCs and venues                                              | RPC endpoints for every exercised chain and venue configuration |

The benefit is one implementation of shared behavior across stock and custom views. A correction to
fees or inventory no longer needs a separate client implementation. The tradeoff is a larger set of
consumers to validate when shared behavior changes, plus migration work for integrations that used
to execute destination transactions themselves. This refactor is not a promise of a smaller bundle
or faster execution on every route.

## Integration checklist

1. Record the current app pin and resolved SDK/core versions. Compare them with the intended
   published package, its exports and declarations. A higher version on another prerelease branch
   does not prove it contains this refactor.
2. Keep the existing wallet provider stack and import `styles.css` once. Configure SDK
   `rpcOverrides` for every source, conversion and destination chain the app can exercise. wagmi
   transports alone do not configure the SDK. See [Configuration](/sdk/configuration#client-rpcs).
3. Keep the host UI. Use the stock component when it fits, or the
   [controlled action hook](/guides/custom-ui). Replace host-owned balance timers, fee estimation,
   funding orchestration and execution state with the shared SDK owners.
4. Audit the callbacks and contract ownership described below. Preserve approved asset restrictions,
   beneficiary semantics and application behavior; do not remove a restriction merely to pass a test.
5. Validate the exact candidate in each affected app, then update its exact pin and lockfile through
   the app's release process. Document pending migrations instead of claiming universal compatibility.

## Callback and ownership migration

| Previous integration                                                 | Refactored integration                                                             |
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `useSupplyAssets` + `useSupplyQuote` + host fee/batch/executor state | `useSpiceSupply`; render the returned assets, fees, readiness, status and recovery |
| `onPayExecute`                                                       | Protocol calls in `buildActionCalls`; application updates in awaited `onSuccess`   |
| `onLockExecute`                                                      | Destination calls in `lockBatches`; application updates in awaited `onLockSuccess` |
| `actionExecution.wallet: "external"`                                 | Requires an explicitly supported beneficiary-capable action before adoption        |
| Host retry after a source transfer                                   | SDK journal reconciliation and controller recovery                                 |

The three execution overrides above now produce integration errors. Keeping their export/type names
does not make them functional compatibility paths. This change does not remove every
`onDepositExecute` or `onWithdrawExecute` contract; inspect those distinct flows before changing them.

External mode selects the external source wallet, funds the embedded wallet, and then uses the same
downstream action executor as embedded mode. **The source wallet is not necessarily the destination
contract caller.** Use `BuildActionCallsContext.executorAddress` and `beneficiary` deliberately.
For example, a contract offering `stakeFor(beneficiary, amount)` can preserve a user's position owner;
a contract assigning ownership only to `msg.sender` needs a separate integration decision. Verify
NFT receiver callbacks as well as token balances. Do not delete an override and silently change who
owns the resulting position.

### Same-chain destination assets do not automatically bypass the SDK

<Note>
  The behavior below describes published consistency candidate `.16`. The unpublished
  [SDK PR #104](https://github.com/pepper-research/spiceflow-ui/pull/104) restores automatic direct execution inside the shared
  controller for External mode and a matching wallet token/chain. It uses the same host action
  definition, needs no embedded signer, and charges no Spicenet fee. Network gas is paid in the
  external wallet, with a gas reserve for native Max. Calls are sequential, with receipt tracking
  and recovery of unfinished calls; success callbacks are still awaited. Other tokens/chains,
  Embedded mode, fee-only/proceeds-funded actions and zero-amount payments retain their existing
  behavior. Existing funded actions are reconciled on their original path. Do not assume `.16`
  includes this restoration; adopt and verify an exact new candidate after publication.
</Note>

In the refactored Supply/Pay/Lock controller, selecting the destination token on its own chain
avoids conversion, but does not select external-wallet destination execution. For example, external
USDC on Base funding a Base USDC action still takes the external-to-embedded funding stage and
shared executor. The controller disables its former direct-external action path.

**This was previously SDK-owned behavior.** Before the refactor, Supply and Pay detected an
external source that matched the destination token and chain, then called the SDK's
`executeDirectExternalAction`. Lock had the equivalent `executeDirectExternalLock` branch.
The host supplied protocol calls; the SDK selected the direct path and submitted those calls
with the external signer, without first funding the embedded wallet. See the
[pre-refactor Supply executor](https://github.com/pepper-research/spiceflow-ui/blob/c1aa6fec9368d0312901cecae49ccb120bbf7892/src/components/SpiceSupply/lib/executors.ts#L490)
and [Lock executor](https://github.com/pepper-research/spiceflow-ui/blob/c1aa6fec9368d0312901cecae49ccb120bbf7892/src/components/SpiceLock/lib/executors.ts#L927).

Implementing a separate native action in the host is therefore a workaround for a removed SDK
capability, not an unchanged client responsibility. If a host already has such an action,
audit its routing before claiming it was preserved. The External mode toggle does not provide
that bypass in `.16`. The unpublished restoration above keeps this choice in the shared SDK
controller, so clients continue to supply one action definition.

## Amounts, fees and Max

| `amountDirection` | Meaning                                                             | Submit action                          |
| ----------------- | ------------------------------------------------------------------- | -------------------------------------- |
| `input`           | Entered principal; fees added to the required spend                 | `submitSupply()`                       |
| `input-inclusive` | Entered total budget; fees deducted before building the action      | `submitSupply()`; stock Lock uses this |
| `output`          | Fixed destination amount; SDK derives source funding including fees | `submitPay()`                          |

Preserve raw integer amounts and the decimals of the token each amount belongs to. Use the
controller's accepted preview and `canExecute`; do not recompute readiness from a nonempty input.
Use `resolveMaxAmount(asset)` for Max. A `null` result means Max is unavailable, not permission to
use the full balance.

An accepted plan binds the source identity/cap, beneficiary, destination minimum, route/calls, fee
bounds, expiry and provider/policy configuration. A stale or changed plan must be reviewed again.
Some changing-fee failures are intentional: execution must fit the accepted bounds. The refactor
does not guarantee zero quote retries.

Deposit gas and rollup withdrawal rules remain distinct from these action fees. Account wallet
withdrawal uses a fee-only Supply action; do not apply a blanket "withdrawals never need a quote"
rule to that path.

## Inventory and refresh

Use `useSpiceInventory` for custom holdings and the action controller's asset list for action
selection. `assetPolicy` is shared internal logic, not a new provider prop. Clients configure
`allowedTokens`, chain scope, RPCs and venues.

* Keep owner, wallet/rollup location, chain and token/receipt identity. Use `assetPositionId(asset)`
  for row keys and selection matching; equal symbols do not mean equal positions.
* Complete raw inventory retains dust and unpriced positions. Default visibility is separate:
  discoveries outside configured tokens need a finite positive price and at least \$0.01. Account
  also hides configured priced dust; configured tokens retain the missing-price exception.
* Preserve displayed data during background refresh. Confirmed zero replaces the old balance;
  stale/failed balance reads cannot authorize spending. A price-only failure is different from a
  balance failure. Do not turn `refreshing` into an empty list or an initial loading screen.
* Extra assets can supply metadata and otherwise-undiscovered positions. They cannot overwrite an
  SDK-acquired balance, including zero. Apply the same client policy across views.

Inventory is scoped by owner, API, RPC and fetcher identity. The underlying quote/execution
transports still have global state; simultaneous differently configured providers are not supported
by this isolation alone.

## Completion and recovery

Render the controller's status until required credit/delivery and destination receipts complete.
Await application updates in the success callback and make them safe to retry. A failed application
update can be retried with `retryFinalization()` without resubmitting the transaction.

Use the existing pending-action journal for uncertain results. Preserve the original action inputs,
beneficiary and configuration when resuming. Keep lock duration and other host-owned terms available
after reload; automatic restoration of those host inputs is not guaranteed. If
`fundingQuoteReview` is present, show both destination minima and obtain explicit review before
calling its `accept()` action. That review cannot widen the source cap or authorize another transfer.

## Updating the SDK from now on

1. Start from fresh SDK main and change the shared owner. Audit affected stock and controlled
   consumers instead of applying equivalent patches in each widget or app.
2. Run focused regressions and the SDK's required `bun test`, `bun run typecheck`, and
   `bun run build`. Validate affected app builds and rendered refresh/error behavior. Exercise
   authorized financial cases across relevant wallet modes, routes and recovery states.
3. Record the exact tested source, package artifact, app/configuration and coverage. A prior
   candidate's financial results do not automatically apply to a later candidate or merged main.
4. If core changes are required, release core before updating the UI dependency. An inventory or
   controller-only fix does not inherently require a core release. Coordinate API changes before
   their consumers rely on them.
5. Publish an approved prerelease with an explicit non-`latest` tag, verify its registry artifact,
   and pin that exact version in the test app. Verify the deployed bundle as well as the lockfile.
6. Treat stable promotion, individual client adoption and docs targeting as separate release steps.
   Choose stable versioning based on behavioral breaks as well as type changes; the candidate's
   patch-like numbering is not evidence of a drop-in patch upgrade.
   Match the docs' SDK drift check to the documented package when promoting a version.

The refactor itself adds no automatic npm publishing. It also does not authorize publishing,
deployment or wallet transactions on behalf of an integrator.

## Evidence and limits

The `.16` candidate passed 978 SDK tests, typecheck/build, rendered filtering tests and authenticated
Account refresh checks. The preceding `.15` candidate completed 28 financial cases and an additional
outage/reload withdrawal test in a test integration. That full financial matrix was not rerun on
`.16`. Frozen builds of five consumers passed on `.15`, while existing full-typecheck debt remained
in two. These results support the tested cases; they do not establish settlement or caller
compatibility for every client. See the SDK repository's `CONSISTENCY-MIGRATION.md` for the release
evidence and open limits.
