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