Morpho Markets Adapter – Absinthe Docs
Skip to content

Morpho Markets Adapter

Lending markets with supply/borrow tracking

What It Tracks

  • Supply positions (lending)
  • Borrow positions (debt)

Design Reasoning

Why assetSelectors.marketId?

Morpho Blue has many markets, each identified by a marketId. The morphoBlueAddress identifies the protocol, but you need marketId to know WHICH market to track.

Why track shares, not assets?

Morpho uses a shares-based accounting system:

  • Users hold "shares" not raw assets
  • Share value appreciates with interest
  • The pricer converts shares → assets → USD
Why separate supply and borrow?

In the output, we create separate asset keys:

  • morpho:{marketId}-supply for lenders
  • morpho:{marketId}-borrow for borrowers

Manifest

export const manifest: Manifest = {
  name: 'morpho-markets',
  version: '0.0.1',
  chainArch: 'evm',
  trackables: {
    morphoblue: {
      kind: 'position',
      quantityType: 'token_based',
      params: {
        morphoBlueAddress: evmAddress('The morpho blue address'),
      },
      assetSelectors: {
        marketId: stringField('The market id to track'),
      },
      requiredPricer: morphomarketsFeed,
    },
  },
};

Event Handling

// Events tracked: Supply, Withdraw, Borrow, Repay
 
async function handleSupply(log, emitFns, instance, marketId, redis) {
  const { id, onBehalf, assets, shares } = morphov1Abi.events.Supply.decode(log);
  
  // Only process events for our configured market
  if (id.toLowerCase() !== marketId) return;
 
  // Update market index in Redis (for pricing)
  const supplyIndex = deriveIndexFromEvent(assets, shares);
  await updateMarketData(redis, marketId, { supplyIndex });
 
  // Emit the position change
  await emitFns.position.balanceDelta({
    user: onBehalf.toLowerCase(),
    asset: { type: 'custom', prefix: 'morpho', key: `${marketId}-supply` },
    amount: BigInt(shares),  // Track shares, not assets
    activity: 'hold',
    trackableInstance: instance,
    meta: { marketId, shares: shares.toString(), positionSide: 'supply' },
  });
}
 
async function handleBorrow(log, emitFns, instance, marketId, redis) {
  const { id, onBehalf, assets, shares } = morphov1Abi.events.Borrow.decode(log);
  
  if (id.toLowerCase() !== marketId) return;
 
  const borrowIndex = deriveIndexFromEvent(assets, shares);
  await updateMarketData(redis, marketId, { borrowIndex });
 
  await emitFns.position.balanceDelta({
    user: onBehalf.toLowerCase(),
    asset: { type: 'custom', prefix: 'morpho', key: `${marketId}-borrow` },
    amount: BigInt(shares),
    activity: 'hold',
    trackableInstance: instance,
    meta: { marketId, shares: shares.toString(), positionSide: 'borrow' },
  });
}

Example Config

{
  "adapterConfig": {
    "adapterId": "morpho-markets",
    "config": {
      "morphoblue": [
        {
          "params": {
            "morphoBlueAddress": "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"
          },
          "assetSelectors": {
            "marketId": "0xd0d22868a9460dd837f020a1ba300b81f26520602cc61f2b515cf6413b15ddac"
          },
          "pricing": {
            "kind": "morphomarkets",
            "underlyingAsset": {
              "kind": "pegged",
              "usdPegValue": 1
            }
          }
        }
      ]
    }
  }
}

Config Fields

FieldDescription
params.morphoBlueAddressThe Morpho Blue protocol contract
assetSelectors.marketIdThe specific market to track (bytes32)
pricing.underlyingAssetHow to price the underlying loan asset

Key Patterns

  1. Filter by marketId: Only process events for the configured market
  2. Track shares, not assets: Shares are the accounting unit; pricer handles conversion
  3. Separate supply/borrow: Different asset keys for different position types
  4. Update indices in Redis: Pricing needs up-to-date market indices