refund_htlc
Recover locked funds after HTLC timelock expires when the swap fails to complete. Only the original sender can refund with no counterparty risk.
Instructions
Trustless unwind — recover locked funds after the HTLC timelock expires. Non-custodial refund guarantee: if the swap does not complete, the original sender reclaims their asset with zero counterparty risk.
USE WHEN: the timelock deadline has passed AND the counterparty never locked their side (or the swap otherwise failed to complete). DO NOT USE WHEN: counterparty HAS locked and the swap can still complete — use withdraw_htlc instead. Only the original lock sender can call refund, and only after the deadline.
PARAM NOTES: txHash is the on-chain refund tx hash (0x-prefixed). No preimage needed — expiry alone unlocks the refund path. Set chainType to "bitcoin" or "sui" for non-EVM legs.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| tradeId | Yes | Trade ID | |
| txHash | Yes | On-chain refund transaction hash (0x-prefixed) | |
| chainType | No | Chain type: evm, bitcoin, or sui | |
| client_request_id | No | Idempotency key. Retrying the SAME write with the SAME id within this MCP session returns the first result instead of triggering a second on-chain/backend side effect. Best-effort: not durable across MCP restarts. |
Implementation Reference
- src/index.ts:99-121 (registration)MCP tool registration for 'refund_htlc'. Defines the tool name, description (trustless unwind after HTLC timelock expiry), input schema (tradeId, txHash, chainType, client_request_id), and handler via wrapTool.
server.tool( 'refund_htlc', [ 'Trustless unwind — recover locked funds after the HTLC timelock expires. Non-custodial refund guarantee: if the swap does not complete, the original sender reclaims their asset with zero counterparty risk.', '', 'USE WHEN: the timelock deadline has passed AND the counterparty never locked their side (or the swap otherwise failed to complete).', 'DO NOT USE WHEN: counterparty HAS locked and the swap can still complete — use withdraw_htlc instead. Only the original lock sender can call refund, and only after the deadline.', '', 'PARAM NOTES: `txHash` is the on-chain refund tx hash (0x-prefixed). No preimage needed — expiry alone unlocks the refund path. Set `chainType` to "bitcoin" or "sui" for non-EVM legs.', ].join('\n'), { tradeId: z.string().describe('Trade ID'), txHash: z.string().describe('On-chain refund transaction hash (0x-prefixed)'), chainType: z.string().optional().describe('Chain type: evm, bitcoin, or sui'), client_request_id: z.string().optional().describe('Idempotency key. Retrying the SAME write with the SAME id within this MCP session returns the first result instead of triggering a second on-chain/backend side effect. Best-effort: not durable across MCP restarts.'), }, wrapTool(async ({ tradeId, txHash, chainType, client_request_id }) => { const input = { tradeId, txHash, chainType }; const result = await idempotency.remember(idempotencyKey('refund_htlc', client_request_id, input), () => hl.refundHTLC(input)); return okContent(result); }), ); - src/index.ts:115-120 (handler)The actual handler function for refund_htlc. Constructs input (tradeId, txHash, chainType), uses idempotency guard with key 'refund_htlc', and delegates to hl.refundHTLC(input) from the @hashlock-tech/sdk.
wrapTool(async ({ tradeId, txHash, chainType, client_request_id }) => { const input = { tradeId, txHash, chainType }; const result = await idempotency.remember(idempotencyKey('refund_htlc', client_request_id, input), () => hl.refundHTLC(input)); return okContent(result); }), - src/index.ts:109-114 (schema)Zod input schema for refund_htlc: tradeId (string), txHash (string), chainType (optional string: evm/bitcoin/sui), client_request_id (optional string for idempotency).
{ tradeId: z.string().describe('Trade ID'), txHash: z.string().describe('On-chain refund transaction hash (0x-prefixed)'), chainType: z.string().optional().describe('Chain type: evm, bitcoin, or sui'), client_request_id: z.string().optional().describe('Idempotency key. Retrying the SAME write with the SAME id within this MCP session returns the first result instead of triggering a second on-chain/backend side effect. Best-effort: not durable across MCP restarts.'), }, - src/lib/idempotency.ts:40-47 (helper)Helper function idempotencyKey used to generate a dedup key scoped by operation name ('refund_htlc'), client_request_id, and payload JSON.
/** Compose a cache key scoped by operation + exact payload so the same * client_request_id reused for a different tool or a different payload * does NOT replay an unrelated result. Returns undefined when no id * (=> no dedup, always run). */ export function idempotencyKey(scope: string, clientRequestId: string | undefined, payload: unknown): string | undefined { if (!clientRequestId) return undefined; return `${scope}:${clientRequestId}:${JSON.stringify(payload)}`; }