Skip to content

FixedPriceSale Contract#

Overview#

The FixedPriceSale contract provides a simple, fixed-price minting mechanism for the AbrahamEarlyWorks NFT collection. Users can purchase NFTs at a fixed price of 0.01 ETH, with automatic minting and payment processing.

Key Features#

  • Fixed Pricing: Consistent 0.01 ETH price per NFT
  • Automated Minting: Direct integration with NFT contract
  • Batch Minting: Support for multiple NFT purchases
  • Revenue Withdrawal: Owner can withdraw collected funds
  • Refund Protection: Automatic refunds for overpayment
  • Gas Optimized: Efficient transaction processing

Architecture#

graph TB
    subgraph "FixedPriceSale Contract"
        A[User Payment] --> B[Validate Amount]
        B --> C[Mint NFT]
        C --> D[Process Refund]
        D --> E[Store Revenue]

        F[Batch Purchase] --> G[Loop Minting]
        G --> C

        H[Owner Functions] --> I[Withdraw Funds]
        I --> J[Transfer ETH]
    end

    subgraph "External Contracts"
        K[AbrahamEarlyWorks NFT]
        L[User Wallet]
        M[Owner Wallet]
    end

    C --> K
    D --> L
    J --> M

Contract Functions#

Public Minting Functions#

mint(string metadataURI)#

  • Purpose: Purchase and mint a single NFT
  • Access: Public (payable)
  • Price: 0.01 ETH
  • Parameters: metadataURI: IPFS URI for NFT metadata
  • Returns: uint256 - Token ID of minted NFT
  • Events: Emits NFTMinted(buyer, tokenId, metadataURI)

batchMint(string[] metadataURIs)#

  • Purpose: Purchase and mint multiple NFTs in one transaction
  • Access: Public (payable)
  • Price: 0.01 ETH × number of NFTs
  • Parameters: metadataURIs: Array of IPFS URIs for each NFT
  • Returns: uint256[] - Array of token IDs for minted NFTs
  • Events: Emits NFTMinted for each minted NFT

Owner Functions#

withdraw()#

  • Purpose: Withdraw accumulated revenue to owner
  • Access: Owner only
  • Transfers: All contract balance to owner address
  • Events: Emits FundsWithdrawn(owner, amount)

withdraw(uint256 amount)#

  • Purpose: Withdraw specific amount to owner
  • Access: Owner only
  • Parameters: amount: Wei amount to withdraw
  • Requirements: Amount ≤ contract balance
  • Events: Emits FundsWithdrawn(owner, amount)

emergencyWithdraw()#

  • Purpose: Emergency fund recovery (if needed)
  • Access: Owner only
  • Transfers: All contract balance immediately

View Functions#

PRICE()#

  • Purpose: Get current NFT price
  • Access: Public view
  • Returns: uint256 - Price in wei (10^16 = 0.01 ETH)

nftContract()#

  • Purpose: Get linked NFT contract address
  • Access: Public view
  • Returns: address - AbrahamEarlyWorks contract address

getContractBalance()#

  • Purpose: Check contract's current balance
  • Access: Public view
  • Returns: uint256 - Balance in wei

Events#

event NFTMinted(
    address indexed buyer, 
    uint256 indexed tokenId, 
    string metadataURI
);
event FundsWithdrawn(address indexed owner, uint256 amount);

Payment Flow#

sequenceDiagram
    participant User as Buyer
    participant Sale as FixedPriceSale
    participant NFT as AbrahamEarlyWorks
    participant Owner as Contract Owner

    User->>Sale: mint(metadataURI) + 0.01 ETH
    Sale->>Sale: Validate payment ≥ 0.01 ETH
    Sale->>NFT: mintTo(metadataURI, user)
    NFT->>NFT: Mint NFT to user
    NFT-->>Sale: Return tokenId

    alt Overpayment
        Sale->>User: Refund excess ETH
    end

    Sale->>Sale: Store 0.01 ETH
    Sale-->>User: Emit NFTMinted event

    Note over User,Owner: Later...

    Owner->>Sale: withdraw()
    Sale->>Owner: Transfer all stored ETH
    Sale-->>Owner: Emit FundsWithdrawn event

Security Features#

  • Reentrancy Protection: ReentrancyGuard on payable functions
  • Overflow Protection: Safe math operations
  • Access Control: Owner-only functions protected
  • Payment Validation: Exact payment verification
  • Refund Safety: Automatic overpayment refunds
  • Emergency Functions: Emergency withdrawal capability

Gas Optimization#

  • Efficient Minting: Direct contract-to-contract calls
  • Batch Operations: Reduced gas cost per NFT in batches
  • Minimal State: Limited state variables for efficiency
  • Single Transaction: Complete purchase in one transaction

Pricing Strategy#

// Fixed price structure
const PRICE_IN_ETH = 0.01;
const PRICE_IN_WEI = ethers.parseEther("0.01"); // 10^16 wei

// Batch pricing scales linearly
const batchPrice = PRICE_IN_WEI * numberOfNFTs;

Usage Examples#

Single NFT Purchase#

import { ethers } from 'ethers';
import FixedPriceSaleABI from './FixedPriceSale.json';

const SALE_CONTRACT_ADDRESS = "0xYourSaleContractAddress";
const PRICE = ethers.parseEther("0.01");

const saleContract = new ethers.Contract(
  SALE_CONTRACT_ADDRESS,
  FixedPriceSaleABI,
  buyerSigner
);

// Purchase single NFT
const tx = await saleContract.mint(
  "ipfs://QmYourMetadataHash",
  { value: PRICE }
);

const receipt = await tx.wait();
console.log("NFT purchased! Token ID:", receipt.events[0].args.tokenId);

Batch NFT Purchase#

// Purchase multiple NFTs at once
const metadataURIs = [
  "ipfs://QmHash1",
  "ipfs://QmHash2",
  "ipfs://QmHash3"
];

const totalPrice = PRICE * BigInt(metadataURIs.length);

const tx = await saleContract.batchMint(metadataURIs, {
  value: totalPrice
});

const receipt = await tx.wait();
console.log("Batch minted NFTs:", receipt.events.map(e => e.args.tokenId));

Revenue Withdrawal (Owner)#

// Check current balance
const balance = await saleContract.getContractBalance();
console.log("Contract balance:", ethers.formatEther(balance), "ETH");

// Withdraw all funds
if (balance > 0) {
  const tx = await saleContract.withdraw();
  await tx.wait();
  console.log("Funds withdrawn successfully");
}

// Or withdraw specific amount
const partialAmount = ethers.parseEther("1.0");
await saleContract.withdraw(partialAmount);

Frontend Integration#

// React component example
function NFTPurchase() {
  const [metadataURI, setMetadataURI] = useState("");
  const [isLoading, setIsLoading] = useState(false);

  const handlePurchase = async () => {
    setIsLoading(true);
    try {
      const tx = await saleContract.mint(metadataURI, {
        value: ethers.parseEther("0.01")
      });

      await tx.wait();
      alert("NFT purchased successfully!");
    } catch (error) {
      alert("Purchase failed: " + error.message);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div>
      <input 
        value={metadataURI}
        onChange={(e) => setMetadataURI(e.target.value)}
        placeholder="Enter IPFS metadata URI"
      />
      <button onClick={handlePurchase} disabled={isLoading}>
        {isLoading ? "Minting..." : "Purchase NFT (0.01 ETH)"}
      </button>
    </div>
  );
}

Testing#

Comprehensive test coverage includes:

describe("FixedPriceSale", function() {
  it("Should mint NFT for correct payment", async function() {
    const price = ethers.parseEther("0.01");
    const metadataURI = "ipfs://test";

    const tx = await saleContract
      .connect(buyer)
      .mint(metadataURI, { value: price });

    await expect(tx).to.emit(saleContract, "NFTMinted");
  });

  it("Should reject insufficient payment", async function() {
    const lowPrice = ethers.parseEther("0.005");

    await expect(
      saleContract.mint("ipfs://test", { value: lowPrice })
    ).to.be.revertedWith("Insufficient payment");
  });

  it("Should refund overpayment", async function() {
    const overpayment = ethers.parseEther("0.02");
    const initialBalance = await buyer.getBalance();

    await saleContract
      .connect(buyer)
      .mint("ipfs://test", { value: overpayment });

    // Buyer should have paid exactly 0.01 ETH (plus gas)
    const finalBalance = await buyer.getBalance();
    // Assert refund logic here...
  });
});

Error Handling#

The contract provides clear error messages:

  • "Insufficient payment" - Payment below 0.01 ETH
  • "NFT minting failed" - Underlying NFT contract reverted
  • "Withdrawal failed" - ETH transfer unsuccessful
  • "Empty metadata array" - Batch mint with no URIs
  • "Max supply reached" - NFT contract at capacity


Contract Status: Production Ready
Last Updated: September 1, 2025