A real bank card stamped a wallet
0xd7e0…bc07
Made under our first card list, which also allowed prepaid cards.
Watermark lets only wallets backed by a real bank card claim. Add it with one line. No KYC. Your users pay no fees.
Airdrop claimsExample
Only stamped wallets can claim.
How it works
In your contract, your backend or your AI agent.
On our phone app. A private proof checks the card is real. No card number is stored.
Bots get refused before they touch your tokens.
Add it
Check a stamp from Solidity, TypeScript or your AI agent. No API key.
Any contract can check a stamp in one call. A wallet with no live stamp gets refused.
function claim()external onlyStamped { … }
Paste once, from docs/INTEGRATE.md
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");
_;
}Check one wallet, or up to 200 in one call. It reads the chain directly, with no API key and no server of ours.
The SDK lives in the repo (sdk/). It isn't on npm yet.
import { check, checkMany } from "@watermark/sdk";
const registry = "0x998934Efa31a6bE75945fDfAa126d3D6401F8Fd6";
const appId = "393344891601835728760966937624776276735512086066329273346256164761263100630";
const { stamped, expiresAt } = await check(userAddress, appId, { registry });
if (!stamped) showGetStampedButton(); // links to the /verify page (verifyLink)
// Airdrop or allowlist export: up to 200 wallets per call, one multicall
const results = await checkMany(addresses, appId, { registry });Your agent checks a wallet before paying a reward or giving access, audits an app, or drafts a card list. It holds no keys and never takes card data.
{
"mcpServers": {
"watermark": {
"command": "node",
"args": ["/absolute/path/to/watermark/mcp/src/index.ts"],
"env": { "WHITECHAIN_RPC": "https://rpc.ankr.com/whitechain_testnet" }
}
}
}12 tools, each named watermark_…
watermark_check_walletwatermark_check_walletswatermark_stamp_historywatermark_app_infowatermark_get_policywatermark_explain_policywatermark_list_appswatermark_supported_cardswatermark_draft_policywatermark_prepare_register_appwatermark_create_verify_linkwatermark_integration_snippetRead the full guide (opens in a new tab)MCP setup (opens in a new tab)
18740x998934Efa31a6bE75945fDfAa126d3D6401F8Fd6VerifiedExplorer (opens in a new tab)39334489160183572876096693762477627673551208606632927334625616476126310063012335610For AI agents
Copy this prompt into Claude Code, Cursor or any coding agent. It reads our docs and adds the bot check to your app.
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 claim our airdrops, mints and rewards.
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 pays out: airdrop claims, mints, rewards, 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.Try to break it
Press the button. We make 5 bot wallets and send them at our own drop. Every one gets refused. These are real requests.
Illustration
We ask the chain. Your app would get the same answer.
Read-only. Nothing is signed or sent.
You stay in control
Use our live card list, or register your own app with your own. Every list is public, and fixed for its app.
4,208 RuPay bank debit ranges from India. No prepaid cards, because anyone can buy many.
They pay $0, up to 20 payments a day each.
One card, one wallet per app.
No KYC files, no card numbers.
Visa and MastercardComing soon
Public records · Test networks
Every step leaves a public record. Tap any one to check it.
The fine print
You have to trust us on the wipe. Proving on the phone is the real fix. It's on our roadmap.