🎓 A1Learner
🔍
0 of 48 lessons completed

Solidity Pull Payments

Pull payments are withdrawals by the people who are owed. The contract records each amount owed, and each person claims it in a separate call.

Amounts owed are recorded, not sent

Distributing records an amount for one address. The ether stays in the contract until that address claims it.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract PullPayments {
    address public owner;
    mapping(address => uint256) public owed;

    constructor() {
        owner = msg.sender;
    }

    receive() external payable {}

    function distribute(address to, uint256 amount) public {
        require(msg.sender == owner, "not owner");
        owed[to] += amount;
    }
}

Each person claims exactly once

The owed person withdraws the recorded amount. The owed amount is cleared first, so a second claim reverts.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract PullPayments {
    address public owner;
    mapping(address => uint256) public owed;

    constructor() {
        owner = msg.sender;
    }

    receive() external payable {}

    function distribute(address to, uint256 amount) public {
        require(msg.sender == owner, "not owner");
        owed[to] += amount;
    }

    function withdrawPayment() public {
        uint256 amount = owed[msg.sender];
        require(amount > 0, "nothing owed");
        owed[msg.sender] = 0;
        (bool ok, ) = payable(msg.sender).call{value: amount}("");
        require(ok, "payout failed");
    }
}

One claim leaves other amounts untouched

Two people are owed different amounts. The first claim pays only its own amount and leaves the second amount in place.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract PullPayments {
    address public owner;
    mapping(address => uint256) public owed;

    constructor() {
        owner = msg.sender;
    }

    receive() external payable {}

    function distribute(address to, uint256 amount) public {
        require(msg.sender == owner, "not owner");
        owed[to] += amount;
    }

    function withdrawPayment() public {
        uint256 amount = owed[msg.sender];
        require(amount > 0, "nothing owed");
        owed[msg.sender] = 0;
        (bool ok, ) = payable(msg.sender).call{value: amount}("");
        require(ok, "payout failed");
    }
}

Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract PullPayments {
    address public owner;
    mapping(address => uint256) public owed;

    constructor() {
        owner = msg.sender;
    }

    receive() external payable {}

    function distribute(address to, uint256 amount) public {
        require(msg.sender == owner, "not owner");
        owed[to] += amount;
    }

    function withdrawPayment() public {
        uint256 amount = owed[msg.sender];
        require(amount > 0, "nothing owed");
        owed[msg.sender] = 0;
        (bool ok, ) = payable(msg.sender).call{value: amount}("");
        require(ok, "payout failed");
    }
}

Key points

  • Owed: an amount the contract records for one address.
  • Claim: a separate withdrawal call by the owed person.
  • Exactly once: the owed amount is cleared before the payout, so a second claim reverts.
  • Isolation: one person claiming does not change what others are owed.
  • Funding: the contract holds the ether until each claim is made.

In this example, the owner records 2 ether for one address and 3 ether for another, with the contract funded by 10 ether. Each address claims in its own call and receives exactly its recorded amount. A second claim by the same address reverts because the owed amount was cleared before the first payout.

Note

a failed claim for one person never blocks the claims of others.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Remix IDE

Exercise

In pull payments, who starts the payout?