Skip to content

Withdraw a refund ​

The exact contract function is:

solidity
withdrawRefund(uint256 submissionId)

After finalization, an eligible author of a non-winning submission may withdraw the refundable CLAY belonging to that submission. The winning submission cannot withdraw its settled bid.

Before calling withdrawRefund:

  1. read the submission and its auction;
  2. confirm that finalization has completed;
  3. confirm the connected account is the submission author;
  4. read canonical refund eligibility; and
  5. simulate the withdrawal.

Example: check eligibility and withdraw ​

ts
const refund = await withdrawRefund(41n);
ts
import {
  encodeFunctionData,
  getAddress,
  parseEventLogs,
  type Hash,
} from "viem";

import { tabletsAbi } from "@tablets/abi";

import {
  TABLETS_ADDRESS,
  assertPin,
  getActiveAccount,
  invariant,
  only,
  pinLatestBlock,
  publicClient,
  waitForExactTransaction,
  walletClient,
} from "./tablets-client.js";

export type WithdrawnRefund = {
  readonly transactionHash: Hash;
  readonly blockNumber: bigint;
  readonly auctionId: bigint;
  readonly submissionId: bigint;
  readonly amount: bigint;
};

export async function withdrawRefund(
  submissionId: bigint,
): Promise<WithdrawnRefund> {
  invariant(submissionId > 0n, "submissionId must be positive");
  const account = await getActiveAccount();
  const pin = await pinLatestBlock();

  const submission = await publicClient.readContract({
    address: TABLETS_ADDRESS,
    abi: tabletsAbi,
    functionName: "getSubmission",
    args: [submissionId],
    blockNumber: pin.number,
  });
  const [auction, refundable] = await Promise.all([
    publicClient.readContract({
      address: TABLETS_ADDRESS,
      abi: tabletsAbi,
      functionName: "getAuction",
      args: [submission.auctionId],
      blockNumber: pin.number,
    }),
    publicClient.readContract({
      address: TABLETS_ADDRESS,
      abi: tabletsAbi,
      functionName: "isRefundable",
      args: [submissionId],
      blockNumber: pin.number,
    }),
  ]);
  invariant(
    getAddress(submission.submitter) === account &&
      auction.finalized &&
      auction.winnerSubmissionId !== submissionId &&
      !submission.refundWithdrawn &&
      refundable,
    "Caller is not canonically eligible for this refund",
  );

  const { request } = await publicClient.simulateContract({
    address: TABLETS_ADDRESS,
    abi: tabletsAbi,
    functionName: "withdrawRefund",
    args: [submissionId],
    account,
    blockNumber: pin.number,
  });
  await assertPin(pin);

  const input = encodeFunctionData({
    abi: tabletsAbi,
    functionName: "withdrawRefund",
    args: [submissionId],
  });
  const transactionHash = await walletClient.writeContract(request);
  const receipt = await waitForExactTransaction(transactionHash, {
    account,
    to: TABLETS_ADDRESS,
    input,
  });
  const withdrawn = only(
    parseEventLogs({
      abi: tabletsAbi,
      eventName: "RefundWithdrawn",
      logs: receipt.logs.filter(
        (log) => getAddress(log.address) === TABLETS_ADDRESS,
      ),
      strict: true,
    }),
    "RefundWithdrawn event",
  );
  invariant(
    withdrawn.args.auctionId === submission.auctionId &&
      withdrawn.args.submissionId === submissionId &&
      getAddress(withdrawn.args.submitter) === account &&
      withdrawn.args.amount === submission.bidAmount,
    "RefundWithdrawn event mismatch",
  );

  const [stored, stillRefundable] = await Promise.all([
    publicClient.readContract({
      address: TABLETS_ADDRESS,
      abi: tabletsAbi,
      functionName: "getSubmission",
      args: [submissionId],
      blockNumber: receipt.blockNumber,
    }),
    publicClient.readContract({
      address: TABLETS_ADDRESS,
      abi: tabletsAbi,
      functionName: "isRefundable",
      args: [submissionId],
      blockNumber: receipt.blockNumber,
    }),
  ]);
  invariant(
    stored.refundWithdrawn && !stillRefundable,
    "Receipt-pinned refund state mismatch",
  );

  return {
    transactionHash,
    blockNumber: receipt.blockNumber,
    auctionId: withdrawn.args.auctionId,
    submissionId,
    amount: withdrawn.args.amount,
  };
}

After confirmation, decode RefundWithdrawn. Do not infer success from a transaction hash alone.