Skip to content

SDK + CLI: getPositions() to fetch all wallet lend positions in one call #454

@its-everdred

Description

@its-everdred

The SDK's wallet.lend.getPosition({ marketId }) requires a specific marketId and throws MarketIdRequiredError otherwise (packages/sdk/src/actions/lend/namespaces/WalletLendNamespace.ts:52). There is no API to fetch all of a wallet's open positions across configured markets/providers. The same gap exists at the read-only actions.lend.* namespace.

Why

Every consumer that wants to render "the wallet's portfolio" has to:

  1. Walk the configured market allowlist.
  2. Call getPosition({ marketId }) once per market in parallel.
  3. Filter zero-balance results client-side.

That's M × P RPC calls (markets × providers) per page-load — small for the demo allowlist, ugly for any larger config. It also makes batched optimisations (multicall, cf. #452) impossible because each SDK call is single-market.

Existing demo cost

packages/demo/frontend/src/hooks/useLendProvider.ts:106-139 already fans out manually:

```ts
const positionPromises = marketInfoList.map(async (market) =>
operations.getPosition({ ... })
)
const positionResults = await Promise.all(positionPromises)
```

Two calls' worth of activity log entries (getMarket + getPosition) hide N RPCs. Replacing this with a single SDK call simplifies the hook AND makes activity-log accounting honest.

The post-trade refresh in useLendPosition.ts:76-97, 129-150 already invalidates one position cache key (['position', address, chainId]); that pattern stays as-is — single-market getPosition is still the right tool when you know which market just changed.

Proposed API

```ts
// Read namespace (no wallet)
actions.lend.getPositions(walletAddress: Address, params?: GetPositionsParams): Promise<LendMarketPosition[]>

// Wallet namespace (uses wallet.address implicitly)
wallet.lend.getPositions(params?: GetPositionsParams): Promise<LendMarketPosition[]>

interface GetPositionsParams {
/** Filter to one chain (mirrors GetLendMarketsParams). /
chainId?: SupportedChainId
/
* Filter to one provider ("morpho" | "aave" etc.). /
provider?: LendProviderName
/
* Drop zero-balance positions before returning. Default: false (return every market for parity with getMarkets). */
nonZeroOnly?: boolean
}
```

Returns the same LendMarketPosition shape the existing getPosition already uses — no new type.

Implementation notes

  • Backed by multicall (Batch Wallet.getBalance asset fetches via Multicall3 #452 follow-up): every provider runs getPosition against viem Multicall3 — one eth_call per chain instead of one per market. The savings stack with Batch Wallet.getBalance asset fetches via Multicall3 #452's balance optimization.
  • Per-call failure semantics: allowFailure: true so a single bad market RPC doesn't poison the batch. Failures map to a typed ActionsError per market (or are dropped, depending on nonZeroOnly).
  • Provider aggregation: BaseLendNamespace.getPositions runs Promise.all over configured providers; each provider's _getPositions walks its own allowlist. Symmetric to the existing _getMarkets pattern.

CLI follow-up

Once the SDK ships wallet.lend.getPositions, add actions wallet lend positions (plural — the existing singular position --market <name> stays).

```
actions wallet lend positions [--chain | --chain-id ] [--non-zero-only]
```

Output: LendMarketPosition[] verbatim, bigints stringified.

Until the SDK ships this, the CLI could implement the fan-out itself against collectMarkets(config), but doing so client-side defeats the multicall optimization. Skip the CLI work until the SDK lands.

Acceptance criteria

  • actions.lend.getPositions(address, params?) and wallet.lend.getPositions(params?) exist on the SDK.
  • Aggregates across all configured providers and markets.
  • LendMarketPosition[] shape matches the existing getPosition return.
  • Per-market failures don't poison the batch.
  • Demo useLendProvider.ts consumes getPositions instead of the manual fan-out.
  • CLI actions wallet lend positions ships.

Parent: #407.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions