Podcast Sponsorship Escrow
What it does:
Holds sponsorship funds in escrow and releases payments to podcasters only when agreed sponsorship deliverables are completed and verified on-chain.
Why it matters:
Protects both sponsors and podcasters, reduces payment disputes, enforces sponsorship terms transparently, and removes the need for trusted intermediaries or manual invoicing.
How it works:
-
Sponsors create a sponsorship deal with budget, deliverables, and deadlines
-
Funds are deposited into an on-chain escrow contract
-
Podcasters submit proof of sponsorship delivery (episode link, timestamp, reference hash)
-
Sponsors or designated verifiers approve completed deliverables
-
Smart contract releases payment automatically upon approval
-
Partial payments can be unlocked per milestone or episode
-
All agreements, approvals, and payouts are transparent and auditable on-chain
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title PodcastSponsorshipEscrow
* @author Nam
* @notice Escrow contract for podcast sponsorships with milestone-based releases
*/
contract PodcastSponsorshipEscrow {
uint256 public dealCount;
// -------------------- STRUCTS --------------------
struct Milestone {
string description; // e.g. "Mid-roll ad in Episode #42"
uint256 amount; // payment amount
bool approved;
bool paid;
string proofURI; // episode link / IPFS hash
}
struct SponsorshipDeal {
address sponsor;
address payable podcaster;
uint256 totalBudget;
uint256 deadline;
bool cancelled;
Milestone[] milestones;
}
// -------------------- STORAGE --------------------
mapping(uint256 => SponsorshipDeal) public deals;
// -------------------- EVENTS --------------------
event DealCreated(
uint256 indexed dealId,
address indexed sponsor,
address indexed podcaster,
uint256 totalBudget
);
event ProofSubmitted(uint256 indexed dealId, uint256 milestoneIndex, string proofURI);
event MilestoneApproved(uint256 indexed dealId, uint256 milestoneIndex);
event MilestonePaid(uint256 indexed dealId, uint256 milestoneIndex, uint256 amount);
event DealCancelled(uint256 indexed dealId);
// -------------------- DEAL CREATION --------------------
function createDeal(
address payable _podcaster,
uint256 _deadline,
string[] calldata _milestoneDescriptions,
uint256[] calldata _milestoneAmounts
) external payable returns (uint256) {
require(_podcaster != address(0), "Invalid podcaster");
require(_milestoneDescriptions.length == _milestoneAmounts.length, "Length mismatch");
require(_milestoneDescriptions.length > 0, "No milestones");
uint256 total;
for (uint256 i = 0; i < _milestoneAmounts.length; i++) {
total += _milestoneAmounts[i];
}
require(msg.value == total, "Budget mismatch");
dealCount += 1;
SponsorshipDeal storage d = deals[dealCount];
d.sponsor = msg.sender;
d.podcaster = _podcaster;
d.totalBudget = msg.value;
d.deadline = _deadline;
for (uint256 i = 0; i < _milestoneDescriptions.length; i++) {
d.milestones.push(
Milestone({
description: _milestoneDescriptions[i],
amount: _milestoneAmounts[i],
approved: false,
paid: false,
proofURI: ""
})
);
}
emit DealCreated(dealCount, msg.sender, _podcaster, msg.value);
return dealCount;
}
// -------------------- PODCASTER ACTIONS --------------------
function submitProof(
uint256 _dealId,
uint256 _milestoneIndex,
string calldata _proofURI
) external {
SponsorshipDeal storage d = deals[_dealId];
require(msg.sender == d.podcaster, "Not podcaster");
require(_milestoneIndex < d.milestones.length, "Invalid milestone");
Milestone storage m = d.milestones[_milestoneIndex];
require(!m.paid, "Already paid");
m.proofURI = _proofURI;
emit ProofSubmitted(_dealId, _milestoneIndex, _proofURI);
}
// -------------------- SPONSOR ACTIONS --------------------
function approveMilestone(uint256 _dealId, uint256 _milestoneIndex) external {
SponsorshipDeal storage d = deals[_dealId];
require(msg.sender == d.sponsor, "Not sponsor");
require(_milestoneIndex 0, "No proof submitted");
m.approved = true;
emit MilestoneApproved(_dealId, _milestoneIndex);
_releasePayment(_dealId, _milestoneIndex);
}
// -------------------- INTERNAL PAYMENT LOGIC --------------------
function _releasePayment(uint256 _dealId, uint256 _milestoneIndex) internal {
SponsorshipDeal storage d = deals[_dealId];
Milestone storage m = d.milestones[_milestoneIndex];
require(!m.paid, "Already paid");
m.paid = true;
d.podcaster.transfer(m.amount);
emit MilestonePaid(_dealId, _milestoneIndex, m.amount);
}
// -------------------- DEAL CANCELLATION --------------------
function cancelDeal(uint256 _dealId) external {
SponsorshipDeal storage d = deals[_dealId];
require(msg.sender == d.sponsor, "Not sponsor");
require(!d.cancelled, "Already cancelled");
d.cancelled = true;
uint256 refund;
for (uint256 i = 0; i 0) {
payable(d.sponsor).transfer(refund);
}
emit DealCancelled(_dealId);
}
}