Saltar a contenido

Fluid Vault (NFT-CDP)

Connector for Fluid's vault borrow surface (protocol="fluid_vault"): NFT-CDP lending positions driven by a single signed-delta operate() entrypoint per type-1 vault on Arbitrum and Base. market_id is the vault address and is required on every intent; the fToken lending surface (protocol="fluid") is documented under Fluid.

almanak.connectors.fluid_vault

Fluid vault (NFT-CDP) connector — thin second manifest over the fluid package.

Phase 3 (VIB-5031, ADR r2 Q0): Fluid vault borrow positions are NFT-CDPs driven by a single signed-delta operate() entrypoint per vault, with market_id (the vault address) REQUIRED — while the shipped Phase-2 fToken surface (protocol="fluid" / "fluid_lending") REJECTS any market_id. One protocol key cannot demand and forbid market_id simultaneously, so vault lending is its own connector registration: protocol="fluid_vault", one codebase (all implementation modules live in almanak.connectors.fluid), two manifests.

Scope (Checkpoint-1): arbitrum + base, type-1 vaults only. Position keys: lending:{chain}:fluid_vault:{wallet}:{vault}:{asset} (vault lowercased; the nftId is metadata in extracted_data_json, never a key segment).

Example

from decimal import Decimal

from almanak.framework.intents import BorrowIntent

intent = BorrowIntent( protocol="fluid_vault", market_id="0xeAbBfca72F8a8bf14C4ac59e69ECB2eB69F0811C", # arbitrum vault id 1 collateral_token="ETH", collateral_amount=Decimal("1"), borrow_token="USDC", borrow_amount=Decimal("500"), chain="arbitrum", )

FluidVaultCompiler

Bases: BaseLendingCompiler

Compile fluid_vault SUPPLY / BORROW / REPAY / WITHDRAW / DELEVERAGE.

DELEVERAGE routes to :meth:compile_repay via the :class:BaseLendingCompiler dispatch (framework convention: a deleverage is a repay with risk context).

FluidVaultSDK

FluidVaultSDK(
    chain: str,
    rpc_url: str | None = None,
    gateway_client: GatewayClient | None = None,
)

Low-level Fluid vault (NFT-CDP) protocol SDK.

All reads go through the VaultResolver with typed-ABI decoding; writes are offline calldata builders for the vault's operate() entrypoint.

Parameters:

Name Type Description Default
chain str

Chain name (one of FLUID_ADDRESSES).

required
rpc_url str | None

DEPRECATED — direct RPC URL for ad-hoc scripts/tests only.

None
gateway_client GatewayClient | None

Gateway client routing all eth_call traffic through the gateway's RpcService. Preferred for production code paths.

None

get_vault_entire_data

get_vault_entire_data(vault: str) -> FluidVaultData

VaultResolver.getVaultEntireData(vault) — 97-word typed decode.

position_by_nft_id

position_by_nft_id(
    nft_id: int,
) -> tuple[FluidVaultPosition, FluidVaultData]

VaultResolver.positionByNftId(nftId) — (12 + 97)-word typed decode.

positions_by_user

positions_by_user(wallet: str) -> list[FluidVaultPosition]

VaultResolver.positionsByUser(wallet) — every position with its vault.

The two returned arrays are index-aligned (position i ↔ its vault's data at i — verification report D1), so each position carries its lowercased vault address. No pagination exists; cost is bounded by the one-NFT-per-(wallet,vault) invariant.

resolve_user_nft_for_vault

resolve_user_nft_for_vault(
    wallet: str, vault: str
) -> int | None

Resolve the wallet's nftId on vault fresh from chain state.

The VIB-5010 answer: chain is the source of truth — persisted nftIds are never load-bearing. Returns None when the wallet holds no position NFT on the vault (the measured "no position" answer — distinct from a read failure, which RAISES FluidSDKError so the compiler fails closed instead of minting a duplicate position).

If the wallet somehow holds MULTIPLE NFTs on one vault (user acted outside the SDK), selection is deterministic: lowest nftId wins, with a warning — the others stay invisible by design (ADR §1.1).

encode_operate_calldata

encode_operate_calldata(
    nft_id: int, col_delta: int, debt_delta: int, to: str
) -> str

ABI-encode operate(nftId, newCol, newDebt, to) calldata.

Signed deltas: positive = deposit/borrow, negative = withdraw/repay, INT256_MIN = the protocol max sentinel (full withdraw/repay).

build_operate_tx

build_operate_tx(
    vault: str,
    nft_id: int,
    col_delta: int,
    debt_delta: int,
    to: str,
    value: int = 0,
) -> dict[str, Any]

Build an operate() transaction for a Fluid type-1 vault.

Parameters:

Name Type Description Default
vault str

Vault contract address (the per-market operate target).

required
nft_id int

Position NFT id (0 mints a new position).

required
col_delta int

Signed collateral delta (raw units; INT256_MIN = all).

required
debt_delta int

Signed debt delta (raw units; INT256_MIN = full repay).

required
to str

Recipient of withdrawn collateral / borrowed debt.

required
value int

msg.value — MUST equal col_delta for native-collateral deposits; 0 for every ERC-20 leg.

0

__getattr__

__getattr__(name: str) -> Any

PEP 562 lazy attribute access (no registration side effects here).

Implementation modules

The implementation lives in the fluid package (one codebase, two manifests):

almanak.connectors.fluid.vault_sdk

Back-compat shim: fluid.vault_sdk re-exports the shared implementation from almanak.connectors._fluid_core.vault_sdk (single source of truth).

FluidVaultPosition dataclass

FluidVaultPosition(
    nft_id: int,
    owner: str,
    is_liquidated: bool,
    is_supply_position: bool,
    tick: int,
    tick_id: int,
    supply: int,
    borrow: int,
    dust_borrow: int,
    vault: str = "",
)

One decoded UserPosition (+ the vault it belongs to, when paired).

supply / borrow are exchange-price-scaled TOKEN amounts in the vault pair's native base units (verification report note 4); borrow already has dust_borrow netted out.

FluidVaultData dataclass

FluidVaultData(
    vault: str,
    is_smart_col: bool,
    is_smart_debt: bool,
    supply_token: str,
    borrow_token: str,
    vault_id: int,
    vault_type: int,
    collateral_factor: int,
    liquidation_threshold: int,
    liquidation_max_limit: int,
    liquidation_penalty: int,
    oracle: str,
    oracle_price_operate: int,
    oracle_price_liquidate: int,
    withdrawable: int,
    borrowable: int,
    total_supply_vault: int,
    total_borrow_vault: int,
)

The VaultEntireData fields the compiler / lending read consume.

FluidVaultSDK

FluidVaultSDK(
    chain: str,
    rpc_url: str | None = None,
    gateway_client: GatewayClient | None = None,
)

Low-level Fluid vault (NFT-CDP) protocol SDK.

All reads go through the VaultResolver with typed-ABI decoding; writes are offline calldata builders for the vault's operate() entrypoint.

Parameters:

Name Type Description Default
chain str

Chain name (one of FLUID_ADDRESSES).

required
rpc_url str | None

DEPRECATED — direct RPC URL for ad-hoc scripts/tests only.

None
gateway_client GatewayClient | None

Gateway client routing all eth_call traffic through the gateway's RpcService. Preferred for production code paths.

None

get_vault_entire_data

get_vault_entire_data(vault: str) -> FluidVaultData

VaultResolver.getVaultEntireData(vault) — 97-word typed decode.

position_by_nft_id

position_by_nft_id(
    nft_id: int,
) -> tuple[FluidVaultPosition, FluidVaultData]

VaultResolver.positionByNftId(nftId) — (12 + 97)-word typed decode.

positions_by_user

positions_by_user(wallet: str) -> list[FluidVaultPosition]

VaultResolver.positionsByUser(wallet) — every position with its vault.

The two returned arrays are index-aligned (position i ↔ its vault's data at i — verification report D1), so each position carries its lowercased vault address. No pagination exists; cost is bounded by the one-NFT-per-(wallet,vault) invariant.

resolve_user_nft_for_vault

resolve_user_nft_for_vault(
    wallet: str, vault: str
) -> int | None

Resolve the wallet's nftId on vault fresh from chain state.

The VIB-5010 answer: chain is the source of truth — persisted nftIds are never load-bearing. Returns None when the wallet holds no position NFT on the vault (the measured "no position" answer — distinct from a read failure, which RAISES FluidSDKError so the compiler fails closed instead of minting a duplicate position).

If the wallet somehow holds MULTIPLE NFTs on one vault (user acted outside the SDK), selection is deterministic: lowest nftId wins, with a warning — the others stay invisible by design (ADR §1.1).

encode_operate_calldata

encode_operate_calldata(
    nft_id: int, col_delta: int, debt_delta: int, to: str
) -> str

ABI-encode operate(nftId, newCol, newDebt, to) calldata.

Signed deltas: positive = deposit/borrow, negative = withdraw/repay, INT256_MIN = the protocol max sentinel (full withdraw/repay).

build_operate_tx

build_operate_tx(
    vault: str,
    nft_id: int,
    col_delta: int,
    debt_delta: int,
    to: str,
    value: int = 0,
) -> dict[str, Any]

Build an operate() transaction for a Fluid type-1 vault.

Parameters:

Name Type Description Default
vault str

Vault contract address (the per-market operate target).

required
nft_id int

Position NFT id (0 mints a new position).

required
col_delta int

Signed collateral delta (raw units; INT256_MIN = all).

required
debt_delta int

Signed debt delta (raw units; INT256_MIN = full repay).

required
to str

Recipient of withdrawn collateral / borrowed debt.

required
value int

msg.value — MUST equal col_delta for native-collateral deposits; 0 for every ERC-20 leg.

0

position_from_tuple

position_from_tuple(
    raw: Any, vault: str = ""
) -> FluidVaultPosition

Map a decoded 12-field UserPosition tuple onto the dataclass.

vault_data_from_tuple

vault_data_from_tuple(raw: Any) -> FluidVaultData

Map a decoded 97-word VaultEntireData tuple onto the dataclass.

__getattr__

__getattr__(name: str)

Re-export private / non-all names for back-compat (PEP 562).

almanak.connectors.fluid.vault_compiler

Back-compat shim: fluid.vault_compiler re-exports the shared implementation from almanak.connectors._fluid_core.vault_compiler (single source of truth).

FluidVaultCompiler

Bases: BaseLendingCompiler

Compile fluid_vault SUPPLY / BORROW / REPAY / WITHDRAW / DELEVERAGE.

DELEVERAGE routes to :meth:compile_repay via the :class:BaseLendingCompiler dispatch (framework convention: a deleverage is a repay with risk context).

__getattr__

__getattr__(name: str)

Re-export private / non-all names for back-compat (PEP 562).