REST API.

For teams that want their own interface rather than the embed widget. The API prepares transactions; the user's wallet still signs and submits them, so nothing here can move funds.

Updated 2026-10-07 · HoodLock Team

Base URL and format#

All endpoints live under https://hoodlock.tech/api/dev, take and return JSON, and send permissive CORS headers so they work from a browser as well as a server.

Note

These routes send X-Robots-Tag: noindex, so search engines index this page rather than the JSON.

Authentication#

One credential: a public API key of the form pk_ followed by 48 hex characters. You get it by signing in with your wallet and registering a developer handle in the developer dashboard. A handle is 3 to 20 characters of lowercase letters, digits, underscores or hyphens.

MethodHow the key is passed
GETIn the query string, as ?key=pk_…
POSTAs a key field in the JSON body
Careful

There is no header form. The CORS policy allows Content-Type only, so an Authorization header will be blocked on cross-origin requests. Use the query string or the body field.

Note

The key is public on purpose, in the same sense as a Stripe publishable key. It credits actions to you and does nothing else. It cannot move funds, read balances or reach admin. Claiming earnings requires a signature from your wallet, which the key cannot produce. Embedding it in frontend source is the intended use.

GET /api/dev/config#

Everything your interface needs to build a transaction. Call it once at boot rather than hardcoding addresses or fees. The fees are read from the contracts and can change.

shell
curl "https://hoodlock.tech/api/dev/config?key=pk_YOUR_KEY"
json
{
  "chainId": 4663,
  "rpc": "https://rpc.mainnet.chain.robinhood.com",
  "explorer": "https://robinhoodchain.blockscout.com",
  "locker": "0xd0f7d8c6e9f6d80c297bebe4f7fd1b9c8125c32f",
  "burner": "0x6bf43ca706faa8ea46803299c191484e82280652",
  "vesting": "0x910e19bcC4bce46999994Ed7297E0Fc4431ec72E",
  "feeWei": "5000000000000000",
  "feeEth": 0.005,
  "airdrop": "0x6B1fE7b821001144Ee74EEA258b0AafdE20102E8",
  "airdropFee": { "perWalletQuoteAt1Eth": 0, "quote": "call quote(uint32 recipients) on the airdrop contract" },
  "fees":    { "lock": "5000000000000000", "burn": "5000000000000000", "vesting": "5000000000000000" },
  "feesEth": { "lock": 0.005, "burn": 0.005, "vesting": 0.005 },
  "commission": 0.5,
  "code": "your-handle",
  "liquidityLocker": {
    "address": "0x91Aa2dA1956C77F8448E18DCaf53383bCc658e67",
    "v3Positions": "0x73991a25C818Bf1f1128dEAaB1492D45638DE0D3",
    "v4Positions": "0x58daec3116aae6D93017bAAea7749052E8a04fA7",
    "collectFeeBps": 2500,
    "fixedFeeWei": "0",
    "paused": false,
    "intent": "POST /api/dev/lp-lock-intent"
  }
}

feeWei and feeEth hold the lock fee. They predate the per-product fields and are kept so older integrations keep working, so prefer fees. A product that is not configured on the chain returns null.

airdrop is the airdrop contract. Its fee depends on how many recipients an airdrop declares, so call quote(recipients) on that contract rather than reading a flat number. There is no intent endpoint for airdrops. commission is your share of each credited fee, and code is your handle, which the attribution signature below needs.

liquidityLocker describes the Liquidity Locker for Uniswap V3 and V4 positions: its address, the two Uniswap position managers, and its live fee. collectFeeBps is HoodLock's share of the trading fees an owner claims, in basis points (currently 25%), and fixedFeeWei is the fee paid when locking. Both are read from the contract. It is null where the locker is not deployed.

Errors: 404 {"error":"unknown key"}, or 503 if the database is unavailable.

Transaction intents#

Each intent validates your inputs, encodes the call and returns an unsigned transaction. You submit it from the user's wallet.

json
{ "to": "0x…", "data": "0x…", "value": "5000000000000000", "chainId": 4663, "note": "…" }
Careful

For the lock, burn and vesting intents, the user must first approve the token for to. Those intents cover the HoodLock call, not the ERC-20 approval. The burn and vesting responses repeat this in note, but the lock response does not, so do not rely on it. The liquidity lock intent is different: it returns the NFT approval itself, see below.

Note

Intents are also how you get paid. The server remembers the token, the exact amount and, for locks and vesting, the exact unlock or end second. When a matching record appears on-chain after the intent, its fee share is credited to you, whichever wallet signed it and however often that wallet has used HoodLock before. Submit exactly the values you asked for, or the action will not match. A fee-on-transfer token records less than was sent, so those actions are not matched automatically.

Invalid input returns 400 with an error message, and an unknown key returns 404 {"error":"unknown key"}.

POST /api/dev/lock-intent#

FieldTypeNotes
keystringYour API key.
tokenaddressThe ERC-20 to lock.
amountstringInteger string, in the token's smallest unit.
unlockTimestringUnix seconds. Must be in the future.
shell
curl -X POST https://hoodlock.tech/api/dev/lock-intent \
  -H "content-type: application/json" \
  -d '{"key":"pk_YOUR_KEY",
       "token":"0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73",
       "amount":"1000000000000000000000",
       "unlockTime":"1790000000"}'

POST /api/dev/burn-intent#

Fields: key, token, amount. Returns 404 if burning is not configured on the chain.

Irreversible

Burning is irreversible. Show the user what is about to happen in your own UI, because the intent endpoint will not stop them.

POST /api/dev/vesting-intent#

FieldTypeNotes
keystringYour API key.
tokenaddressThe ERC-20 to vest.
amountstringInteger string, smallest unit.
beneficiaryaddressWho receives the tokens.
endstringUnix seconds. Must be at least 24 hours after start.
startstring, optionalDefaults to the time of the request.
cliffstring, optionalDefaults to start. Must satisfy start ≤ cliff ≤ end.

Returns 404 if vesting is not configured on the chain. The endpoint checks the dates and the cliff, so most schedules that would revert are refused before they reach a signature prompt. It does not reject a zero beneficiary or a token address with no code, and the contract rejects both.

Careful

Leave a margin on the 24 hours. This endpoint checks that end - start ≥ 86400 and that end is more than 24 hours from now, as the contract does with end > block.timestamp + MIN_DURATION. The contract checks again in the block where the transaction lands, so a schedule that ends almost exactly 24 hours from now can pass here and still revert if the user signs late. Add a few minutes.

POST /api/dev/lp-lock-intent#

Locks a Uniswap V3 or V4 liquidity position, an NFT, in the Liquidity Locker until a date the user picks. The position keeps earning trading fees the whole time and its owner claims them whenever they like. The unlock date can only be extended, and the position comes back whole after it. Everything is checked against the chain before anything is returned: the position exists and holds liquidity, the named owner holds it, the date is inside what the contract accepts, and new locks are not paused.

FieldTypeNotes
keystringYour API key.
versionstring"v3" or "v4": which Uniswap position manager holds the NFT.
tokenIdstringThe position's NFT token id.
unlockTimestringUnix seconds. In the future and at most 100 years ahead.
owneraddress, optionalThe wallet that will sign. When given, the request is refused unless it holds the NFT.
feeRecipientaddress, optionalWhere the claimed trading fees go. Defaults to the owner. The owner can change it later.
acceptUnverifiedHookboolean, optionalSend true only after your user accepted a warning about the pool's hook. See below.
shell
curl -X POST https://hoodlock.tech/api/dev/lp-lock-intent \
  -H "content-type: application/json" \
  -d '{"key":"pk_YOUR_KEY","version":"v4","tokenId":"3546660",
       "owner":"0xUSER","unlockTime":"1822500000"}'
json
{
  "chainId": 4663,
  "approve": { "to": "0x58daec3116aae6D93017bAAea7749052E8a04fA7", "data": "0x…", "value": "0" },
  "lock":    { "to": "0x91Aa2dA1956C77F8448E18DCaf53383bCc658e67", "data": "0x…", "value": "0" },
  "collectFeeBps": 2500,
  "hookStatus": "none",
  "owner": "0xUSER",
  "feeRecipient": "0xUSER",
  "note": "…"
}

Send approve first and wait for it to confirm, then lock, both from the wallet that holds the position. approve is null when the locker is already approved for that NFT. lock.value is the fixed fee in wei. The id of the new lock is the first indexed topic of the Locked event in the receipt, and its proof page is https://hoodlock.tech/proof/position/<id>.

Hooks. Any Uniswap V4 pool can be locked, whatever hook it uses. hookStatus says what the pool's hook is: none (no hook), verified (reviewed by HoodLock), safe (not reviewed, but it has no permission that runs when liquidity is removed) or unverified (not reviewed and it can act when liquidity is removed). For an unverified hook the request is refused with 409 until you send "acceptUnverifiedHook": true. Show your user a warning first and send it only if they accept; HoodLock's own app asks the same question. Withdrawing after the unlock date never calls the pool or its hook, so no hook can block it.

StatusWhen
400Bad version, tokenId, unlockTime, owner or feeRecipient.
404Unknown key, no position with that id, or the Liquidity Locker is not deployed on the chain.
409The position is already locked, holds no liquidity, is not held by owner, new locks are paused, or its hook needs acceptUnverifiedHook.
503The chain could not be read. Try again.

To read a lock afterwards, GET /api/lp/lock/{id} returns its pair, amounts, value, unlock date and owner, and GET /api/lp/claimable/{id} returns the trading fees it could claim right now, after HoodLock's share. The owner claims with collect(id) on the locker and takes the position back with withdraw(id, to) after the unlock date.

POST /api/dev/lp-collect-intent#

Builds the fee claim for one or more liquidity locks, so a creator can claim their share on your site instead of on HoodLock. The owner's share always goes to each lock's fee recipient; this only prepares the transaction and says which wallet must send it.

FieldTypeNotes
keystringYour API key.
id or idsinteger or arrayOne lock id, or up to 50 with the same owner.
owneraddress, optionalThe wallet that will sign. When given, the request is refused unless it owns the lock.
json
{
  "chainId": 4663,
  "collect": { "to": "0x91Aa2dA1956C77F8448E18DCaf53383bCc658e67", "data": "0x…", "value": "0" },
  "owner": "0xCREATOR",
  "locks": [{ "id": 42, "feeRecipient": "0xCREATOR", "collectFeeBps": 2500 }]
}
StatusWhen
400No ids, more than 50, or an id that is not a positive integer.
404Unknown key or no lock with that id.
409A lock was withdrawn, the ids have different owners, or owner does not own the lock.
503The chain could not be read. Try again.

If your launchpad's contract owns the lock instead, it calls collect(id) on the locker itself; that function can be open to anyone, because the creator's share can only go to the creator.

Paying the token's creator. The fee recipient is fixed at lock time and receives the owner's share of every collect. To pay the wallet that launched the token, send it as feeRecipient; left out, it defaults to the wallet that signs. From a contract, pass the creator address you stored at launch, never msg.sender, tx.origin or address(this). After a test lock, GET /api/lp/lock/{id} shows the feeRecipient the lock was made with.

Automatic locks from your launchpad contract#

The liquidity is locked in HoodLock either way; what differs is who calls the locker. A launchpad whose smart contract calls HoodLock's locker itself, the moment a token graduates say, never asks the API for an intent, so the lock carries no API key. Add that contract under Launchpad contracts in your developer dashboard and every HoodLock liquidity lock it creates from then on is credited to your account, the wallet you signed up with, like a lock made through an intent. Add the contract that calls lock() (the owner in the Locked event). A launchpad that deploys one contract per token adds the factory that deploys them instead, and every lock by a contract that factory created is credited. When your contract calls lock itself, pass acceptUnverifiedHook = true for any V4 pool whose hook is not on HoodLock's allowlist (hookAllowed), your own fee hook included: the safe status above is the API's, and without it the call reverts with "unverified hook". To add a contract or factory, your signed-in wallet must have deployed it directly or be its owner(); anything else is added by us on request. Locks made before it was added are not credited, a contract belongs to one key, and public deployers anyone can use cannot be added as a factory.

The developer dashboard also links a Solidity interface for the locker and a tested example launchpad contract, both checked against the live locker on a fork of Robinhood Chain.

POST /api/dev/attribute#

Credits a wallet to your handle, so that its later locks, burns and vesting schedules earn you commission even when they do not go through your intents. The wallet has to agree by signing a short message, so call it once when the user connects, not on every page load.

FieldTypeNotes
keystringYour API key.
walletaddressThe connected wallet.
tsnumberUnix seconds when the message was signed. Must be within five minutes of the server clock.
signaturestringThe wallet's personal_sign of HoodLock referral <code> <ts>, where code is the value from /api/dev/config. Each signature works once.
shell
curl -X POST https://hoodlock.tech/api/dev/attribute \
  -H "content-type: application/json" \
  -d '{"key":"pk_YOUR_KEY","wallet":"0xUSER","ts":1790000000,"signature":"0x…"}'

Returns {"ok":true}, or {"ok":false,"reason":"…"} with HTTP 200. A refusal is an expected outcome, not an error. An unknown key returns 404 with {"ok":false,"reason":"unknown-key"}.

ReasonMeaning
bad-walletNot a valid address.
unknown-codeThe handle behind the key no longer exists.
already-attributedAnother partner or referral link got there first. First touch wins and is permanent.
no-proofts or signature is missing.
stale-proofts is more than five minutes away from the server clock.
proof-replayedThis signature has already been used. Ask the wallet for a new one.
bad-proofThe signature does not match the wallet and the message.
unverifiedThe wallet's history could not be read from the chain. Try again later.
established-walletThe wallet has already locked, burned or created vesting on-chain.
daily-capThe handle reached its daily safety limit.
Note

Only wallets with no HoodLock history on-chain can be attributed, and only actions after attribution count. That is deliberate: it stops a partner from claiming users who were already here. Actions built through your intents are credited either way.

Rate limits#

EndpointRequests per minute per IP
/api/dev/attribute30
Each *-intent endpoint, counted separately, lp-lock-intent and lp-collect-intent included60
/api/dev/lock-contracts (registering a contract)10

Over the limit returns 429 {"error":"rate limited"}.

A minimal integration#

javascript
const API = "https://hoodlock.tech/api/dev";
const cfg = await (await fetch(`${API}/config?key=${KEY}`)).json();

// after the user connects: one signature, once per wallet
const ts = Math.floor(Date.now() / 1000);
const signature = await walletClient.signMessage({
  account, message: `HoodLock referral ${cfg.code} ${ts}`,
});
await fetch(`${API}/attribute`, {
  method: "POST", headers: { "content-type": "application/json" },
  body: JSON.stringify({ key: KEY, wallet: account, ts, signature }),
});

// 1. approve the token for cfg.locker, 2. lock
const intent = await (await fetch(`${API}/lock-intent`, {
  method: "POST", headers: { "content-type": "application/json" },
  body: JSON.stringify({ key: KEY, token, amount: String(amount), unlockTime: String(unlockTime) }),
})).json();

await walletClient.sendTransaction({
  account, to: intent.to, data: intent.data, value: BigInt(intent.value),
});

Getting paid#

Developers earn 50% of the fee on every action credited to them, whether through an intent or an attributed wallet. A liquidity lock keeps paying: you earn 50% of HoodLock's share every time its owner claims trading fees, for the life of the lock, plus 50% of any fixed fee paid when locking. Referral links from the affiliate program earn 30%. Actions from the wallet that owns the handle do not count. Earnings accrue automatically and are claimed from the developer dashboard with a wallet signature once the claimable balance is at least $10. The API key plays no part in claiming. For the no-code route, see the embed widget page.