Circle Internet Financial
Circle Internet Financial Logo

Apr 17, 2025

April 17, 2025

Refund Protocol: Non-Custodial Dispute Resolution for Stablecoin Payments

what you’ll learn

Refund Protocol enables secure, non-custodial refunds for stablecoin payments with transparent, smart contract-based dispute resolution.

Refund Protocol: Non-Custodial Dispute Resolution for Stablecoin Payments

TL;DR:

Stablecoin payments have traditionally lacked refund and dispute resolution mechanisms, making them closer to cash than modern digital payments. Refund Protocol is a smart contract that introduces onchain dispute resolution and escrow for ERC-20 payments—allowing for non-custodial refunds, lockups, and mediated resolutions while minimizing trust in a centralized third party. Designed for developers, Refund Protocol makes stablecoin payments safer and more flexible for real-world use.

Highlights:

  • Circle Research releases Refund Protocol: a smart contract enabling non-custodial escrow and onchain dispute resolution for stablecoin payments
  • Supports lockup periods, arbiter-mediated refunds, and early withdrawals based on offchain agreements
  • Built for composability and transparency in onchain commerce

Overview

When you buy a new pair of hiking boots online and receive a pair that has clearly been on a few expeditions without you, your credit card issuer can help you dispute the charge. Trusted dispute resolution allows buyers and sellers to confidently engage in ecommerce without a personal relationship.

Stablecoin payments, however, have no refund or chargeback mechanisms. They are more akin to cash transactions–the payment involves only the movement of value from one party to another.

This post proposes a way of adding dispute resolution for stablecoin payments with the goal of minimizing trust in third parties.

The trivial (and uninspired) solution: An all powerful arbiter

The trivial way to introduce dispute resolution is to create a trusted all-powerful arbiter.

The arbiter could escrow funds for a period of time before sending payment to the recipient. This escrow account could be a single address controlled by the arbiter, who performs accounting and dispute resolution offchain.

While this solution is tempting in its simplicity, it now introduces an entity that must be trusted by both parties.

A non-custodial arbiter

The problem with the all powerful arbiter is that they have the ability to take (or lose) the funds held in escrow. In the all powerful case, the arbiter is custodial, meaning they have full control and possession of the funds until they choose to forward them to the recipient.

We want to limit the arbiter’s power to just dispute resolution–the arbiter should only have the power to forward the money to the intended recipient or refund it back to the payer. The dispute resolution period must be fixed to avoid an indefinite lock-up of the funds. Since the arbiter can’t send the funds to an address of their choosing and can’t indefinitely prevent the recipient from retrieving their funds, the arbiter is non-custodial. 

What we need is a smart contract.

Refund Protocol

The Refund Protocol is a smart contract that allows a third party to mediate payment disputes without taking direct custody of the funds.

The Refund Protocol gives the arbiter three powers:

  1. to specify a lockup period during which a recipient’s funds are held in escrow
  2. to permit refunds to an address specified up front by the payer
  3. allow an early withdrawal of recipient funds for a mutually agreed upon fee paid by the recipient to the arbiter

Unlike with a centralized arbiter, the arbiter here specifically does not have the ability to send funds to an address of their choosing.

To better illustrate how the protocol works, let’s walk through the lifecycle of a payment.

1. Payment

Customers send ERC20 tokens by calling the pay function on the Refund Protocol smart contract. The smart contract records the recipient address, value, and refund address.

function pay(
    address to,
    uint256 amount,
    address refundTo
) external {
    ...
}

After this function is called, the recipient would consider the payment as having been rendered, though the funds are transferred to the escrow contract instead of to the ultimate recipient.

2. Refund

Now let’s say there is an issue with the order, such as a lack of delivery, and the customer wishes to dispute the payment. The customer can contact the recipient directly, and the recipient can issue a refund with the escrow contract using the `refundByRecipient` function.

function refundByRecipient(uint256 paymentID) external {
    Payment memory payment = payments[paymentID];
    if (msg.sender != payment.to) {
        revert CallerNotAllowed();
    }

    uint256 recipientBalance = balances[payment.to];

    if (payment.amount > recipientBalance) {
        revert InsufficientFunds();
    }

    balances[payment.to] = recipientBalance - payment.amount;

    _executeRefund(paymentID, payment);
}

However, in the case where the recipient disputes the refund, the customer can also appeal to the arbiter, who is able to issue a refund from the recipient’s balance using the `refundByArbiter` function.

function refundByArbiter(uint256 paymentID) onlyArbiter external {
    Payment memory payment = payments[paymentID];

    uint256 recipientBalance = balances[payment.to];

    if (payment.amount <= recipientBalance) {
        balances[payment.to] = recipientBalance - payment.amount;
        return _executeRefund(paymentID, payment);
    }

    uint256 arbiterBalance = balances[arbiter];

    if (payment.amount > arbiterBalance) {
        revert InsufficientFunds();
    }

    balances[arbiter] = arbiterBalance - payment.amount;
    debts[payment.to] += payment.amount;

    _executeRefund(paymentID, payment);
}

These disputes can be mediated offchain, and since these refund rates are transparent, the arbiter has an incentive to apply reasonable standards to fulfilling refund requests.

If the funds have already been withdrawn early (see the section on withdrawal below), and the recipient has an insufficient balance from which to refund, the arbiter could choose to cover the amount of the refund themselves, and register a debt owed by the recipient to the arbiter in the escrow contract to be covered by future payments.

3. Withdrawal

After the lockup period has passed, the recipient is able to withdraw the funds associated with that payment without any input from the arbiter and as long as that payment has not been refunded. This option keeps the escrow contract non-custodial.

4. Early Withdrawal

The recipient may want access to their funds sooner than the lockup period allows, which is made possible with an early withdrawal feature. The arbiter may choose, for instance, that only 5% of receipts should be held in the escrow as a buffer to satisfy future disputes.

The arbiter may allow early withdrawals from the escrow contract, and the Refund Protocol allows the arbiter to set a fee for doing so. In order to protect the payment recipient from arbitrary fees set by the arbiter, the early withdrawal involves an offchain signature from the recipient to indicate their consent to the withdrawal terms. This signature is verified onchain before the withdrawal is executed.

function earlyWithdrawByArbiter(
    uint256[] calldata paymentIDs,
    uint256[] calldata withdrawalAmounts,
    uint256 feeAmount,
    uint256 expiry,
    uint256 salt,
    address recipient,
    uint8 v,
    bytes32 r,
    bytes32 s
) onlyArbiter external {
    ...
}

Considerations

There are several practical items to consider before using Refund Protocol, including:

1. Payments from a malicious arbiter

A malicious arbiter can initiate payments from a separate address and issue unwarranted refunds. Extending the example of the beginning of the post for instance, the arbiter could receive a perfectly good pair of hiking boots and still approve the refund for them. The extent of the potential harm to the payment receiver depends on the size of payments they are processing and the types of goods and services rendered.

2. Specifying the refund address up front

Specifying a refund address at the time of payment may be more difficult than it seems.

For instance, the customer making the payment may not be using a non-custodial wallet. In that case, the custodial wallet would need to support the Refund Protocol interface and be able to specify an address that would be earmarked for returns to that customer.

More difficult still, the end customer may not be the one holding USDC (or another blockchain token) at all. They may be ultimately paying in fiat and using an onramp provider. It would then be incumbent on the onramp provider to specify an address to which refunds can be routed.

If a proper refund address can’t be specified up front, that task may be left to the arbiter. However, if the arbiter has full control of where a refund goes, the contract is no longer non-custodial.

3. Scalability with the number of payments

The Refund Protocol uses more gas than a typical ERC20 payment. The payment must first be deposited in the contract, and then a separate transaction is required to withdraw. To accommodate the different withholding periods for each payment, withdrawals must reference the state of each payment individually.

While for convenience withdrawals can happen in a batch in a single transaction, the gas cost scales linearly with the number of payments being withdrawn–i.e. it is roughly 10X more expensive to withdraw a balance of 100 that is composed of 100 separate payments than a balance of 100 that is composed of 10 separate payments.

4. Earning yield on escrowed assets

Assets sitting in the escrow contract being held to settle future disputes are currently unproductive. One could extend the contract to allow funds to be swept into a lending protocol like Aave so that they could earn yield. This yield could be shared with the payment recipients, and perhaps some could be kept by the arbiter as a further way to monetize their role as the party that handles disputes.

5. Supporting contract wallets

Right now the contract assumes that the recipient wallet is an EOA, since only EIP-712 signatures are supported for early withdrawals. If needed, the contract can be extended to support EIP-1271 signatures to allow the recipient address to be a contract.

The Code

If you found Refund Protocol interesting, check out the code on GitHub. We’d welcome your feedback on GitHub or by reaching out directly.

Acknowledgements

Refund Protocol was driven by Alex Kroeger and Kaili Wang from Circle Research.

We’d additionally like to thank the team at Inflow for their advice, knowledge, and conversations, particularly Danny Ba and Hanafi Issahnane.

Related posts

Cross-Chain USDC Transfers on Telegram: Simplifying P2P Payments

Cross-Chain USDC Transfers on Telegram: Simplifying P2P Payments

March 31, 2025
Introducing USDCKit: Seamless, Scalable Payment Flows with USDC

Introducing USDCKit: Seamless, Scalable Payment Flows with USDC

March 27, 2025
Now Live: Native USDC and CCTP V2 on Linea

Now Live: Native USDC and CCTP V2 on Linea

March 26, 2025
Blog
Refund Protocol: Non-Custodial Dispute Resolution for Stablecoin Payments
refund-protocol-non-custodial-dispute-resolution-for-stablecoin-payments
April 17, 2025
Refund Protocol enables secure, non-custodial refunds for stablecoin payments with transparent, smart contract-based dispute resolution.
Developer
Circle Research