import { z } from "zod"
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import type { ChainlinkApiClient } from "../utils/api-client.js"
import { validateNetwork, validateSubscriptionId } from "../utils/validators.js"
import { formatNetworkName, formatTimestamp } from "../utils/formatters.js"
export function registerVrfTools(server: McpServer, apiClient: ChainlinkApiClient) {
// Tool: Request Random Words
server.tool(
"request_random_words",
"Request cryptographically secure random numbers from Chainlink VRF. Returns a request ID for tracking.",
{
numWords: z.number().min(1).max(500).describe("Number of random words to request"),
subscriptionId: z.string().describe("VRF subscription ID"),
keyHash: z.string().describe("VRF key hash identifying the gas lane"),
callbackGasLimit: z.number().min(20000).max(2500000).describe("Gas limit for callback"),
requestConfirmations: z.number().min(3).max(200).default(3).describe("Number of block confirmations"),
network: z.string().optional().describe("Blockchain network (default: ethereum)")
},
async ({ numWords, subscriptionId, keyHash, callbackGasLimit, requestConfirmations = 3, network = "ethereum" }) => {
try {
const networkValidation = validateNetwork(network)
if (!networkValidation.valid) {
throw new Error(networkValidation.error)
}
const subIdValidation = validateSubscriptionId(subscriptionId)
if (!subIdValidation.valid) {
throw new Error(subIdValidation.error)
}
// Mock VRF request
const requestId = `0x${Math.random().toString(16).slice(2).padStart(64, '0')}`
const result = {
requestId,
numWords,
subscriptionId,
keyHash,
callbackGasLimit,
requestConfirmations,
network: formatNetworkName(network),
status: "pending",
requestedAt: formatTimestamp(Date.now() / 1000),
estimatedFulfillmentTime: `${requestConfirmations * 15} seconds`,
estimatedCost: "0.25 LINK"
}
return {
content: [
{
type: "text",
text: `VRF random words request submitted successfully\n\n` +
`Request ID: ${requestId}\n` +
`Number of Words: ${numWords}\n` +
`Network: ${result.network}\n` +
`Status: ${result.status}\n` +
`Estimated fulfillment: ${result.estimatedFulfillmentTime}\n\n` +
`Full Details:\n${JSON.stringify(result, null, 2)}`
}
]
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error requesting random words: ${error instanceof Error ? error.message : String(error)}`
}
]
}
}
}
)
// Tool: Get Random Result
server.tool(
"get_random_result",
"Get the random numbers for a completed VRF request. Returns the randomness along with fulfillment details.",
{
requestId: z.string().describe("VRF request ID"),
network: z.string().optional().describe("Blockchain network")
},
async ({ requestId, network = "ethereum" }) => {
try {
const networkValidation = validateNetwork(network)
if (!networkValidation.valid) {
throw new Error(networkValidation.error)
}
// Mock result checking
const isFulfilled = Math.random() > 0.3 // 70% chance fulfilled
if (!isFulfilled) {
return {
content: [
{
type: "text",
text: `VRF request is still pending. Request ID: ${requestId}\n\n` +
`Status: pending\n` +
`Network: ${formatNetworkName(network)}\n` +
`Please check again in a few moments.`
}
]
}
}
// Mock random words generation
const numWords = Math.floor(Math.random() * 5) + 1
const randomWords = Array.from({ length: numWords }, () =>
Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)
)
const result = {
requestId,
network: formatNetworkName(network),
status: "fulfilled",
randomWords,
numWords: randomWords.length,
fulfilledAt: formatTimestamp(Date.now() / 1000),
blockNumber: Math.floor(Math.random() * 1000000) + 18000000,
gasUsed: "150000",
actualCost: "0.23 LINK"
}
return {
content: [
{
type: "text",
text: `VRF request fulfilled successfully\n\n` +
`Request ID: ${requestId}\n` +
`Network: ${result.network}\n` +
`Random Words: ${randomWords.join(", ")}\n` +
`Fulfilled At: ${result.fulfilledAt}\n` +
`Cost: ${result.actualCost}\n\n` +
`Full Details:\n${JSON.stringify(result, null, 2)}`
}
]
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting random result: ${error instanceof Error ? error.message : String(error)}`
}
]
}
}
}
)
// Tool: Verify Randomness
server.tool(
"verify_randomness",
"Verify the cryptographic proof of a VRF result. Ensures the randomness is genuine and tamper-proof.",
{
requestId: z.string().describe("VRF request ID to verify"),
randomWords: z.array(z.number()).describe("Random words to verify"),
proof: z.string().optional().describe("VRF proof (optional, will be fetched if not provided)"),
network: z.string().optional().describe("Blockchain network")
},
async ({ requestId, randomWords, proof, network = "ethereum" }) => {
try {
const networkValidation = validateNetwork(network)
if (!networkValidation.valid) {
throw new Error(networkValidation.error)
}
// Mock verification
const isValid = Math.random() > 0.05 // 95% valid rate
const result = {
requestId,
network: formatNetworkName(network),
randomWords,
isValid,
verificationStatus: isValid ? "verified" : "invalid",
proofHash: proof || `0x${Math.random().toString(16).slice(2).padStart(64, '0')}`,
verifiedAt: formatTimestamp(Date.now() / 1000),
cryptographicStrength: isValid ? "256-bit" : "invalid",
tamperProof: isValid
}
return {
content: [
{
type: "text",
text: `VRF randomness verification ${isValid ? 'successful' : 'failed'}\n\n` +
`Request ID: ${requestId}\n` +
`Network: ${result.network}\n` +
`Status: ${result.verificationStatus}\n` +
`Tamper Proof: ${result.tamperProof}\n` +
`Cryptographic Strength: ${result.cryptographicStrength}\n\n` +
`Random Words: ${randomWords.join(", ")}\n\n` +
`Full Details:\n${JSON.stringify(result, null, 2)}`
}
]
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error verifying randomness: ${error instanceof Error ? error.message : String(error)}`
}
]
}
}
}
)
// Tool: Get VRF Configuration
server.tool(
"get_vrf_config",
"Get VRF configuration details for a network including key hashes, gas lanes, and pricing.",
{
network: z.string().optional().describe("Blockchain network (default: ethereum)")
},
async ({ network = "ethereum" }) => {
try {
const networkValidation = validateNetwork(network)
if (!networkValidation.valid) {
throw new Error(networkValidation.error)
}
// Mock VRF configuration
const config = {
network: formatNetworkName(network),
coordinatorAddress: "0x271682DEB8C4E0901D1a1550aD2e64D568E69909",
linkTokenAddress: "0x514910771AF9Ca656af840dff83E8264EcF986CA",
keyHashes: [
{
name: "30 gwei",
hash: "0x8af398995b04c28e9951adb9721ef74c74f93e6a478f39e7e0777be13527e7ef",
gasLane: "30 gwei",
maxGasPrice: "30000000000"
},
{
name: "150 gwei",
hash: "0xff8dedfbfa60af186cf3c830acbc32c05aae823045ae5ea7da1e45fbfaba4f92",
gasLane: "150 gwei",
maxGasPrice: "150000000000"
}
],
minimumRequestConfirmations: 3,
maximumRequestConfirmations: 200,
minimumWords: 1,
maximumWords: 500,
callbackGasLimit: {
minimum: 20000,
maximum: 2500000
},
pricing: {
premium: "0.25 LINK",
flatFee: "0.0005 LINK"
}
}
return {
content: [
{
type: "text",
text: `VRF configuration for ${formatNetworkName(network)}\n\n` +
`Coordinator: ${config.coordinatorAddress}\n` +
`LINK Token: ${config.linkTokenAddress}\n` +
`Available Gas Lanes: ${config.keyHashes.length}\n` +
`Premium: ${config.pricing.premium}\n\n` +
`Full Configuration:\n${JSON.stringify(config, null, 2)}`
}
]
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting VRF configuration: ${error instanceof Error ? error.message : String(error)}`
}
]
}
}
}
)
// Tool: Estimate VRF Cost
server.tool(
"estimate_vrf_cost",
"Estimate the total cost for a VRF request including LINK premium and gas fees.",
{
numWords: z.number().min(1).max(500).describe("Number of random words"),
callbackGasLimit: z.number().min(20000).max(2500000).describe("Gas limit for callback"),
gasPrice: z.number().optional().describe("Gas price in gwei (optional)"),
network: z.string().optional().describe("Blockchain network")
},
async ({ numWords, callbackGasLimit, gasPrice, network = "ethereum" }) => {
try {
const networkValidation = validateNetwork(network)
if (!networkValidation.valid) {
throw new Error(networkValidation.error)
}
// Mock cost calculation
const basePremium = 0.25 // LINK
const perWordCost = 0.01 // LINK per word
const gasCostInLink = ((gasPrice || 30) * callbackGasLimit) / 1e9 * 0.0001 // rough conversion
const totalCost = basePremium + (numWords * perWordCost) + gasCostInLink
const result = {
network: formatNetworkName(network),
numWords,
callbackGasLimit,
gasPrice: gasPrice || 30,
costBreakdown: {
basePremium: `${basePremium} LINK`,
perWordCost: `${(numWords * perWordCost).toFixed(4)} LINK`,
gasCost: `${gasCostInLink.toFixed(4)} LINK`,
totalCost: `${totalCost.toFixed(4)} LINK`
},
estimatedUsdCost: `$${(totalCost * 15).toFixed(2)}`, // Assuming $15/LINK
recommendations: {
gasLane: gasPrice && gasPrice > 100 ? "150 gwei" : "30 gwei",
optimizations: numWords > 10 ? ["Consider requesting fewer words if possible"] : []
}
}
return {
content: [
{
type: "text",
text: `VRF cost estimation\n\n` +
`Total Cost: ${result.costBreakdown.totalCost}\n` +
`USD Estimate: ${result.estimatedUsdCost}\n` +
`Network: ${result.network}\n` +
`Recommended Gas Lane: ${result.recommendations.gasLane}\n\n` +
`Cost Breakdown:\n` +
`- Base Premium: ${result.costBreakdown.basePremium}\n` +
`- Words (${numWords}): ${result.costBreakdown.perWordCost}\n` +
`- Gas Cost: ${result.costBreakdown.gasCost}\n\n` +
`Full Details:\n${JSON.stringify(result, null, 2)}`
}
]
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error estimating VRF cost: ${error instanceof Error ? error.message : String(error)}`
}
]
}
}
}
)
}