Skip to content
Watermark
Menu
Bot protection for any onchain app

Keep bots out of your app.

Your users tap their bank card on their phone once, and their wallet gets a stamp. Bots can make endless wallets, but they can't make bank cards, so they never get in. No KYC, and no gas fees for your users.

Who gets inExample

Only wallets with a stamp get through.

  1. 0x9c1e…41abRefused · no stamp
  2. 0x4d07…e2f0Refused · no stamp
  3. 0x71aa…09c3Refused · no stamp
  4. 0xb3f2…7d15Claimed #042 · stamped wallet
Genesis Drop stamp #042 of 100: a Mumbai postage stamp of the Bandra–Worli Sea Link

What it stops

Anywhere bots cost you money.

A bot can make a thousand wallets in a minute, but it can't make a thousand bank cards. That one fact protects all of these.

  • Airdrops and mints

    Each real card gets one claim, so someone with 1,000 wallets gets nothing extra.

  • Gas you sponsor

    You only pay gas for stamped wallets, so bots can't burn through your budget.

  • Faucets

    Free test tokens go to people with real cards, not to scripts.

  • Rewards and referrals

    Points, quests and referral bonuses go to real users instead of bot farms.

  • Votes and grants

    Every voter needs a real card, so faking a crowd gets expensive fast.

  • AI agents

    Your AI agent can check a wallet before it sends money or gives access.

Behind all of them is the same one-line check: isStamped(wallet, appId)

Three steps

How it works.

  1. Step 1

    You add one line

    Put one check in your contract, your server or your AI agent.

  2. Step 2

    Users tap their card

    They tap their bank card on our Android app once, and a private proof checks that it's real.

  3. Step 3

    Bots get refused

    No real card means no stamp, and no stamp means no way in.

Add it

Add it in one line.

Use Solidity, TypeScript or your AI agent. There's no API key and nothing to sign up for.

Add one modifier to your contract, and wallets without a stamp get turned away.

Your contract: the one line

function claim()external onlyStamped { … }

Paste once, from docs/INTEGRATE.md

Solidity · onlyStamped
interface IWatermarkRegistry {
    function isStamped(address wallet, uint256 appId) external view returns (bool ok, uint64 expiresAt);
}

modifier onlyStamped() {
    (bool ok, uint64 expiresAt) = watermark.isStamped(msg.sender, watermarkAppId);
    require(ok && expiresAt > block.timestamp, "Watermark: no live stamp");
    _;
}
Chain IDWhitechain Sepolia
1874
RegistryWatermarkRegistry
0x998934Efa31a6bE75945fDfAa126d3D6401F8Fd6VerifiedExplorer (opens in a new tab)
Live appIdRuPay bank debit (India, no prepaid)
393344891601835728760966937624776276735512086066329273346256164761263100630
Deploy blockEvent history starts here
12335610

For AI agents

Let your AI agent add it.

Copy this prompt into Claude Code, Cursor or any coding agent. It reads our docs and adds the bot check to your app.

Prompt
Add Watermark anti-bot gating to this project. A Watermark stamp means a wallet was linked to one real bank card. Only stamped wallets may get what this app gives away or protects: airdrops, mints, rewards, faucet tokens, sponsored gas, votes or access.

1. Read https://usewatermark.xyz/watermark.txt first. Use only the addresses, functions and tool names it gives. Don't invent any.

2. Find every place this repo gives something away or lets a wallet in: airdrop claims, mints, rewards, faucets, sponsored gas, votes, allowlists. Check whether they live in Solidity contracts, a TypeScript/JavaScript backend, or both.

3. App id: <YOUR_APP_ID> below is a placeholder; keep it in one config constant. For real RuPay bank cards, set it to the live app id: 393344891601835728760966937624776276735512086066329273346256164761263100630. Register our own app only if we want our own card list: draft it with watermark_draft_policy, then watermark_prepare_register_app (unsigned; I send it, and we must run our own session signer). Tell me which you chose and why.

4. Solidity: contracts on Whitechain Sepolia (chain 1874) can gate directly. Add the IWatermarkRegistry interface and the onlyStamped modifier from the docs, with the registry 0x998934Efa31a6bE75945fDfAa126d3D6401F8Fd6 and <YOUR_APP_ID>, and put onlyStamped on each claim function. A contract on another chain can't read the registry: check in the backend instead.

5. Backend: before paying out, call check() from @watermark/sdk, or the plain viem isStamped read in the docs (the SDK isn't on npm yet). Refuse when stamped is false. Never trust a stamp flag sent by the browser.

6. For users who aren't stamped, show a "Get verified with Watermark" link to https://usewatermark.xyz/verify?app=<YOUR_APP_ID> (what watermark_create_verify_link returns). Tell them to verify the same wallet they claim with.

7. Never ask for, store or log card numbers or any card data. We only need the wallet address.

8. Keep every existing test passing. Add a test showing an unstamped wallet is refused (mock the registry or the check). Add no keys or secrets; reading a stamp needs none.

Finish with a short summary: files changed, where each check sits, the app id and why, how to run the new test, and anything left for me to do.

Read the full docs: watermark.txt (opens in a new tab)

Try to break it

Try to farm it.

Press the button and we'll send 5 bot wallets at our own drop. You'll watch every one of them get turned away, and these are real requests.

Illustration

Is this wallet stamped?

We ask the chain. Your app would get the same answer.

App

Live app: RuPay bank debit cards from India.

Read-only. Nothing is signed or sent.

You stay in control

You decide who counts.

You can use our card list or bring your own. Either way, the list is public.

  • Which cards count

    4,208 RuPay bank debit ranges from India. No prepaid cards, because anyone can buy many.

    • Classic
    • Platinum
    • PMJDY (Jan Dhan)
    • RuPay Select
    • Standard
  • Sponsor your users' fees

    They pay $0, up to 20 payments a day each.

    Fees your users pay
    $0
    Free payments per wallet, per day
    20
  • Stamps last up to 3 months

    One card, one wallet per app.

  • Nothing to store

    You never hold KYC files or card numbers.

Public records · Test networks

See it yourself.

Everything below happened on a public chain. Tap any card to check it yourself.

Privacy and limits

The honest part.

  • What we see

    Your card data, just long enough to make the proof. Then we wipe it.

  • What we keep

    Nothing from your card.

  • What goes on-chain

    A proof, a one-way card ID, your wallet and an end date.

  • For now, you have to trust us to wipe it. Making the proof on the phone itself fixes that, and it's next on our list.

    Read our privacy notes (opens in a new tab)
  • Good to know

    • Right now, only RuPay debit cards from India work.
    • Everything runs on test networks for now.
    • One card works for one wallet per app, and it can't be reset.
    • A stamp isn't KYC. Someone with two cards can get two stamps.
    • There's no PIN, so a stamp shows someone had a real card, not who they are.