Intro To The Spigot
Before diving into the technicals of the Spigot you should read into the Spigot product docs, to learn why we built it, why we designed it this way, and how we use it ourselves at Debt DAO before you use it yourself.
The most important thing to know is this. The Spigot is the first time anyone has been able to trustlessly collateralize onchain revenue streams allowing DeFi protocols to access an entirely new primitive for financial applications.
Integrating The Spigot
TK link to guides
‣
Interface
interface ISpigot {
struct Setting {
uint8 ownerSplit; // x/100 % to Owner, rest to Operator
bytes4 claimFunction; // function signature on contract to call and claim revenue
bytes4 transferOwnerFunction; // function signature on contract to call and transfer ownership
}
// Spigot Events
event AddSpigot(address indexed revenueContract, uint256 ownerSplit, bytes4 claimFnSig, bytes4 trsfrFnSig);
event RemoveSpigot(address indexed revenueContract, address token);
event UpdateWhitelistFunction(bytes4 indexed func, bool indexed allowed);
event UpdateOwnerSplit(address indexed revenueContract, uint8 indexed split);
event ClaimRevenue(address indexed token, uint256 indexed amount, uint256 escrowed, address revenueContract);
event ClaimOwnerTokens(address indexed token, uint256 indexed amount, address owner);
event ClaimOperatorTokens(address indexed token, uint256 indexed amount, address operator);
// Stakeholder Events
event UpdateOwner(address indexed newOwner);
event UpdateOperator(address indexed newOperator);
// Errors
error BadFunction();
error OperatorFnNotWhitelisted();
error OperatorFnNotValid();
error OperatorFnCallFailed();
error ClaimFailed();
error NoRevenue();
error UnclaimedRevenue();
error CallerAccessDenied();
error BadSetting();
error InvalidRevenueContract();
// ops funcs
function claimRevenue(
address revenueContract,
address token,
bytes calldata data
) external returns (uint256 claimed);
function operate(address revenueContract, bytes calldata data) external returns (bool);
// owner funcs
function claimOwnerTokens(address token) external returns (uint256 claimed);
function claimOperatorTokens(address token) external returns (uint256 claimed);
function addSpigot(address revenueContract, Setting memory setting) external returns (bool);
function removeSpigot(address revenueContract) external returns (bool);
// stakeholder funcs
function updateOwnerSplit(address revenueContract, uint8 ownerSplit) external returns (bool);
function updateOwner(address newOwner) external returns (bool);
function updateOperator(address newOperator) external returns (bool);
function updateWhitelistedFunction(bytes4 func, bool allowed) external returns (bool);
// Getters
function owner() external view returns (address);
function operator() external view returns (address);
function isWhitelisted(bytes4 func) external view returns (bool);
function getOwnerTokens(address token) external view returns (uint256);
function getOperatorTokens(address token) external view returns (uint256);
function getSetting(
address revenueContract
) external view returns (uint8 split, bytes4 claimFunc, bytes4 transferFunc);
}
‣
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
},
{
"internalType": "address",
"name": "_operator",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "BadFunction",
"type": "error"
},
{
"inputs": [],
"name": "BadSetting",
"type": "error"
},
{
"inputs": [],
"name": "CallerAccessDenied",
"type": "error"
},
{
"inputs": [],
"name": "ClaimFailed",
"type": "error"
},
{
"inputs": [],
"name": "InvalidRevenueContract",
"type": "error"
},
{
"inputs": [],
"name": "NoRevenue",
"type": "error"
},
{
"inputs": [],
"name": "OperatorFnCallFailed",
"type": "error"
},
{
"inputs": [],
"name": "OperatorFnNotValid",
"type": "error"
},
{
"inputs": [],
"name": "OperatorFnNotWhitelisted",
"type": "error"
},
{
"inputs": [],
"name": "UnclaimedRevenue",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "revenueContract",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ownerSplit",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bytes4",
"name": "claimFnSig",
"type": "bytes4"
},
{
"indexed": false,
"internalType": "bytes4",
"name": "trsfrFnSig",
"type": "bytes4"
}
],
"name": "AddSpigot",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"name": "ClaimOperatorTokens",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "ClaimOwnerTokens",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "escrowed",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "revenueContract",
"type": "address"
}
],
"name": "ClaimRevenue",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "revenueContract",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "RemoveSpigot",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "newOperator",
"type": "address"
}
],
"name": "UpdateOperator",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "UpdateOwner",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "revenueContract",
"type": "address"
},
{
"indexed": true,
"internalType": "uint8",
"name": "split",
"type": "uint8"
}
],
"name": "UpdateOwnerSplit",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes4",
"name": "func",
"type": "bytes4"
},
{
"indexed": true,
"internalType": "bool",
"name": "allowed",
"type": "bool"
}
],
"name": "UpdateWhitelistFunction",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "revenueContract",
"type": "address"
},
{
"components": [
{
"internalType": "uint8",
"name": "ownerSplit",
"type": "uint8"
},
{
"internalType": "bytes4",
"name": "claimFunction",
"type": "bytes4"
},
{
"internalType": "bytes4",
"name": "transferOwnerFunction",
"type": "bytes4"
}
],
"internalType": "struct ISpigot.Setting",
"name": "setting",
"type": "tuple"
}
],
"name": "addSpigot",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "claimOperatorTokens",
"outputs": [
{
"internalType": "uint256",
"name": "claimed",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "claimOwnerTokens",
"outputs": [
{
"internalType": "uint256",
"name": "claimed",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "revenueContract",
"type": "address"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "claimRevenue",
"outputs": [
{
"internalType": "uint256",
"name": "claimed",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "getOperatorTokens",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "getOwnerTokens",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "revenueContract",
"type": "address"
}
],
"name": "getSetting",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
},
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
},
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "func",
"type": "bytes4"
}
],
"name": "isWhitelisted",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "revenueContract",
"type": "address"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "operate",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "operator",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "revenueContract",
"type": "address"
}
],
"name": "removeSpigot",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOperator",
"type": "address"
}
],
"name": "updateOperator",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "updateOwner",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "revenueContract",
"type": "address"
},
{
"internalType": "uint8",
"name": "ownerSplit",
"type": "uint8"
}
],
"name": "updateOwnerSplit",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "func",
"type": "bytes4"
},
{
"internalType": "bool",
"name": "allowed",
"type": "bool"
}
],
"name": "updateWhitelistedFunction",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]
Contract | Function Name | Modifiers | Description | Calls (sequential order DESC) | Called By | Function Signature |
---|---|---|---|---|---|---|
Spigot.sol | addSpigot(address revenueContract, SpigotSettings memory setting) | onlyOwner | Attach a new Revenue Contract to a Spigot. MUST include revenue split + valid transferOwner and claimRevenue functions
SpigotSettings:
- uint8 ownerSplit - % of revenue that goes to Owner. 0 decimals
- bytes4 claimFunction - function selector on Revenue Contract to claim revenue
- bytes4 transferOwnerFunction - function selector on Revenue Contract to relinquish ownership from Spigot | |||
Spigot.sol | updateWhitelistedFunction(bytes4 func, bool isAllowed) | onlyOwner | Allows the Owner to update Spigot whitelist of functions Operator can call on Revenue Contracts. This allows Operator to perform managerial tasks on its Revenue Contracts so they continue generating revenue while they are collateralized in the Spigot to repay debt. | |||
Spigot.sol | claimRevenue(address revenueContract, bytes calldata claimData) | ANYONE | Claims Revenue Tokens earned by one or more Revenue Contracts and updates amount claimable by Owner and Operator.
If push payments: When revenue is sent directly to the Spigot without us doing anything. We must calculate how many have been sent to us and disburse. (push payments = claimFunction == bytes4(0) )
if pull payments: When we must call the Revenue Contract to claim Revenue Tokens and then disburse the amount of tokens received from Revenue Contract. (pull payments = claimFunction == bytes4('0xrealfunc') ) | |||
Spigot.sol | claimOwnerTokens(address token, uint256 amount) | onlyOwner | Owner withdraws their share of Revenue Tokens that have already been claimed via claimRevenue() | |||
Spigot.sol | claimOperatorTokens(address token, uint256 amount) | onlyOperator | - Operator withdraws their share of Revenue Tokens that have already been claimed via claimRevenue() | |||
Spigot.sol | getOwnerTokens(address token) | view | Returns the amount of Revenue Tokens held by Spigot that the Owner can withdraw. | |||
Spigot.sol | getOperatorTokens(address token) | view | Returns the amount of Revenue Tokens held by Spigot that the Operator can withdraw. | |||
Spigot.sol | isWhitelisted(bytes4 functionSignature) | view | Returns if the function has been whitelisted across Revenue Contracts for the Operator to call in operate() | |||
Spigot.sol | getSetting(address revenueContract) | view | Returns the config for a Revenue Contract:
- uint8 ownerSplit - % of revenue that goes to Owner. 0 decimals
- bytes4 claimFunction - function selector on Revenue Contract to claim revenue
- bytes4 transferOwnerFunction - function selector on Revenue Contract to relinquish ownership from Spigot | |||
Spigot.sol | operate(address revenueContract, bytes calldata msgData) | onlyOperator | - Allows an Operator to call the whitelisted functions on its Revenue Contracts attached to the Spigot and carry on its business as usual activities. | |||
Spigot.sol | updateOwnerSplit(address revenueContract, uint8 split) | onlyOwner | Changes the revenue split between the Operator and the Owner based upon programmed conditions or otherwise if the Owner and Operator wish to change the split. | |||
Spigot.sol | updateOwner(address newOwner) | onlyOwner | Puts a new Owner in control the Spigot.
| |||
Spigot.sol | updateOperator(address newOperator) | onlyOperator | - Puts a new Operator address in place to interact with the Revenue Contracts if ever this were needed. | |||
Spigot.sol | removeSpigot(address revenueContract) | onlyOwner | - Calls transferOwnerFunction on Revneu Contract settings and transfers ownership to Operator. | |||
Spigot.sol | owner() | view | Returns the address of the current Spigot Owner. | |||
Spigot.sol | operator() | view | Returns the address of the current Spigot Operator. |