Resolver service
Centralized resolver that submits release or refund decisions onchain.
Centralized resolver that submits release or refund decisions onchain.
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { z } from "zod";
import {
createPublicClient,
createWalletClient,
defineChain,
http,
isAddress,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { autoEscrowAbi } from "./abi.js";
const envSchema = z.object({
PORT: z.coerce.number().default(8787),
RPC_URL: z.string().optional(),
CONTRACT_ADDRESS: z.string().optional(),
RESOLVER_PRIVATE_KEY: z.string().optional(),
});
const resolveRequestSchema = z.object({
contractId: z.coerce.bigint(),
decision: z.enum(["release", "refund"]),
resolutionHash: z.string().regex(/^0x[a-fA-F0-9]{64}$/),
});
const env = envSchema.parse(process.env);
const tempo = defineChain({
id: 42161_4217,
name: "Tempo",
nativeCurrency: {
name: "Tempo",
symbol: "TMP",
decimals: 18,
},
rpcUrls: {
default: {
http: env.RPC_URL ? [env.RPC_URL] : ["http://localhost:8545"],
},
},
});
const app = new Hono();
app.get("/", (c) =>
c.json({
service: "auto-contracts-resolver",
status: "ok",
}),
);
app.get("/health", (c) =>
c.json({
status: "ok",
configured: Boolean(
env.RPC_URL && env.CONTRACT_ADDRESS && env.RESOLVER_PRIVATE_KEY,
),
}),
);
app.post("/resolve", async (c) => {
const json = await c.req.json();
const payload = resolveRequestSchema.parse(json);
if (!env.RPC_URL || !env.CONTRACT_ADDRESS || !env.RESOLVER_PRIVATE_KEY) {
return c.json(
{
error:
"Resolver is missing RPC_URL, CONTRACT_ADDRESS, or RESOLVER_PRIVATE_KEY.",
},
400,
);
}
if (!isAddress(env.CONTRACT_ADDRESS)) {
return c.json({ error: "CONTRACT_ADDRESS is invalid." }, 400);
}
const account = privateKeyToAccount(env.RESOLVER_PRIVATE_KEY as `0x${string}`);
const transport = http(env.RPC_URL);
const publicClient = createPublicClient({
chain: tempo,
transport,
});
const walletClient = createWalletClient({
account,
chain: tempo,
transport,
});
const { request } = await publicClient.simulateContract({
address: env.CONTRACT_ADDRESS as `0x${string}`,
abi: autoEscrowAbi,
functionName: "resolve",
args: [
payload.contractId,
payload.decision === "release" ? 1 : 2,
payload.resolutionHash as `0x${string}`,
],
account,
});
const txHash = await walletClient.writeContract(request);
return c.json({
status: "submitted",
txHash,
});
});
serve(
{
fetch: app.fetch,
port: env.PORT,
},
(info: { port: number }) => {
console.log(`resolver listening on http://localhost:${info.port}`);
},
);