Skip to content

feat: add transaction status tool #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,19 @@ Example query to Claude:

> "Buy $20 worth of OpenRouter credits."

### transaction_status

Checks the status and details of a transaction by its hash.

Parameters:

- `txHash`: The hash of the transaction to check
- `chainId`: The ID of the chain on which the transaction was submitted (optional)

Example query to Claude:

> "Check the status of my transaction with hash 0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"

## Security Considerations

- The configuration file contains sensitive information (API keys and seed phrases). Ensure it's properly secured and not shared.
Expand Down
22 changes: 22 additions & 0 deletions examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,28 @@ _Claude uses the `transfer-funds` tool with the following parameters:_

**Claude:** I've initiated the transfer of 0.01 ETH to 0x9876543210abcdef9876543210abcdef98765432. The transaction has been submitted to the blockchain.

### Checking Transaction Status

**You:** Check the status of my transaction with hash 0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890

**Claude:** I'll check the status of that transaction for you.

_Claude uses the `transaction_status` tool with the following parameters:_

- txHash: 0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890

**Claude:** Your transaction has been **confirmed successfully**. Here are the details:

- Status: Success
- Block: #12345678
- From: 0x1234567890abcdef1234567890abcdef12345678
- To: 0x9876543210abcdef9876543210abcdef98765432
- Value: 0.01 ETH
- Gas Used: 21000
- Gas Fee: 0.000441 ETH

You can view more details on [BaseScan](https://basescan.org/tx/0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890).

### Deploying a Smart Contract

**You:** Deploy a simple ERC20 token contract for me.
Expand Down
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { mnemonicToAccount } from 'viem/accounts';
import { base } from 'viem/chains';
import { chainIdToCdpNetworkId, chainIdToChain } from './chains.js';
import { baseMcpTools, toolToHandler } from './tools/index.js';
import { convertBigIntToString } from './tools/utils/transaction.js';
import { version } from './version.js';

async function main() {
Expand Down Expand Up @@ -121,11 +122,14 @@ async function main() {

const result = await tool(viemClient, request.params.arguments);

// Convert any BigInt values to strings for proper JSON serialization
const safeResult = convertBigIntToString(result);

return {
content: [
{
type: 'text',
text: JSON.stringify(result),
text: JSON.stringify(safeResult),
},
],
};
Expand Down
2 changes: 2 additions & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { erc20BalanceTool, erc20TransferTool } from './erc20/index.js';
import { getMorphoVaultsTool } from './morpho/index.js';
import { getOnrampAssetsTool, onrampTool } from './onramp/index.js';
import { buyOpenRouterCreditsTool } from './open-router/index.js';
import { transactionStatusTool } from './transaction-status/index.js';
import type { ToolHandler, ToolWithHandler } from './types.js';

export const baseMcpTools: ToolWithHandler[] = [
Expand All @@ -13,6 +14,7 @@ export const baseMcpTools: ToolWithHandler[] = [
erc20BalanceTool,
erc20TransferTool,
buyOpenRouterCreditsTool,
transactionStatusTool,
];

export const toolToHandler: Record<string, ToolHandler> = baseMcpTools.reduce<
Expand Down
85 changes: 85 additions & 0 deletions src/tools/transaction-status/handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { isHash, type PublicActions, type WalletClient } from 'viem';
import type { z } from 'zod';
import { chainIdToChain } from '../../chains.js';
import type { TransactionStatusResponse } from '../types.js';
import {
calculateConfirmations,
convertBigIntToString,
formatTransactionStatusResponse,
isValidTxHash,
} from '../utils/index.js';
import { TransactionStatusSchema } from './schemas.js';

/**
* Handler for the transaction status tool
* Retrieves the status and details of a transaction
*
* @param wallet The wallet client with public actions
* @param args The validated input arguments
* @returns A JSON string with the transaction status information
*/
export async function transactionStatusHandler(
wallet: WalletClient & PublicActions,
args: z.infer<typeof TransactionStatusSchema>,
): Promise<any> {
const { txHash, chainId } = args;
const chain = chainId ? chainIdToChain(chainId) : wallet.chain;

// Ensure we have a valid chain
if (!chain) {
throw new Error(`Invalid chain ID: ${chainId}`);
}

// Validate transaction hash format
if (!isValidTxHash(txHash)) {
throw new Error(`Invalid transaction hash: ${txHash}`);
}

try {
// Convert txHash to typed 0x-prefixed string
const hash = txHash as `0x${string}`;

// Get transaction details
const transaction = await wallet.getTransaction({ hash });

// Get transaction receipt (will be null for pending transactions)
const receipt = await wallet
.getTransactionReceipt({ hash })
.catch(() => null);

// Get current block number for confirmations
const currentBlock = await wallet.getBlockNumber();

// Basic status determination
let status = 'pending';
if (receipt) {
status = receipt.status === 'success' ? 'success' : 'failed';
}

// Create a manually serialized response with only primitive types
const response = {
hash: txHash,
status: status,
from: transaction?.from,
to: transaction?.to,
blockNumber: receipt?.blockNumber ? String(receipt.blockNumber) : null,
value: transaction?.value ? String(transaction.value) : null,
gasUsed: receipt?.gasUsed ? String(receipt.gasUsed) : null,
nonce:
transaction?.nonce !== undefined ? String(transaction.nonce) : null,
confirmations: receipt?.blockNumber
? String(Number(currentBlock) - Number(receipt.blockNumber))
: null,
explorerUrl: `https://${chain.name === 'baseSepolia' ? 'sepolia.' : ''}basescan.org/tx/${txHash}`,
};

// No need to use JSON.stringify here, return the object directly
return response;
} catch (error) {
// Handle errors and provide useful error messages
if (error instanceof Error) {
throw new Error(`Failed to get transaction status: ${error.message}`);
}
throw new Error(`Failed to get transaction status: Unknown error`);
}
}
14 changes: 14 additions & 0 deletions src/tools/transaction-status/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { generateTool } from '../../utils.js';
import { transactionStatusHandler } from './handlers.js';
import { TransactionStatusSchema } from './schemas.js';

/**
* Transaction Status Tool
* Allows checking the status and details of a transaction by its hash
*/
export const transactionStatusTool = generateTool({
name: 'transaction_status',
description: 'Check the status of a previously submitted transaction',
inputSchema: TransactionStatusSchema,
toolHandler: transactionStatusHandler,
});
15 changes: 15 additions & 0 deletions src/tools/transaction-status/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { z } from 'zod';

/**
* Schema for the transaction status tool
* Validates the input parameters for checking a transaction's status
*/
export const TransactionStatusSchema = z.object({
txHash: z
.string()
.describe('The transaction hash to check'),
chainId: z
.number()
.optional()
.describe('The chain ID (defaults to Base mainnet if not specified)'),
});
65 changes: 65 additions & 0 deletions src/tools/transaction-status/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Tests for the transaction status tool
*
* To run this test, you can use the following command:
* node --loader ts-node/esm src/tools/transaction-status/test.ts
*/

import { fileURLToPath } from 'url';
import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';
import { transactionStatusHandler } from './handlers.js';

// Example transaction hash to test with
const EXAMPLE_TX_HASH =
'0xcf0bb80bfc5859cb33353c488045022811e2d412274e1963c75d83a4b038c22d';

// Mock privateKey - DO NOT use this in production
const PRIVATE_KEY =
'0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef';

async function testTransactionStatus() {
try {
// This is just for testing purposes
const account = privateKeyToAccount(PRIVATE_KEY as `0x${string}`);

// Create a wallet client
const walletClient = createWalletClient({
account,
chain: base,
transport: http('https://base.llamarpc.com'),
});

// Create a public client
const publicClient = createPublicClient({
chain: base,
transport: http('https://base.llamarpc.com'),
});

// Extend wallet client with public methods
const extendedWallet = {
...walletClient,
...publicClient,
};

// Call the transaction status handler
console.log('Testing transaction status handler...');

const result = await transactionStatusHandler(extendedWallet as any, {
txHash: EXAMPLE_TX_HASH,
});

console.log('Result:', JSON.parse(result));
console.log('Test completed successfully!');
} catch (error) {
console.error('Test failed:', error);
}
}

// Run the test (ES module version)
const currentFilePath = fileURLToPath(import.meta.url);
// This checks if this module is the main module
if (process.argv[1] === currentFilePath) {
testTransactionStatus();
}
19 changes: 19 additions & 0 deletions src/tools/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@ export type OpenRouterTransferIntentResponse = {
};
};

/**
* Transaction Status types
*/
export type TransactionStatusResponse = {
hash: string;
status: 'success' | 'failed' | 'pending';
blockNumber?: number;
blockTimestamp?: string;
from: string;
to: string;
value?: string;
gasUsed?: string;
gasFee?: string;
nonce?: number;
confirmations?: number;
explorerUrl: string;
errorMessage?: string;
};

export type ToolHandler = (
wallet: WalletClient & PublicActions,
// TODO: fix
Expand Down
3 changes: 3 additions & 0 deletions src/tools/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ export const checkToolSupportsChain = ({

throw new Error(`Not implemented on ${chainName}`);
};

// Export transaction utilities
export * from './transaction.js';
Loading