User Flows
Key user journeys through the Softlaw Marketplace system.
Creating IP Assets
Native IP Creation
sequenceDiagram
actor Owner as IP Owner
participant IP as IPAsset
participant LT as LicenseToken
participant RD as RevenueDistributor
Owner->>IP: mintIP(metadata)
IP->>IP: Mint ERC-721 token
IP-->>Owner: tokenId
Owner->>RD: configureSplit(tokenId, recipients, shares)
RD->>RD: Store revenue split
RD-->>Owner: Split configured
Owner->>IP: mintLicense(ipTokenId, licensee, params)
IP->>LT: mintLicense(to, ipAssetId, params)
LT->>LT: Create ERC-1155 license
LT->>IP: updateActiveLicenseCount(tokenId, +1)
LT-->>IP: licenseId
IP-->>Owner: licenseId
Wrapping External NFTs
sequenceDiagram
actor Owner as NFT Owner
participant NFT as External NFT
participant IP as IPAsset
participant LT as LicenseToken
Note over Owner,NFT: Owner has existing NFT<br/>(e.g., Bored Ape, CryptoPunk)
Owner->>NFT: approve(IPAsset, tokenId)
Owner->>IP: wrapNFT(nftContract, nftTokenId, metadata)
IP->>NFT: ownerOf(nftTokenId)
NFT-->>IP: owner address
IP->>IP: Check not already wrapped
IP->>IP: Mint IPAsset token
IP->>NFT: safeTransferFrom(owner, IPAsset, nftTokenId)
NFT->>NFT: Transfer NFT to IPAsset (custody)
IP-->>Owner: ipTokenId
Note over Owner,IP: NFT locked in IPAsset contract<br/>Owner controls via IPAsset token
Owner->>IP: mintLicense(ipTokenId, licensee, params)
IP->>LT: mintLicense(...)
LT-->>IP: licenseId
Unwrapping NFTs
sequenceDiagram
actor Owner as IPAsset Owner
participant IP as IPAsset
participant NFT as External NFT
Note over Owner,IP: Owner wants original NFT back
Owner->>IP: unwrapNFT(ipTokenId)
IP->>IP: Check owner
IP->>IP: Check no active licenses
IP->>IP: Check no active disputes
IP->>IP: Burn IPAsset token
IP->>NFT: safeTransferFrom(IPAsset, owner, nftTokenId)
NFT-->>Owner: Original NFT returned
Note over Owner,NFT: Cannot unwrap if licenses/disputes exist
Buying a Listed License
sequenceDiagram
actor Seller
actor Buyer
participant MP as Marketplace
participant LT as LicenseToken
participant RD as RevenueDistributor
Seller->>LT: approve(Marketplace, licenseId)
Seller->>MP: createListing(nftContract, tokenId, price)
MP->>MP: Store listing
MP-->>Seller: listingId
Buyer->>MP: buyListing(listingId) + ETH
MP->>LT: safeTransferFrom(seller, buyer, licenseId)
MP->>RD: distributePayment(ipAssetId, amount, seller)
RD->>RD: Auto-detect primary/secondary sale
RD->>RD: Calculate fees & splits accordingly
RD->>RD: Update balances
MP-->>Buyer: License transferred
Note over Seller,RD: Primary sale: platform fee + split to recipients<br/>Secondary sale: royalty to IP owners, rest to seller<br/>All parties can withdraw later
Making an Offer
sequenceDiagram
actor Buyer
actor Seller
participant MP as Marketplace
participant LT as LicenseToken
participant RD as RevenueDistributor
Buyer->>MP: createOffer(nftContract, tokenId, expiry) + ETH
MP->>MP: Store offer with escrowed funds
MP-->>Buyer: offerId
Note over Seller: Seller decides to accept
Seller->>LT: approve(Marketplace, tokenId)
Seller->>MP: acceptOffer(offerId)
MP->>LT: safeTransferFrom(seller, buyer, tokenId)
MP->>RD: distributePayment(ipAssetId, amount, seller)
RD->>RD: Auto-detect primary/secondary sale
RD->>RD: Calculate fees & splits accordingly
RD->>RD: Update balances
MP-->>Seller: Offer accepted
Note over Seller,RD: Primary sale: platform fee + split to recipients<br/>Secondary sale: royalty to IP owners, rest to seller
alt Buyer cancels before acceptance
Buyer->>MP: cancelOffer(offerId)
MP->>Buyer: Refund escrowed ETH
end
Recurring Payments (Subscription Licenses)
sequenceDiagram
actor Licensee
participant MP as Marketplace
participant LT as LicenseToken
participant RD as RevenueDistributor
Note over Licensee,MP: License has paymentInterval > 0<br/>maxMissedPayments configured (default: 3)<br/>penaltyRateBPS configured (default: 500 = 5%)
loop Every payment interval
Licensee->>MP: getTotalPaymentDue(licenseContract, licenseId)
MP-->>Licensee: baseAmount, penalty (if > 3 days overdue), total
Note over Licensee,MP: Grace period: 3 days after due date<br/>No penalty if paid within grace period
Licensee->>MP: makeRecurringPayment(licenseContract, licenseId) + ETH
MP->>MP: Calculate missed payments
alt < maxMissedPayments
MP->>RD: distributePayment(ipAssetId, amount, seller)
RD->>RD: Distribute payment (typically primary sale)
MP-->>Licensee: Payment successful
else >= maxMissedPayments
MP->>LT: revokeForMissedPayments(licenseId, missedCount)
LT->>LT: Mark license as revoked
MP-->>Licensee: License auto-revoked
end
end
Dispute Resolution
sequenceDiagram
actor User
actor Arbitrator
participant GA as GovernanceArbitrator
participant LT as LicenseToken
participant IP as IPAsset
User->>GA: submitDispute(licenseId, reason, proofURI)
GA->>LT: Check license is active
GA->>IP: setDisputeStatus(tokenId, true)
GA->>GA: Create dispute record
GA-->>User: disputeId
Note over Arbitrator,GA: Within 30 days
Arbitrator->>GA: resolveDispute(disputeId, approved, reason)
GA->>GA: Update dispute status
GA-->>Arbitrator: Dispute resolved
alt Dispute approved
Arbitrator->>GA: executeRevocation(disputeId)
GA->>LT: revokeLicense(licenseId, reason)
LT->>LT: Mark license revoked
LT->>IP: updateActiveLicenseCount(tokenId, -1)
GA->>IP: setDisputeStatus(tokenId, false)
else Dispute rejected
GA->>IP: setDisputeStatus(tokenId, false)
end
Revenue Withdrawal
sequenceDiagram
actor Recipient
participant RD as RevenueDistributor
Recipient->>RD: getBalance(address)
RD-->>Recipient: balance
Recipient->>RD: withdraw()
RD->>RD: Check balance > 0
RD->>RD: Reset balance to 0
RD->>Recipient: Transfer ETH
RD-->>Recipient: Withdrawal successful