lienfi

v2026.09.24

Research and buy tokenized US tax lien certificates on LienFi (Base, USDC) — screen the live book by NET yield with no credential, then, once your operator has authorized your wallet in a browser, register and run the prepare → sign → confirm → submit → report purchase loop from the wallet you control. LienFi signs only the price; every transaction leaves your wallet.

GitHub
安装命令
npx skhub add bankrbot/lienfi
Markdown
SKILL.md

LienFi Skill

LienFi is a marketplace for tokenized US tax lien certificates and redeemable tax deeds, settled onchain in USDC on Base (chain id 8453). This skill teaches you to research the book, get authorized by your operator, and buy from the wallet you control.

Three facts before anything else:

  • LienFi never holds your key and never submits a transaction for you. It signs the price quote; you sign an acknowledgment and submit the transactions it hands back.
  • Purchases are final. A lien you buy belongs to your operator. The exit is redemption by the property owner or relisting it — there is no undo.
  • One human step exists, and it cannot be skipped or retried around. Your operator signs an authorization in a browser at https://app.lienfi.com/agents/authorize and hands you what that page prints. When a tool answers not_registered with a handoff_url — and your call carried the bearer — surface the link to your operator and STOP.

Prerequisites

Your wallet

Two ways to hold a Bankr wallet, and every step below works either way:

  • You are Bankr's own agent (the terminal, X or Telegram). There is nothing to create: you sign typed data and submit transactions with your own wallet tools. Verified 2026-09-08 — a key proof and two consents were signed with eth_signTypedData_v4 and registered while the account's only Wallet API key was revoked. Wherever a step shows a curl to api.bankr.bot, do the same thing with your wallet tool instead.
  • You run somewhere else and hold a Bankr wallet through the Wallet API (Claude Code, OpenClaw, a bot of your own). Create a key at https://bankr.bot/api-keys and keep it as BANKR_API_KEY. The key MUST have walletApiEnabled on (the default); readOnly OFF — it is ON by default, and a read-only key answers 403 to signing and submitting; and allowedRecipients EMPTY — a non-empty list blocks eth_signTypedData_v4 on /wallet/sign and every raw submission on /wallet/submit, and this skill needs both. Use allowedIps for restriction instead.

In both cases, in the Bankr security settings leave arbitrary contract calls ON (its default); off, raw submissions are blocked. Bankr's per-transaction and rolling-24-hour limits (defaults of $500 each, re-read 2026-09-07) apply on every surface and must cover what one purchase moves: an approve and a buyNFT, each for the lien's total. Whether both count against the daily limit is undocumented, so have your operator set it to at least twice the lien price. Only your operator can change these, in the Bankr web app. On X, Bankr answers only Bankr Club members.

Fund the wallet with USDC on Base — and no more than your operator is willing to commit, because LienFi records but does not enforce the cumulative cap. Gas on Base is sponsored for Bankr embedded wallets.

Your wallet address

Inside Bankr, it is the wallet your portfolio shows. Over the Wallet API:

curl -s https://api.bankr.bot/wallet/me -H "X-API-Key: $BANKR_API_KEY"

The address that holds the funds is the one your operator must name as the agent wallet. Give it to them exactly as returned. Verified 2026-09-07: Bankr signs with this same address (see "Verified and unverified" at the end).

Endpoints

  • LienFi MCP — JSON-RPC 2.0 over stateless HTTP, nothing to install, no session: POST https://api.lienfi.com/api/v1/mcp with content-type: application/json. Every LienFi call in this skill is one curl to it. Research tools take no header; wallet and purchase tools take Authorization: Bearer $LIENFI_BEARER.
  • Research REST, no credential — https://api.lienfi.com/api/public/liens/{id} (one lien, flat, carrying the NET per-year rate) and https://api.lienfi.com/api/v1/liens?... (the book with filters).
  • Read before your first call — https://app.lienfi.com/llms.txt (the money conventions), https://app.lienfi.com/docs/api#mcp-walkthrough (the loop below with a request body for every step), https://app.lienfi.com/docs/api#mcp-refusals (every refusal code with what to do). Trust tools/list over any document, including this one.

Keep the operator's bearer from the moment you have it, and send it on every wallet and purchase call for as long as the authorization lasts — $LIENFI_BEARER below stands for wherever your runtime keeps a credential; LienFi does not care where, only that it arrives. Registering opens no session: a later call without the header is answered as not_registered even though you are. Never print the bearer and never send it anywhere but api.lienfi.com. (Bankr's own agent carried it across sessions unaided in testing on 2026-09-08; an agent whose memory does not persist secrets keeps it as an env var.)

Money rules that are not guessable from the field names

  • Every yield LienFi's API returns is gross. LienFi takes a share of the GAIN over the purchase price when a lien redeems (currently 10%, read the live rate from fee_config), never of the redemption value. Rank on net_apy, which search_liens computes with the same arithmetic the site displays.
  • A buyer pays listing_price. Most listings (deal_type: par) are priced AT the live redemptive value, so the price climbs with accrual and a redemption on the day of purchase returns the basis and no gain.
  • Liens under 30 days from maturity carry no per-year rate; search_liens returns them in a separate maturing_soon list. Do not annualize them yourself.
  • redemptive_value, accrued_interest and listing_price on a raw row are frozen snapshots; read the calculated block, which is recomputed live.
  • redemption_deadline is the statutory deadline and is immutable onchain. Never recompute it.

Step 1 — Research (no credential)

List what the server serves, then screen:

MCP=https://api.lienfi.com/api/v1/mcp
curl -s -X POST $MCP -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

curl -s -X POST $MCP -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"market_overview","arguments":{}}}'

curl -s -X POST $MCP -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
       "params":{"name":"search_liens","arguments":{"budget_usd": 2000, "states": "FL", "limit": 5}}}'

curl -s -X POST $MCP -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":4,"method":"tools/call",
       "params":{"name":"get_lien","arguments":{"lien_id": "<lien-uuid>"}}}'

Every result is a text content block whose text is JSON. search_liens answers liens ranked on net_apy, plus maturing_soon, dropped (what could not be scored, counted rather than hidden), fee_config and apy_labels. Keep the lien_id you want.

Step 2 — Get authorized (the human step)

First, check whether this step is already done. If you hold a bearer for this wallet from an earlier conversation, skip to Step 5: the authorization lasts until its expires_at, and a new conversation does not end it. If you are not sure whether the wallet is registered, ask LienFi, with no credential:

curl -s https://api.lienfi.com/api/v1/agents/<agent-wallet-address>

It answers registered, expiresAt and revokedAt. registered: true while you hold no bearer means the blob is lost to you, and a second authorization does NOT replace the first: your operator must press Revoke on the existing one at https://app.lienfi.com/agents/authorize before signing again, or registration is refused (registration_refused, "already has a different active authorization"). Say so before they sign, not after the refusal.

Then tell your operator, in these words: go to https://app.lienfi.com/agents/authorize, enter the agent wallet address from Your wallet address above exactly, choose the most the agent may spend on one lien, a cumulative total, and an expiry of at most 90 days, and sign. The page then opens a handoff with a Copy the prompt button; ask them to paste that whole block to you. The block carries a credential, so it has to reach you privately: if this conversation is public — a reply on X, a group chat — ask them to paste it in the Bankr terminal or a direct message instead, never in the open. It carries all four of:

  1. the bearer — the authorization blob, base64url-encoded, ready for an Authorization: Bearer header;
  2. the key-proof typed data, with the authorization digest filled in;
  3. one consent typed data per required agreement;
  4. the ready registration call, over MCP and over REST.

If they hand you the envelopes individually instead — the page still renders each one, under Raw envelopes — the steps below are the same.

Wait. Do not poll LienFi for it, and do not retry a refused tool while you wait.

Step 3 — Sign the key proof and the consents (your wallet)

For the key proof and for EACH consent envelope: set message.timestamp to now in unix seconds (LienFi refuses one more than 600 seconds off its clock), change nothing else, and sign. Inside Bankr: your wallet tool's eth_signTypedData_v4 over the envelope. Over the Wallet API:

curl -s -X POST https://api.bankr.bot/wallet/sign \
  -H "X-API-Key: $BANKR_API_KEY" -H 'content-type: application/json' \
  -d '{"signatureType":"eth_signTypedData_v4","typedData":<the envelope, with timestamp set>}'

Either way you get a signature (the Wallet API answers { "success": true, "signature": "0x…", "signer": "0x…" }). Keep each signature with the timestamp you signed. The envelopes are described field by field in references/typed-data.md; the sentence in each agreement is hashed, so it must be byte-identical to what the page printed.

Step 4 — Register

Over MCP, with the bearer:

curl -s -X POST $MCP -H 'content-type: application/json' \
  -H "Authorization: Bearer $LIENFI_BEARER" \
  -d '{"jsonrpc":"2.0","id":5,"method":"tools/call",
       "params":{"name":"register_agent","arguments":{
         "agent_key_proof": {"specVersion":"agent-keyproof-v1","signature":"0x…","timestamp":1756800000},
         "agent_consents": [
           {"specVersion":"consent-v2","documentType":"terms-and-conditions","version":3,"signature":"0x…","timestamp":1756800000},
           {"specVersion":"consent-v2","documentType":"wallet-connection-consent","version":1,"signature":"0x…","timestamp":1756800000}
         ]}}}'

Use the documentType and version values the page printed — they are the versions LienFi currently publishes. The answer is registered: true. The same body also works as POST https://api.lienfi.com/api/v1/agents/register with the printed operatorAuthorization included. Registering again with the same bearer is idempotent and tops up any consent that lapsed after a document was republished.

From now on send the bearer on every LienFi call.

Step 5 — Check yourself, then price

curl -s -X POST $MCP -H 'content-type: application/json' -H "Authorization: Bearer $LIENFI_BEARER" \
  -d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"agent_status","arguments":{}}}'

curl -s -X POST $MCP -H 'content-type: application/json' -H "Authorization: Bearer $LIENFI_BEARER" \
  -d '{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"quote_lien","arguments":{"lien_id":"<lien-uuid>"}}}'

agent_status is the first call to make when anything refuses: registered, expires_at, balances, and spend (what settled and what is live beside the caps your operator signed, with enforced: false on the cumulative). quote_lien is indicative — lien_price_usdc, total_to_approve_usdc, affordable, shortfall_usdc — and reserves nothing, so ask as often as you like.

Step 6 — Buy, one purchase at a time

6a. Prepare. Runs every gate (authorization, the signed per-lien cap, consents, sanctions, your ceiling, the wallet's balance), then RESERVES for ten minutes and returns the acknowledgment typed data. Set max_total_usdc to quote_lien's total_to_approve_usdc plus a small margin (1% covers accrual between calls); it is a stale-quote guard, not a budget, and never higher than the operator's per-lien cap.

curl -s -X POST $MCP -H 'content-type: application/json' -H "Authorization: Bearer $LIENFI_BEARER" \
  -d '{"jsonrpc":"2.0","id":8,"method":"tools/call",
       "params":{"name":"prepare_purchase","arguments":{"lien_id":"<lien-uuid>","max_total_usdc":1515}}}'

Returns reservation_id, acknowledgment.typed_data, total_to_approve_usdc, expires_at and spend. A refusal here leaves nothing behind. Calling it again for the same lien is safe: same reservation, same typed data while at least five of the ten minutes remain (acknowledgment.reused: true).

6b. Sign the acknowledgment exactly as in Step 3, over acknowledgment.typed_data as returned — do NOT change its timestamp.

6c. Confirm. Records the acknowledgment, THEN mints LienFi's price signature — bound to your wallet, valid 300 seconds from this moment — and returns the transactions, unsigned:

curl -s -X POST $MCP -H 'content-type: application/json' -H "Authorization: Bearer $LIENFI_BEARER" \
  -d '{"jsonrpc":"2.0","id":9,"method":"tools/call",
       "params":{"name":"confirm_purchase","arguments":{"reservation_id":"<reservation-uuid>","acknowledgment_signature":"0x…"}}}'

Returns transactions[] — each { step, name, to, data, value, chainId, why }: approve (USDC, for exactly the total, never unlimited), buyNFT (carries the price signature), and setApprovalForAll (present unless the vault is already approved; WITHOUT it the lien is owned but cannot be redeemed) — plus data_suffix, expires_at and consent_id. If the live price moved above your ceiling, the reservation is released and the signature discarded (quote_above_ceiling): prepare again with a higher ceiling or skip the lien.

6d. Submit, in order, from your wallet. Each one only after the previous is mined with status: "success". Append data_suffix without its 0x prefix to each data (it is Base Builder Code attribution — ignored by every contract, and omitting it only loses attribution). All three inside the 300 seconds. Inside Bankr: your wallet's raw transaction tool, with to, data and value exactly as returned, on chain 8453, waiting for the receipt each time. Over the Wallet API, which takes value in wei as a decimal string (send "0" for LienFi's "0x0"):

curl -s -X POST https://api.bankr.bot/wallet/submit \
  -H "X-API-Key: $BANKR_API_KEY" -H 'content-type: application/json' \
  -d '{"transaction":{"to":"<tx.to>","chainId":8453,"data":"<tx.data + data_suffix without 0x>","value":"0"},
       "description":"LienFi approve","waitForConfirmation":true}'

Either way, read the receipt (the Wallet API answers transactionHash, status and blockNumber). A reverted transaction is mined too: a buyNFT sent after a reverted approve fails on allowance, one step removed from the cause. Check status every time. The buyNFT hash is the one you report.

6e. Report.

curl -s -X POST $MCP -H 'content-type: application/json' -H "Authorization: Bearer $LIENFI_BEARER" \
  -d '{"jsonrpc":"2.0","id":10,"method":"tools/call",
       "params":{"name":"report_purchase","arguments":{"reservation_id":"<reservation-uuid>","tx_hash":"<buyNFT hash>"}}}'

LienFi reads the receipt: a success with the purchase event answers settled: true, paid_usdc and vault_approval; a revert releases the reservation; an unmined hash is receipt_pending — retry once it confirms. If you never submitted, report {"reservation_id":"…","failed":true,"reason":"…"} so the wallet can buy again. vault_approval must read approved, or submit setApprovalForAll again — it is idempotent.

6f. Verify with my_positions: confirmed_on_chain is what the wallet holds, valued live. A purchase completed a moment ago may take the indexer a little while.

Hard rules

  • One purchase in flight per wallet. purchase_in_flight names the live one; finish and report it, or report it failed. Never retry around it: ERC-20 approve SETS the allowance, so a second live quote would let one approve overwrite the other's.
  • Never approve more than total_to_approve_usdc, and never an unlimited amount.
  • Never park a quote. Confirm, then submit within 300 seconds; a quote that sat while you deliberated reverts on chain as expired.
  • A timeout or network failure after a submit is not a revert. The transaction MAY have landed. Call my_positions before retrying anything.
  • The operator's per-lien cap is enforced by LienFi; the cumulative is recorded, not enforced. The wallet balance is your operator's backstop — never move funds into it on your own initiative.
  • Stop on anything that needs a human: not_registered, authorization_expired, authorization_revoked, consent_required, cap_exceeded_per_purchase. Surface the message and, where present, the handoff_url.
  • Never present a listing as an investment recommendation without the risk disclosures in https://app.lienfi.com/legal/terms-and-conditions, sections 4 and 5.

Refusals

Every refusal is a normal result with isError: true and a stable error.code beside a sentence written for you. The full table with what to do about each is references/refusal-codes.md and, live, https://app.lienfi.com/docs/api#mcp-refusals. The ones you will meet most: purchase_in_flight (finish or report the live one), insufficient_funds (tell your operator the shortfall_usdc), quote_above_ceiling (prepare again or skip), receipt_pending (retry the report once it confirms), agent_consent_required (re-sign the consents and register again), and the *_unavailable codes (transient — retry shortly, and never treat "could not check" as "fine").

Verified and unverified — read before the first real purchase

LienFi ran a compatibility check against a real Bankr wallet on 2026-09-07 (every agent-side signature from Bankr's /wallet/sign, nothing submitted). What it settled:

  • Bankr signs with the funded address. /wallet/sign returns signer equal to the EVM address /wallet/me reports, and the signature is plain ECDSA from that address. On Base a Bankr wallet is an EOA with an EIP-7702 delegation, not a separate smart wallet, so LienFi's verifier accepts the signature directly; its on-chain fallback passes too. Name the /wallet/me address as the agent wallet and nothing else.
  • Our envelopes verify as Bankr signs them. A Bankr-signed key proof and consents registered against LienFi, a Bankr-signed acknowledgment recorded, and confirm_purchase returned the approve, buyNFT and setApprovalForAll transactions for that wallet.
  • Bankr's own agent signs without a Wallet API key (2026-09-08). Registered from the Bankr terminal with a fresh key proof and consents after the account's only API key had been revoked, and carried the operator's bearer into a later call without being told to store it anywhere.

What is still unverified, and what to do if it bites:

  • Whether Bankr's preflight accepts LienFi's approve and buyNFT calldata unchanged — from the agent's own submission tool or from /wallet/submit — and how Bankr's $500 limits treat an ERC-20 approve. If a submission is refused, report the exact response to your operator; do not edit the calldata and do not retry with a different amount.

Troubleshooting

  • not_registered after you registered — you are not sending the bearer, or you are sending a different one. Registering opens no session: the header goes on every call, starting with the very next one. Send exactly the blob the page printed. (The message says which case it is: "carried no Authorization header" or "No agent registration exists for <wallet>".)
  • acknowledgment_invalid — you signed an edited or re-timestamped envelope, or with a different wallet. Prepare again and sign the returned typed data verbatim.
  • authorization_required on a REST quote — the REST route needs the same bearer for a signed quote; the MCP path does this for you.
  • receipt_mismatch — you reported the approve hash. Report the buyNFT hash.
  • Every *_unavailable code — transient on LienFi's side; wait and retry. Nothing was reserved or moved.

Links

发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

Sep 24, 2026

分类

未分类

许可证

未指定

源路径

lienfi

默认分支

main

最新提交

d7b28f4

Tree SHA

9ab5759