Skip to content

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.

function aliasSplitter(
    uint256 newModelId,
    uint256 originalModelId
) external onlyAuthorized

Use Case: When publishing model v2, it should use the same splitter as v1 to maintain revenue continuity.

Example:

// Model v2 uses v1's splitter
await splitterFactory.aliasSplitter(modelIdV2, modelIdV1)


Queries

getSplitter

Gets the splitter address for a model, following aliases.

function getSplitter(uint256 modelId) external view returns (address)

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.

function getEffectiveModelId(uint256 modelId) external view returns (uint256)

Administration

setAuthorized

Grants or revokes authorization to create splitters.

function setAuthorized(address account, bool isAuthorized) external onlyOwner

setMarketplaceWallet

Updates the marketplace fee recipient.

function setMarketplaceWallet(address newWallet) external onlyOwner

setDefaultMarketplaceBps

Updates the default marketplace fee percentage.

function setDefaultMarketplaceBps(uint256 newBps) external onlyOwner

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

  1. Authorization: Only authorized addresses can create splitters
  2. Immutable Implementation: Implementation address cannot be changed
  3. BPS Limits: Royalty and marketplace fees have maximum caps
  4. No Duplicate Splitters: Each model can only have one splitter