Skip to main content
Glama

redeem_reward

Redeem loyalty rewards by verifying on-chain token transfers. Submit transaction details to generate customer vouchers for the Loyal Spark Loyalty Protocol.

Instructions

Redeem a reward by providing a verified token transfer transaction hash. Creates a voucher for the customer.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
reward_idYesUUID of the reward to redeem
customer_addressYesWallet address of the customer who transferred tokens
transaction_hashYesOn-chain tx hash of the token transfer from customer to merchant

Implementation Reference

  • The redeem_reward tool implementation, which verifies a token transfer on the Base network and creates a voucher.
    mcpServer.tool("redeem_reward", {
      description: "Redeem a reward by providing a verified token transfer transaction hash. Creates a voucher for the customer.",
      inputSchema: {
        type: "object" as const,
        properties: {
          reward_id: { type: "string", description: "UUID of the reward to redeem" },
          customer_address: { type: "string", description: "Wallet address of the customer who transferred tokens" },
          transaction_hash: { type: "string", description: "On-chain tx hash of the token transfer from customer to merchant" },
        },
        required: ["reward_id", "customer_address", "transaction_hash"],
      },
      handler: async ({ reward_id, customer_address, transaction_hash }: any) => {
        const err = authGuard(["read"]);
        if (err) return T(err);
        const d = db();
    
        const { data: reward } = await d.from("rewards").select("*").eq("id", reward_id).single();
        if (!reward) return T(JSON.stringify({ error: "Reward not found" }));
        if (reward.merchant_address.toLowerCase() !== agent.ownerAddress.toLowerCase()) return T(JSON.stringify({ error: "Reward not owned by you" }));
        if (!reward.is_active) return T(JSON.stringify({ error: "Reward is inactive" }));
    
        const { data: dup } = await d.from("vouchers").select("id").eq("transaction_hash", transaction_hash).maybeSingle();
        if (dup) return T(JSON.stringify({ error: "Voucher already exists for this transaction" }));
    
        // Verify tx on Base RPC
        const rpcUrl = "https://base-rpc.publicnode.com";
        const txHash = transaction_hash.startsWith("0x") ? transaction_hash : `0x${transaction_hash}`;
        let receipt: any = null;
        for (let i = 0; i < 5; i++) {
          const r = await fetch(rpcUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_getTransactionReceipt", params: [txHash] }) });
          const j = (await r.json()) as any;
          receipt = j?.result;
          if (receipt) break;
          await new Promise(r => setTimeout(r, 2500));
        }
        if (!receipt) return T(JSON.stringify({ error: "Transaction not confirmed yet", retryable: true }));
        if (receipt.status && receipt.status !== "0x1") return T(JSON.stringify({ error: "Transaction failed on-chain" }));
    
        const ERC20 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
        const logs = Array.isArray(receipt.logs) ? receipt.logs : [];
        const ok = logs.some((l: any) => {
          const t = l?.topics || [];
          if ((l?.address || "").toLowerCase() !== reward.token_address.toLowerCase()) return false;
          if (t[0]?.toLowerCase() !== ERC20 || t.length < 3) return false;
          return `0x${t[1].slice(-40)}`.toLowerCase() === customer_address.toLowerCase() && `0x${t[2].slice(-40)}`.toLowerCase() === agent.ownerAddress.toLowerCase();
        });
        if (!ok) return T(JSON.stringify({ error: "Token transfer not verified in tx logs" }));
    
        const { data: prog } = await d.from("loyalty_programs").select("symbol").eq("token_address", reward.token_address.toLowerCase()).maybeSingle();
        const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        const code = "LOYAL-" + Array.from({ length: 4 }, () => Array.from({ length: 4 }, () => chars.charAt(Math.floor(Math.random() * chars.length))).join("")).join("-");
    
        const { data: voucher, error: ve } = await d.from("vouchers").insert({
          code, reward_id: reward.id, reward_name: reward.name, reward_description: reward.description,
          token_address: reward.token_address.toLowerCase(), token_symbol: prog?.symbol || "TOKEN",
          customer_address: customer_address.toLowerCase(), merchant_address: agent.ownerAddress.toLowerCase(),
          status: "active", cost: reward.cost, transaction_hash,
        }).select().single();
        if (ve) return T(JSON.stringify({ error: ve.message }));
    
        return T(JSON.stringify({ voucher: { id: voucher.id, code: voucher.code, reward_name: voucher.reward_name, cost: voucher.cost, status: "active" } }));
      },
    });
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It successfully discloses that the tool 'Creates a voucher' as a side effect and implies validation of the transaction hash. However, it omits critical behavioral details such as error handling for invalid hashes, idempotency guarantees, or whether the voucher creation is synchronous.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description consists of two highly efficient sentences with zero redundancy. It front-loads the core action and mechanism in the first sentence and follows with the outcome/side effect, making it immediately scannable and actionable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the absence of annotations and output schema, the description adequately covers the primary workflow: it explains the redemption mechanism, the required blockchain context, and the voucher creation outcome. It could be improved by describing the return value structure or error conditions, but it provides sufficient context for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, establishing a baseline score of 3. The description adds minimal semantic value beyond the schema, though it reinforces the relationship between parameters by specifying that the transaction_hash must represent a 'verified token transfer.' It does not add syntax details, validation rules, or format examples beyond what the schema provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Redeem a reward'), the required mechanism ('by providing a verified token transfer transaction hash'), and the outcome ('Creates a voucher'). It effectively distinguishes from siblings like 'create_reward' (which creates the reward definition) and 'use_voucher' (which likely consumes vouchers).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage context through the requirement of a 'verified token transfer transaction hash,' suggesting this tool is for post-payment verification scenarios. However, it lacks explicit guidance on when to use this versus 'use_voucher' or what prerequisites must be met before invocation.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aspekt19/unboxed-loyalty-spark'

If you have feedback or need assistance with the MCP directory API, please join our Discord server