SplitterFactory¶
The SplitterFactory contract deploys ModelSplitter clones for each model using EIP-1167 minimal proxies. This enables gas-efficient deployment of revenue splitting contracts.
Contract Information¶
| Property | Value |
|---|---|
| Address (Fuji) | 0xf8d8C220181CAe9A748b8e817BFE337AB5b74731 |
| Solidity Version | ^0.8.24 |
| License | MIT |
| Inherits | Ownable2Step |
| Pattern | EIP-1167 Minimal Proxy |
Overview¶
When a model is published on WasiAI, a dedicated ModelSplitter contract is created to handle revenue distribution. Instead of deploying full contracts (expensive), the factory uses EIP-1167 minimal proxies that delegate to a single implementation contract.
Benefits: - Gas Efficient: ~$0.10-0.30 per splitter vs $5+ for full deployment - Consistent Logic: All splitters share the same implementation - Upgradeable: Implementation can be updated for new splitters
Constants¶
uint256 public constant MAX_BPS = 10_000; // 100% in basis points
uint256 public constant MAX_ROYALTY_BPS = 2_000; // 20% max creator royalty
uint256 public constant MAX_MARKETPLACE_BPS = 1_000; // 10% max marketplace fee
State Variables¶
address public immutable implementation; // ModelSplitter implementation
address public immutable usdc; // USDC token address
address public marketplaceWallet; // Marketplace fee recipient
uint256 public defaultMarketplaceBps; // Default marketplace fee
mapping(uint256 => address) public splitters; // modelId → splitter address
mapping(uint256 => uint256) public splitterAlias; // modelId → original modelId
mapping(address => bool) public authorized; // Authorized creators
uint256 public totalSplitters; // Total splitters created
Functions¶
Splitter Creation¶
createSplitter¶
Creates a new splitter for a model with default marketplace fee.
function createSplitter(
uint256 modelId,
address seller,
address creator,
uint256 royaltyBps
) external onlyAuthorized returns (address splitter)
Parameters: | Name | Type | Description | |------|------|-------------| | modelId | uint256 | Model ID from Marketplace | | seller | address | Seller address (receives majority) | | creator | address | Creator address (receives royalty) | | royaltyBps | uint256 | Creator royalty in basis points |
Returns: splitter (Address of the new splitter clone)
Requirements: - Caller must be authorized or owner - Model must not already have a splitter - Royalty ≤ 20% (2000 bps)
Example:
const splitter = await splitterFactory.createSplitter(
modelId,
sellerAddress,
creatorAddress,
1000 // 10% royalty
)
createSplitterWithCustomFee¶
Creates a splitter with a custom marketplace fee.
function createSplitterWithCustomFee(
uint256 modelId,
address seller,
address creator,
uint256 royaltyBps,
uint256 marketplaceBps
) external onlyAuthorized returns (address splitter)
Splitter Aliasing¶
For model families (versioned models), new versions can share the same splitter.
aliasSplitter¶
Links a new model ID to an existing splitter.
Use Case: When publishing model v2, it should use the same splitter as v1 to maintain revenue continuity.
Example:
Queries¶
getSplitter¶
Gets the splitter address for a model, following aliases.
Returns: Splitter address, or zero address if none exists
Example:
const splitter = await splitterFactory.getSplitter(modelId)
if (splitter === '0x0000...') {
console.log('No splitter for this model')
}
getEffectiveModelId¶
Gets the original model ID for aliased models.
Administration¶
setAuthorized¶
Grants or revokes authorization to create splitters.
setMarketplaceWallet¶
Updates the marketplace fee recipient.
setDefaultMarketplaceBps¶
Updates the default marketplace fee percentage.
Events¶
event SplitterCreated(
uint256 indexed modelId,
address indexed splitter,
address indexed seller,
address creator,
uint256 royaltyBps,
uint256 marketplaceBps
);
event AuthorizationChanged(
address indexed account,
bool authorized
);
event MarketplaceWalletChanged(
address indexed oldWallet,
address indexed newWallet
);
event DefaultMarketplaceBpsChanged(
uint256 oldBps,
uint256 newBps
);
event SplitterAliased(
uint256 indexed newModelId,
uint256 indexed originalModelId,
address splitter
);
Errors¶
error SplitterAlreadyExists();
error SplitterDoesNotExist();
error ZeroAddress();
error InvalidBps();
error OnlyAuthorized();
EIP-1167 Minimal Proxy¶
The factory uses OpenZeppelin's Clones library to deploy minimal proxies:
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
function _createSplitter(...) internal returns (address splitter) {
// Deploy minimal proxy pointing to implementation
splitter = implementation.clone();
// Initialize the clone
ModelSplitter(splitter).initialize(
usdc,
seller,
creator,
marketplaceWallet,
royaltyBps,
marketplaceBps
);
}
Proxy Bytecode: Only 45 bytes, delegates all calls to implementation.
Integration Examples¶
Check if Model Has Splitter¶
const splitter = await publicClient.readContract({
address: SPLITTER_FACTORY_ADDRESS,
abi: SplitterFactoryABI,
functionName: 'getSplitter',
args: [modelId]
})
const hasSplitter = splitter !== '0x0000000000000000000000000000000000000000'
Get Splitter Configuration¶
const splitterAddress = await splitterFactory.getSplitter(modelId)
const config = await publicClient.readContract({
address: splitterAddress,
abi: ModelSplitterABI,
functionName: 'getConfig',
args: []
})
console.log({
seller: config.seller,
creator: config.creator,
royaltyBps: config.royaltyBps,
marketplaceBps: config.marketplaceBps
})
Security Considerations¶
- Authorization: Only authorized addresses can create splitters
- Immutable Implementation: Implementation address cannot be changed
- BPS Limits: Royalty and marketplace fees have maximum caps
- No Duplicate Splitters: Each model can only have one splitter