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
| Name | Required | Description | Default |
|---|---|---|---|
| chain | No | 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 | No | Payer address, name, "me", or "#N" (account index). Defaults to "me" (your first smart account). | |
| to | Yes | Payee address, name, "me", or "#N" (account index). | |
| token | No | Token address for per-token delta. Omit for total USD across all tokens. Use "0x0000000000000000000000000000000000000000" for native ETH. |
Implementation Reference
- src/tools/reputation.ts:246-402 (handler)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); } }, );