Skip to main content
Glama

azeth_get_net_paid

Check payment history between two accounts to determine feedback weight in payment-gated reputation systems. Returns total USD paid or per-token deltas using on-chain queries.

Instructions

Check how much one account has paid another — either total USD or per-token.

Use this when: You want to verify payment history between two accounts, which determines feedback weight in the payment-gated reputation system.

Two modes: • No token (default): Returns total net paid in 18-decimal USD, aggregated across all tokens via the on-chain oracle. Always >= 0. • With token: Returns the signed per-token delta. Positive = "from" paid more, negative = "to" paid more. Use 0x0...0 for native ETH.

"from" defaults to your own address ("me") if omitted. "to" accepts a name, address, or "me".

Note: This is a read-only on-chain query. No private key or gas is required (unless "me" or a name is used for resolution).

Example: { "to": "Alice" } or { "from": "#1", "to": "Bob", "token": "0x036CbD53842c5426634e7929541eC2318f3dCF7e" }

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
chainNoTarget chain. Defaults to AZETH_CHAIN env var or "baseSepolia". Accepts "base", "baseSepolia", "ethereumSepolia", "ethereum" (and aliases like "base-sepolia", "eth-sepolia", "sepolia", "eth", "mainnet").
fromNoPayer address, name, "me", or "#N" (account index). Defaults to "me" (your first smart account).
toYesPayee address, name, "me", or "#N" (account index).
tokenNoToken address for per-token delta. Omit for total USD across all tokens. Use "0x0000000000000000000000000000000000000000" for native ETH.

Implementation Reference

  • Implementation of the azeth_get_net_paid tool, which queries the ReputationModule for net paid amounts between two accounts (total USD or per-token).
    server.registerTool(
      'azeth_get_net_paid',
      {
        description: [
          'Check how much one account has paid another — either total USD or per-token.',
          '',
          'Use this when: You want to verify payment history between two accounts,',
          'which determines feedback weight in the payment-gated reputation system.',
          '',
          'Two modes:',
          '  • No token (default): Returns total net paid in 18-decimal USD, aggregated',
          '    across all tokens via the on-chain oracle. Always >= 0.',
          '  • With token: Returns the signed per-token delta. Positive = "from" paid more,',
          '    negative = "to" paid more. Use 0x0...0 for native ETH.',
          '',
          '"from" defaults to your own address ("me") if omitted. "to" accepts a name, address, or "me".',
          '',
          'Note: This is a read-only on-chain query. No private key or gas is required',
          '(unless "me" or a name is used for resolution).',
          '',
          'Example: { "to": "Alice" } or { "from": "#1", "to": "Bob", "token": "0x036CbD53842c5426634e7929541eC2318f3dCF7e" }',
        ].join('\n'),
        inputSchema: z.object({
          chain: z.string().optional().describe('Target chain. Defaults to AZETH_CHAIN env var or "baseSepolia". Accepts "base", "baseSepolia", "ethereumSepolia", "ethereum" (and aliases like "base-sepolia", "eth-sepolia", "sepolia", "eth", "mainnet").'),
          from: z.string().optional().describe('Payer address, name, "me", or "#N" (account index). Defaults to "me" (your first smart account).'),
          to: z.string().describe('Payee address, name, "me", or "#N" (account index).'),
          token: z.string().regex(/^0x[0-9a-fA-F]{40}$/, 'Must be a valid Ethereum address (0x + 40 hex chars)').optional().describe('Token address for per-token delta. Omit for total USD across all tokens. Use "0x0000000000000000000000000000000000000000" for native ETH.'),
        }),
      },
      async (args) => {
        try {
          const { createPublicClient, http } = await import('viem');
    
          const resolved = resolveChain(args.chain);
          const chain = resolveViemChain(resolved);
          const rpcUrl = process.env[RPC_ENV_KEYS[resolved]] ?? SUPPORTED_CHAINS[resolved].rpcDefault;
    
          const publicClient = createPublicClient({
            chain,
            transport: http(rpcUrl),
          });
    
          const moduleAddress = AZETH_CONTRACTS[resolved].reputationModule;
          if (!moduleAddress || moduleAddress === ('' as `0x${string}`)) {
            return error('NETWORK_ERROR', `ReputationModule not deployed on ${resolved}.`, 'Deploy the ReputationModule first or switch to baseSepolia.');
          }
    
          // Resolve "from" and "to" — support names, "me", "#N"
          const { resolveAddress } = await import('../utils/resolve.js');
          const needsClient = !args.from || !validateAddress(args.from) || !validateAddress(args.to);
          let client;
          if (needsClient) {
            try {
              client = await createClient(resolved);
            } catch {
              // Client creation may fail if no private key; only needed for "me"/name resolution
              if (!args.from || args.from === 'me' || !validateAddress(args.from)) {
                return error('UNAUTHORIZED', '"from" defaults to "me" which requires AZETH_PRIVATE_KEY. Provide an explicit address instead.');
              }
            }
          }
    
          let fromAddr: `0x${string}`;
          let toAddr: `0x${string}`;
          let resolvedFromInfo: string | undefined;
          let resolvedToInfo: string | undefined;
    
          try {
            const fromInput = args.from ?? 'me';
            const fromResult = await resolveAddress(fromInput, client, 'account');
            fromAddr = fromResult.address;
            if (fromResult.resolvedFrom) resolvedFromInfo = `"${fromResult.resolvedFrom}" → ${fromResult.address}`;
    
            const toResult = await resolveAddress(args.to, client, 'account');
            toAddr = toResult.address;
            if (toResult.resolvedFrom) resolvedToInfo = `"${toResult.resolvedFrom}" → ${toResult.address}`;
          } catch (resolveErr) {
            // Fallback: if name resolution fails due to server being down,
            // provide a clear message suggesting explicit addresses.
            if (resolveErr instanceof AzethError && resolveErr.code === 'NETWORK_ERROR') {
              return error(
                'SERVER_UNAVAILABLE',
                `Name resolution failed (server unreachable). Provide explicit addresses instead of names.`,
                'Use azeth_discover_services or azeth_accounts to find addresses, then pass them directly.',
              );
            }
            return handleError(resolveErr);
          } finally {
            try { await client?.destroy(); } catch (e) { process.stderr.write(`[azeth-mcp] destroy error: ${e instanceof Error ? e.message : String(e)}\n`); }
          }
    
          if (args.token) {
            // Per-token mode: signed delta
            const netPaid = await publicClient.readContract({
              address: moduleAddress,
              abi: ReputationModuleAbi,
              functionName: 'getNetPaid',
              args: [fromAddr, toAddr, args.token as `0x${string}`],
            }) as bigint;
    
            // Format per-token amount
            const { formatTokenAmount } = await import('@azeth/common');
            const { TOKENS } = await import('@azeth/common');
            const tokens = TOKENS[resolved];
            const tokenLower = args.token.toLowerCase();
            let netPaidFormatted: string;
            let symbol: string;
            if (tokenLower === tokens.USDC.toLowerCase()) {
              netPaidFormatted = formatTokenAmount(netPaid, 6, 2);
              symbol = 'USDC';
            } else if (tokenLower === tokens.WETH.toLowerCase() || args.token === '0x0000000000000000000000000000000000000000') {
              netPaidFormatted = formatTokenAmount(netPaid, 18, 6);
              symbol = args.token === '0x0000000000000000000000000000000000000000' ? 'ETH' : 'WETH';
            } else {
              netPaidFormatted = netPaid.toString();
              symbol = 'unknown';
            }
    
            return success({
              mode: 'perToken',
              from: fromAddr,
              to: toAddr,
              ...(resolvedFromInfo ? { resolvedFrom: resolvedFromInfo } : {}),
              ...(resolvedToInfo ? { resolvedTo: resolvedToInfo } : {}),
              token: args.token,
              netPaid: netPaid.toString(),
              netPaidFormatted: `${netPaidFormatted} ${symbol}`,
              description: `Signed per-token delta. Positive = "${fromAddr}" paid more.`,
            });
          } else {
            // Total USD mode: aggregated across all tokens
            const totalUSD = await publicClient.readContract({
              address: moduleAddress,
              abi: ReputationModuleAbi,
              functionName: 'getTotalNetPaidUSD',
              args: [fromAddr, toAddr],
            }) as bigint;
    
            const { formatTokenAmount } = await import('@azeth/common');
            const totalNetPaidUSDFormatted = '$' + formatTokenAmount(totalUSD, 18, 2);
    
            return success({
              mode: 'totalUSD',
              from: fromAddr,
              to: toAddr,
              ...(resolvedFromInfo ? { resolvedFrom: resolvedFromInfo } : {}),
              ...(resolvedToInfo ? { resolvedTo: resolvedToInfo } : {}),
              totalNetPaidUSD: totalUSD.toString(),
              totalNetPaidUSDFormatted,
              description: 'Total net paid in 18-decimal USD, aggregated across all tokens.',
            });
          }
        } catch (err) {
          return handleError(err);
        }
      },
    );

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/azeth-protocol/mcp-azeth'

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