{"id":68882,"plugin_id":"plugin_asdk_app_6ac02f0983b0819189bdf9074149a9bb","kind":"skill","collection_source":"plugin_package","comparison_source":null,"observed_at":"2026-10-28T18:06:14.813Z","digest":"66d260fb5137f1365fed3411688ce43b3c1127b9f98d3464951ba5520aad38f2","against":null,"payload":{"description":"Token-holder discounts at InsumerAPI merchants: read a store's terms, check what a wallet qualifies for (free), optionally prove control of the wallet by signing an EIP-4361 message, and create a signed single-use discount code (INSR-XXXXX). Use when the user or agent wants a holder discount at a store in the InsumerAPI merchant directory, wants to know whether signing with the wallet gets a bigger discount, or needs a discount in ACP or UCP checkout format.\n","included_files":[{"relative_path":"scripts/discount.py","size_in_bytes":3754},{"relative_path":"scripts/proof_message.py","size_in_bytes":2115}],"name":"insumer-discount","skill_md_contents":"---\nname: insumer-discount\ndescription: >\n  Token-holder discounts at InsumerAPI merchants: read a store's terms, check\n  what a wallet qualifies for (free), optionally prove control of the wallet\n  by signing an EIP-4361 message, and create a signed single-use discount code\n  (INSR-XXXXX). Use when the user or agent wants a holder discount at a store\n  in the InsumerAPI merchant directory, wants to know whether signing with the\n  wallet gets a bigger discount, or needs a discount in ACP or UCP checkout\n  format.\nmetadata:\n  version: \"0.1.1\"\n  author: InsumerAPI\n---\n\n# InsumerAPI Token-Holder Discounts\n\nA store lists the tokens and NFTs it recognizes and the discount each earns. InsumerAPI reads the wallet, works out the discount, and issues a signed, single-use code (`INSR-XXXXX`, valid 30 minutes) that the store redeems. The store sees the code and the percentage, never the wallet or its balances.\n\n**Two routes, the store sets the terms.**\n\n- **Proven wallet:** the caller sends `walletProof`, a message signed by the wallet. The wallet gets the full discount its holdings earn (under the store's tiers and cap), without the unproven-wallet daily limit.\n- **Unproven wallet:** no proof. The store decides what that gets: the same discount (the default), up to a lower percentage, or nothing, and optionally a daily limit per wallet.\n\nRead the terms first, then decide whether signing is worth it.\n\n## Setup\n\nGet the API key from the `insumer-auth` skill, then:\n\n```bash\nexport INSUMER_API_KEY='insr_live_...'\n```\n\nA code that carries a discount uses one credit from the API key that owns the store. A caller using another key is not charged; when the caller's key owns the store, the caller's key pays. A 0% result is free.\n\n## Reference values (do not hallucinate)\n\n- **API base**: `https://api.insumermodel.com`\n- **Store terms**: `GET /v1/merchants/{merchantId}` → `data.walletTerms` (no key needed). The free check returns the same `walletTerms`, and also answers for a store that is not listed in the public directory\n- **Free check**: `GET /v1/discount/check?merchant={merchantId}&wallet={0x...}` (no key needed)\n- **Create a code**: `POST /v1/verify` with `X-API-Key` (also `POST /v1/acp/discount` and `POST /v1/ucp/discount` for checkout formats)\n- **Validate a code**: `GET /v1/codes/{code}` (no key needed)\n- **Proof message domain**: `api.insumermodel.com`\n- **Proof message URI**: `https://api.insumermodel.com/v1/merchants/{merchantId}` (exactly this merchant)\n- **Proof freshness**: signed within the last 5 minutes; every nonce used once\n- **Proof wallets**: EVM wallets that sign with `personal_sign` (EIP-191). Smart-contract wallets are not accepted yet. Solana and XRPL wallets can still get the unproven terms.\n\n## Usage\n\n### Step 1: read the store's terms (free)\n\n```bash\ncurl -s https://api.insumermodel.com/v1/merchants/acme-store\n```\n\n```json\n\"walletTerms\": {\n  \"proofAccepted\": [\"evm\"],\n  \"proven\":   { \"maxDiscountsPerWalletPerDay\": null },\n  \"unproven\": { \"maxDiscount\": 5, \"maxDiscountsPerWalletPerDay\": 10 }\n}\n```\n\n`unproven.maxDiscount`: `null` = the same as proven, `0` = no discount without proof, otherwise at most that percent. `unproven.maxDiscountsPerWalletPerDay`: `null` = no limit.\n\n### Step 2: check what the wallet qualifies for (free)\n\n```bash\ncurl -s \"https://api.insumermodel.com/v1/discount/check?merchant=acme-store&wallet=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045\"\n```\n\n`totalDiscount` is what the wallet gets **without** proof. If `discountIfProven` is present, signing gets that instead. If it is absent, signing changes the discount by nothing (it still lifts the daily limit).\n\n### Step 3 (optional): prove the wallet\n\nSign this exact message with the wallet, using `personal_sign`:\n\n```\napi.insumermodel.com wants you to sign in with your Ethereum account:\n0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045\n\nProve wallet for a discount.\n\nURI: https://api.insumermodel.com/v1/merchants/acme-store\nVersion: 1\nChain ID: 1\nNonce: k3j9x2m8q1\nIssued At: 2026-10-05T18:00:00.000Z\n```\n\nLine 2 is the wallet (checksummed or lowercase), the URI names the merchant, the nonce is fresh random letters and digits (8 or more), and `Issued At` is the current time in ISO 8601. `scripts/proof_message.py` builds it for you.\n\n### Step 4: create the code\n\n```bash\ncurl -X POST https://api.insumermodel.com/v1/verify \\\n  -H \"X-API-Key: $INSUMER_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"merchantId\": \"acme-store\",\n    \"wallet\": \"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045\",\n    \"walletProof\": { \"message\": \"<the message, exactly as signed>\", \"signature\": \"0x...\" }\n  }'\n```\n\nLeave out `walletProof` for the unproven route. The response carries `code`, `totalDiscount`, `walletProven`, and `discountIfProven` when proof would have got more. `GET /v1/codes/{code}` reports the code's percentage, expiry and `walletProven`.\n\n## Code emission rules\n\nWhen emitting integration code for this flow, the agent MUST:\n\n1. **Read the terms or run the free check before creating a code.** Creating a code that carries a discount spends a credit from the API key that owns the store; a 0% result is free.\n2. **Never hold the user's private key to sign for them.** The wallet's owner signs, in their own wallet. An agent signs only with a wallet it controls itself.\n3. **Build a fresh message for every request.** A nonce is accepted once, and a message older than 5 minutes is refused.\n4. **Send the proof with `wallet` only.** A proof sent with `solanaWallet` or `xrplWallet` is a `400`.\n5. **Treat a failed proof as a failed proof.** A `401` on a request with `walletProof` means the proof was refused (wrong wallet, merchant, domain, expired, nonce reused or bad signature); no credit was used. Fix and re-sign, or retry without proof to take the unproven terms. It never silently falls back.\n6. **Read the API key from an env var**, and call from a backend.\n\n## Helper scripts\n\n`scripts/proof_message.py`: prints the EIP-4361 message for a wallet and merchant, with a fresh nonce and the current time. It never signs and never handles a private key: the wallet signs the printed message itself, and you save `{ \"message\": ..., \"signature\": ... }` as the proof file.\n\n```bash\npython scripts/proof_message.py --merchant acme-store --wallet 0xd8dA...6045\n```\n\n`scripts/discount.py`: reads the terms, runs the free check, and with `--create` creates the code (adding `--proof-file proof.json` for the proven route).\n\n```bash\npython scripts/discount.py --merchant acme-store --wallet 0xd8dA...6045\npython scripts/discount.py --merchant acme-store --wallet 0xd8dA...6045 --create --proof-file proof.json\n```\n\n## Error handling\n\n| Status | Cause | Fix |\n|---|---|---|\n| `400` | Missing or invalid field; proof sent with a non-EVM wallet | Check the request body |\n| `401` | Missing API key, or a `walletProof` that was refused | See `insumer-auth`; re-sign a fresh message |\n| `402` | The API key that owns the store has no credits left | If the store is the caller's own, top up (Path 4 in `insumer-auth`). Otherwise nothing the caller can do; tell the user |\n| `403` | The store has not enabled app access, or has no active owner key | Nothing the caller can do |\n| `404` | Merchant not found. `GET /v1/merchants/{merchantId}` also answers `404` for a store that is paused or not listed in the public directory | Check the merchant ID, then run the free check, which answers for those stores |\n| `409` | The store's recognition is paused by its owner | No code is created and no credit is used; tell the user |\n| `429` | The key's daily limit; the store's daily limit for an unproven wallet; or the hourly limit on discount codes requested with a key other than the store owner's | For the store's daily limit, prove the wallet (the limit does not apply to a proven wallet) or try after 00:00 UTC. For the hourly limit, wait the time the message gives: proving the wallet does not lift it |\n| `503` | A read did not complete (`rpc_failure`) | Retry after a short delay; no credit used. With a `walletProof`, sign a fresh message first: the nonce in the refused request has been used |\n\nA `503` with `rpc_failure` is never \"not eligible\". Retry the call, with a freshly signed message if the request carried a `walletProof`.\n\n## Related skills\n\n| Skill | Purpose |\n|---|---|\n| `insumer-auth` | Get a free API key, configure env vars |\n| `insumer-jwks-verify` | Verify signed responses offline |\n| `insumer-attest` | Signed yes/no on any wallet condition |\n\n## References\n\n- [InsumerAPI OpenAPI spec](https://insumermodel.com/openapi.yaml)\n- [EIP-4361: Sign-In with Ethereum](https://eips.ethereum.org/EIPS/eip-4361)\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}