Skip to main content

Overview

You can create custom recorders for specialized tracking beyond what the built-in recorders provide. Implement the IRecorder interface and extend BaseRecorder for operator access control.

IRecorder Interface

interface IRecorder {
    function record(
        AuthCaptureEscrow.PaymentInfo calldata paymentInfo,
        uint256 amount,
        address caller
    ) external;
}

Extending BaseRecorder

Extend BaseRecorder to ensure only authorized operators can call record():
contract MyRecorder is BaseRecorder {
    constructor(address _escrow) BaseRecorder(_escrow) {}

    function record(
        AuthCaptureEscrow.PaymentInfo calldata paymentInfo,
        uint256 amount,
        address caller
    ) external onlyAuthorizedOperator {
        // Your recording logic here
    }
}

Example: ReleaseCountRecorder

Tracks the number and total amount of releases per payment:
contract ReleaseCountRecorder is BaseRecorder {
    mapping(bytes32 => uint256) public releaseCount;
    mapping(bytes32 => uint256) public totalReleased;

    constructor(address _escrow) BaseRecorder(_escrow) {}

    function record(
        PaymentInfo calldata payment,
        uint256 amount,
        address caller
    ) external onlyAuthorizedOperator {
        bytes32 hash = escrow.getHash(payment);
        releaseCount[hash]++;
        totalReleased[hash] += amount;
    }

    // Enable tracking partial releases
    function getStats(bytes32 paymentHash)
        external view
        returns (uint256 count, uint256 total)
    {
        return (releaseCount[paymentHash], totalReleased[paymentHash]);
    }
}

Testing

Test custom recorders with Forge:
contract ReleaseCountRecorderTest is Test {
    ReleaseCountRecorder recorder;

    function setUp() public {
        recorder = new ReleaseCountRecorder(address(escrow));
    }

    function test_incrementsOnRecord() public {
        recorder.record(paymentInfo, 100e6, caller);
        bytes32 hash = escrow.getHash(paymentInfo);
        (uint256 count, uint256 total) = recorder.getStats(hash);
        assertEq(count, 1);
        assertEq(total, 100e6);
    }

    function test_tracksMultipleReleases() public {
        recorder.record(paymentInfo, 50e6, caller);
        recorder.record(paymentInfo, 30e6, caller);
        bytes32 hash = escrow.getHash(paymentInfo);
        (uint256 count, uint256 total) = recorder.getStats(hash);
        assertEq(count, 2);
        assertEq(total, 80e6);
    }
}

Security Checklist

  • Extends BaseRecorder for operator access control
  • Handles edge cases (zero amounts, duplicate records)
  • Gas-efficient storage layout
  • Comprehensive test coverage
Unlike conditions, recorders do modify state. Ensure proper access control via BaseRecorder to prevent unauthorized writes.

Next Steps

Recorders Overview

Compare recording strategies.

Custom Conditions

Build custom condition contracts.