Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/MintBountyFactory.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/proxy/Clones.sol";
import "./MintBounty.sol";
/**
* @title MintBountyFactory
* @author ripe
* @notice Factory contract for deploying MintBounty clones using EIP-1167
* @dev Uses OpenZeppelin's Clones library for gas-efficient deployment
*/
contract MintBountyFactory {
using Clones for address;
/// @notice The implementation contract that all clones will delegate to
address public immutable implementation;
/// @notice Emitted when a new bounty contract is deployed
event BountyContractDeployed(
address indexed owner,
address indexed bountyContract
);
/**
* @notice Deploy the factory with its implementation contract
* @dev The implementation is deployed once and reused by all clones
*/
constructor() {
implementation = address(new MintBounty());
}
/**
* @notice Deploy a new MintBounty clone for the caller
* @return bountyContract The address of the newly deployed clone
*/
function deployBountyContract() external returns (address bountyContract) {
// Deploy minimal proxy clone
bountyContract = implementation.clone();
// Initialize the clone with msg.sender as owner
MintBounty(bountyContract).initialize(msg.sender);
// Emit event for tracking
emit BountyContractDeployed(msg.sender, bountyContract);
}
}
"
},
"lib/openzeppelin-contracts/contracts/proxy/Clones.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (proxy/Clones.sol)
pragma solidity ^0.8.20;
import {Create2} from "../utils/Create2.sol";
import {Errors} from "../utils/Errors.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*/
library Clones {
error CloneArgumentsTooLong();
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation`.
*
* This function uses the create opcode, which should never revert.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*/
function clone(address implementation) internal returns (address instance) {
return clone(implementation, 0);
}
/**
* @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
* to the new contract.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function clone(address implementation, uint256 value) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create(value, 0x09, 0x37)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple times will revert, since
* the clones cannot be deployed twice at the same address.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*/
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
return cloneDeterministic(implementation, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
* a `value` parameter to send native currency to the new contract.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministic(
address implementation,
bytes32 salt,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create2(value, 0x09, 0x37, salt)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(add(ptr, 0x38), deployer)
mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
mstore(add(ptr, 0x14), implementation)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
mstore(add(ptr, 0x58), salt)
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddress(implementation, salt, address(this));
}
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create opcode, which should never revert.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*/
function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
return cloneWithImmutableArgs(implementation, args, 0);
}
/**
* @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
* parameter to send native currency to the new contract.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneWithImmutableArgs(
address implementation,
bytes memory args,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
assembly ("memory-safe") {
instance := create(value, add(bytecode, 0x20), mload(bytecode))
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
* `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
* at the same address.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal returns (address instance) {
return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
* but with a `value` parameter to send native currency to the new contract.
*
* WARNING: This function does not check if `implementation` has code. A clone that points to an address
* without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they
* have no effect and leave the clone uninitialized, allowing a third party to initialize it later.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
uint256 value
) internal returns (address instance) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.deploy(value, salt, bytecode);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.computeAddress(salt, keccak256(bytecode), deployer);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this));
}
/**
* @dev Get the immutable args attached to a clone.
*
* - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this
* function will return an empty array.
* - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or
* `cloneDeterministicWithImmutableArgs`, this function will return the args array used at
* creation.
* - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This
* function should only be used to check addresses that are known to be clones.
*/
function fetchCloneArgs(address instance) internal view returns (bytes memory) {
bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
assembly ("memory-safe") {
extcodecopy(instance, add(result, 32), 45, mload(result))
}
return result;
}
/**
* @dev Helper that prepares the initcode of the proxy with immutable args.
*
* An assembly variant of this function requires copying the `args` array, which can be efficiently done using
* `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using
* abi.encodePacked is more expensive but also more portable and easier to review.
*
* NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes.
* With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes.
*/
function _cloneCodeWithImmutableArgs(
address implementation,
bytes memory args
) private pure returns (bytes memory) {
if (args.length > 24531) revert CloneArgumentsTooLong();
return
abi.encodePacked(
hex"61",
uint16(args.length + 45),
hex"3d81600a3d39f3363d3d373d3d3d363d73",
implementation,
hex"5af43d82803e903d91602b57fd5bf3",
args
);
}
}
"
},
"src/MintBounty.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "./interfaces/IMint.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
/**
* @title MintBounty
* @author ripe
* @notice Automated bounty system for minting digital artifacts from Mint protocol collections
* @dev This contract incentivizes community members to execute mints on behalf of the owner
* by offering artifact rewards and gas refunds. Designed specifically for daily artifact
* drops on the Mint protocol (mint.vv.xyz) where timing is critical.
*
* The owner funds bounties with ETH and sets parameters like reward amounts and gas limits.
* When new artifacts become available, any minter can execute the bounty to:
* - Mint the total specified number of artifacts (artifactsToMint)
* - Receive their reward artifacts immediately (minterReward amount)
* - Get a full gas refund from the bounty balance (including base tx cost)
* - Leave the owner's artifacts in the contract for later withdrawal (artifactsToMint - minterReward)
*
* This creates a win-win: owners never miss drops, minters earn artifacts for their service.
*/
contract MintBounty is ReentrancyGuard {
// Constants for better readability (compile-time replaced, no gas cost)
uint256 private constant BASE_TX_GAS = 21_000; // Ethereum base transaction cost
uint256 private constant MINT_PRICE_MULTIPLIER = 60_000; // Artifact price = basefee * this
uint256 private constant MIN_GAS_BUFFER = 200_000; // Minimum gas units to cover refund
uint256 private constant ETH_TRANSFER_GAS = 9_000; // Approximate gas for ETH transfer
struct Bounty {
address recipient; // Where owner's artifacts go
bool paused; // Is bounty paused (packed with address)
uint96 lastMintedId; // Last successfully minted token ID (96 bits enough)
uint256 artifactsToMint; // Total number of artifacts to mint (minter gets minterReward, owner gets the rest)
uint256 minterReward; // Number of artifacts the minter keeps (must be less than artifactsToMint)
uint256 maxArtifactPrice; // Max price per artifact in wei (e.g., 0.001 ETH = 1000000000000000 wei)
uint256 balance; // ETH balance for mints + refunds
}
/// @notice The owner address who can manage all bounties
address public owner;
/// @notice Mapping from Mint collection address to its bounty configuration
mapping(address => Bounty) public bounties;
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
modifier bountyExists(address tokenContract) {
require(bounties[tokenContract].recipient != address(0), "No bounty");
_;
}
error TransferFailed();
// Internal helper to handle ETH transfers with error checking
function _transferETH(address to, uint256 amount) internal {
(bool success, ) = payable(to).call{value: amount}("");
if (!success) revert TransferFailed();
}
/**
* @notice Creates a new MintBounty contract
* @dev For direct deployment, call initialize(msg.sender) after deployment.
* For factory deployment, clones will call initialize with the user's address.
*/
constructor() {
// Constructor left empty to support both direct and clone deployment patterns
// Owner must be set via initialize() function
}
/**
* @notice Initialize the contract with an owner address
* @dev Used by factory for clone deployment. Can only be called once.
* @param _owner The address that will own this bounty contract
*/
function initialize(address _owner) external {
require(owner == address(0), "Already initialized");
require(_owner != address(0), "Invalid owner");
owner = _owner;
}
/**
* @notice Creates or updates a bounty for a specific Mint collection
* @dev Overwrites any existing bounty for the same tokenContract. Starts with lastMintedId=0
* to allow minting the currently available token immediately.
* @param tokenContract The Mint collection contract address
* @param recipient Address to receive the owner's artifacts when withdrawn
* @param artifactsToMint Total number of artifacts to mint (e.g., 3 total: 1 for minter, 2 for owner)
* @param minterReward Number of artifacts the minter keeps as payment (e.g., 1 out of 3 total)
* @param maxArtifactPrice Maximum price per artifact in wei (e.g., 0.001 ETH = 1000000000000000 wei)
* Since artifact price = block.basefee * 60000, this also limits gas price.
* Example: At 10 gwei basefee, artifact costs 0.0006 ETH
*/
function createBounty(
address tokenContract,
address recipient,
uint256 artifactsToMint,
uint256 minterReward,
uint256 maxArtifactPrice
) external payable onlyOwner {
require(minterReward < artifactsToMint, "Invalid reward");
require(recipient != address(0), "Invalid recipient");
bounties[tokenContract] = Bounty({
recipient: recipient,
paused: false,
lastMintedId: 0, // Start at 0 to allow minting current token
artifactsToMint: artifactsToMint,
minterReward: minterReward,
maxArtifactPrice: maxArtifactPrice,
balance: msg.value
});
}
/**
* @notice Claims a bounty by minting artifacts and distributing rewards
* @dev Mints artifacts, sends minter their reward, refunds gas (including base tx cost), keeps owner's portion
* @param tokenContract The Mint collection to claim the bounty for
*
* Gas Refund Details:
* - Refunds claiming gas + 21,000 base transaction cost + ETH transfer gas
* - Minter's net cost is effectively zero (maybe a few hundred gas units)
*
* Requirements:
* - Bounty must exist and not be paused
* - New token ID must be available (higher than last minted)
* - Current gas price must be <= maxGasPrice
* - Bounty must have sufficient balance for mint cost + gas refund
* - Mint window must still be open
*/
function claimBounty(address tokenContract) external nonReentrant {
// Reserve gas for the final ETH transfer upfront
uint256 gasStart = gasleft() - ETH_TRANSFER_GAS;
// Check claimability and get required values
(uint256 latestId, uint256 totalCost) = _requireClaimable(
tokenContract
);
Bounty storage bounty = bounties[tokenContract];
// Mint artifacts to this contract
IMint(tokenContract).mint{value: totalCost}(
latestId,
bounty.artifactsToMint
);
// Transfer only minter's reward (owner withdraws their portion later)
IERC1155(tokenContract).safeTransferFrom(
address(this),
msg.sender,
latestId,
bounty.minterReward,
""
);
// Update state
bounty.lastMintedId = uint96(latestId);
// Calculate gas refund (capped at remaining balance after mint)
// Don't include ETH_TRANSFER_GAS since we reserved it upfront
uint256 gasUsed = (gasStart - gasleft() + BASE_TX_GAS) * tx.gasprice;
uint256 remainingBalance = bounty.balance - totalCost;
if (gasUsed > remainingBalance) {
gasUsed = remainingBalance;
}
// Single balance update for both mint cost and gas refund
bounty.balance = remainingBalance - gasUsed;
// Transfer gas refund (we reserved gas for this)
_transferETH(msg.sender, gasUsed);
}
function _requireClaimable(
address tokenContract
) internal view returns (uint256 latestId, uint256 totalCost) {
bool claimable;
string memory reason;
(claimable, latestId, totalCost, reason) = _checkClaimable(
tokenContract
);
require(claimable, reason);
}
function _checkClaimable(
address tokenContract
)
internal
view
returns (
bool claimable,
uint256 latestId,
uint256 totalCost,
string memory reason
)
{
Bounty memory bounty = bounties[tokenContract];
if (bounty.recipient == address(0)) {
return (false, 0, 0, "No bounty");
}
if (bounty.paused) {
return (false, 0, 0, "Bounty paused");
}
// Check if current artifact price exceeds maximum
uint256 currentBaseFee = block.basefee;
uint256 artifactPrice = currentBaseFee * MINT_PRICE_MULTIPLIER;
if (artifactPrice > bounty.maxArtifactPrice) {
return (false, 0, 0, "Artifact price too high");
}
latestId = IMint(tokenContract).latestTokenId();
if (latestId <= bounty.lastMintedId) {
return (false, 0, 0, "No new token");
}
totalCost = artifactPrice * bounty.artifactsToMint;
// Ensure enough balance for mint + reasonable gas refund
uint256 minRequired = totalCost + (MIN_GAS_BUFFER * tx.gasprice);
if (bounty.balance < minRequired) {
return (
false,
latestId,
totalCost,
"Insufficient funds for mint and refund"
);
}
uint256 mintCloseTime = IMint(tokenContract).mintOpenUntil(latestId);
if (block.timestamp >= mintCloseTime) {
return (false, latestId, totalCost, "Mint closed");
}
return (true, latestId, totalCost, "");
}
/**
* @notice Updates all bounty parameters in a single transaction
* @dev More gas efficient than calling multiple update functions
* @param tokenContract The Mint collection contract
* @param recipient New address to receive owner's artifacts
* @param paused Whether the bounty should be paused
* @param artifactsToMint New total number of artifacts to mint (minter + owner combined)
* @param minterReward New number of artifacts for minter (must be less than artifactsToMint)
* @param maxArtifactPrice New maximum price per artifact in wei
*/
function updateBounty(
address tokenContract,
address recipient,
bool paused,
uint256 artifactsToMint,
uint256 minterReward,
uint256 maxArtifactPrice
) external onlyOwner bountyExists(tokenContract) {
require(recipient != address(0), "Invalid recipient");
require(minterReward < artifactsToMint, "Invalid reward");
Bounty storage bounty = bounties[tokenContract];
bounty.recipient = recipient;
bounty.paused = paused;
bounty.artifactsToMint = artifactsToMint;
bounty.minterReward = minterReward;
bounty.maxArtifactPrice = maxArtifactPrice;
}
/**
* @notice Adds ETH to an existing bounty's balance
* @param tokenContract The Mint collection contract to fund
*/
function fundBounty(address tokenContract) external payable onlyOwner {
require(bounties[tokenContract].recipient != address(0), "No bounty");
bounties[tokenContract].balance += msg.value;
}
/**
* @notice Withdraws ETH from a bounty's balance back to owner
* @param tokenContract The Mint collection contract
* @param amount Amount of ETH to withdraw in wei
*/
function withdrawBalance(
address tokenContract,
uint256 amount
) external onlyOwner bountyExists(tokenContract) {
Bounty storage bounty = bounties[tokenContract];
require(bounty.balance >= amount, "Insufficient balance");
bounty.balance -= amount;
_transferETH(owner, amount);
}
/**
* @notice Withdraws accumulated artifacts from the contract to a recipient
* @dev Transfers all available balance for each specified token ID
* @param tokenContract The Mint collection contract holding the artifacts
* @param tokenIds Array of token IDs to withdraw
* @param recipient Address to receive the artifacts (can differ from bounty recipient)
*/
function withdrawArtifacts(
address tokenContract,
uint256[] calldata tokenIds,
address recipient
) external onlyOwner {
for (uint256 i = 0; i < tokenIds.length; i++) {
uint256 balance = IERC1155(tokenContract).balanceOf(
address(this),
tokenIds[i]
);
if (balance > 0) {
IERC1155(tokenContract).safeTransferFrom(
address(this),
recipient,
tokenIds[i],
balance,
""
);
}
}
}
/**
* @notice Checks if a bounty can currently be claimed
* @param tokenContract The Mint collection to check
* @return bool True if all conditions are met for claiming
*/
function isBountyClaimable(
address tokenContract
) external view returns (bool) {
Bounty memory bounty = bounties[tokenContract];
// Quick checks without calculating costs
if (bounty.recipient == address(0) || bounty.paused) return false;
// Check artifact price
uint256 artifactPrice = block.basefee * MINT_PRICE_MULTIPLIER;
if (artifactPrice > bounty.maxArtifactPrice) return false;
uint256 latestId = IMint(tokenContract).latestTokenId();
if (latestId <= bounty.lastMintedId) return false;
// Check mint window
uint256 mintCloseTime = IMint(tokenContract).mintOpenUntil(latestId);
if (block.timestamp >= mintCloseTime) return false;
// Check sufficient funds for mint + gas refund
uint256 totalCost = artifactPrice * bounty.artifactsToMint;
uint256 minRequired = totalCost + (MIN_GAS_BUFFER * tx.gasprice);
return bounty.balance >= minRequired;
}
/**
* @notice Handles receipt of a single ERC1155 token type
* @dev Required for the contract to receive ERC1155 tokens
* @return bytes4 The selector to confirm token transfer acceptance
*/
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes calldata
) external pure returns (bytes4) {
return this.onERC1155Received.selector;
}
/**
* @notice Handles receipt of multiple ERC1155 token types
* @dev Required for the contract to receive batch ERC1155 transfers
* @return bytes4 The selector to confirm token transfer acceptance
*/
function onERC1155BatchReceived(
address,
address,
uint256[] calldata,
uint256[] calldata,
bytes calldata
) external pure returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Create2.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
* `CREATE2` can be used to compute in advance the address where a smart
* contract will be deployed, which allows for interesting new mechanisms known
* as 'counterfactual interactions'.
*
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
* information.
*/
library Create2 {
/**
* @dev There's no code to deploy.
*/
error Create2EmptyBytecode();
/**
* @dev Deploys a contract using `CREATE2`. The address where the contract
* will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `amount`.
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
if (bytecode.length == 0) {
revert Create2EmptyBytecode();
}
assembly ("memory-safe") {
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
// if no address was created, and returndata is not empty, bubble revert
if and(iszero(addr), not(iszero(returndatasize()))) {
let p := mload(0x40)
returndatacopy(p, 0, returndatasize())
revert(p, returndatasize())
}
}
if (addr == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
* `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
return computeAddress(salt, bytecodeHash, address(this));
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
assembly ("memory-safe") {
let ptr := mload(0x40) // Get free memory pointer
// | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... |
// |-------------------|---------------------------------------------------------------------------|
// | bytecodeHash | CCCCCCCCCCCCC...CC |
// | salt | BBBBBBBBBBBBB...BB |
// | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA |
// | 0xFF | FF |
// |-------------------|---------------------------------------------------------------------------|
// | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
// | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |
mstore(add(ptr, 0x40), bytecodeHash)
mstore(add(ptr, 0x20), salt)
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
mstore8(start, 0xff)
addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Errors.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}
"
},
"src/interfaces/IMint.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IMint {
function latestTokenId() external view returns (uint256);
function mint(uint256 tokenId, uint256 amount) external payable;
function mintOpenUntil(uint256 tokenId) external view returns (uint256);
}
interface IERC1155 {
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes calldata data
) external;
function balanceOf(address account, uint256 id) external view returns (uint256);
}"
},
"lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
"
}
},
"settings": {
"remappings": [
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"forge-std/=lib/forge-std/src/",
"erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
"halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": false
}
}}
Submitted on: 2025-09-18 11:08:41
Comments
Log in to comment.
No comments yet.