Skip to content

LicenseNFTV2

The LicenseNFTV2 contract is an ERC-721 token that represents access rights to AI models on WasiAI. Each license grants specific capabilities (API access, download) for a defined duration (perpetual or subscription).


Contract Information

Property Value
Address (Fuji) 0xC657F1B26fc56A0AA1481F502BCC6532B93d7426
Token Name WasiAI License V2
Token Symbol WASI-LIC2
Solidity Version ^0.8.24
Inherits ERC721, ERC721URIStorage, Ownable2Step

Overview

License NFTs provide an alternative to pay-per-inference. Instead of paying for each API call, users can purchase a license that grants unlimited access for a period (or forever).

License Types: - Perpetual: One-time payment, lifetime access - Subscription: Monthly payment, renewable

License Rights: - API Access: Call the inference endpoint - Download Access: Download model artifacts


Constants

uint8 public constant KIND_PERPETUAL = 0;    // One-time purchase
uint8 public constant KIND_SUBSCRIPTION = 1; // Monthly subscription

uint8 public constant RIGHTS_API = 1;        // API access (bit 0)
uint8 public constant RIGHTS_DOWNLOAD = 2;   // Download access (bit 1)

Data Structures

License

struct License {
    uint256 modelId;        // Model this license is for
    address originalBuyer;  // First purchaser (for analytics)
    uint8 kind;             // 0 = perpetual, 1 = subscription
    uint8 rights;           // Bitmask of granted rights
    uint256 expiresAt;      // Expiration timestamp (0 = never)
    bool revoked;           // Whether license has been revoked
}

State Variables

uint256 public nextTokenId = 1;              // Next license ID to mint
address public marketplace;                  // Marketplace contract (minter)
mapping(uint256 => License) public licenses; // License data by token ID

Functions

Minting

mint

Mints a new license NFT. Only callable by the Marketplace contract.

function mint(
    address to,
    uint256 modelId,
    uint8 kind,
    uint8 rights,
    uint256 duration
) external onlyMarketplace returns (uint256 tokenId)

Parameters: | Name | Type | Description | |------|------|-------------| | to | address | Recipient of the license | | modelId | uint256 | Model this license is for | | kind | uint8 | 0 = perpetual, 1 = subscription | | rights | uint8 | Rights bitmask | | duration | uint256 | Duration in seconds (0 for perpetual) |

Returns: tokenId (The minted license token ID)

Example (called by Marketplace):

uint256 licenseId = licenseNFT.mint(
    buyer,
    modelId,
    KIND_PERPETUAL,
    RIGHTS_API | RIGHTS_DOWNLOAD,
    0  // No expiration
);


Validation

isValid

Checks if a license is currently valid (not expired, not revoked).

function isValid(uint256 tokenId) external view returns (bool)

Returns: true if license is valid, false otherwise

Example:

const valid = await licenseNFT.isValid(licenseId)
if (!valid) {
  console.log('License expired or revoked')
}

hasRights

Checks if a license grants specific rights.

function hasRights(uint256 tokenId, uint8 requiredRights) external view returns (bool)

Example:

// Check if license has API access
const hasApi = await licenseNFT.hasRights(licenseId, 1)

// Check if license has download access
const hasDownload = await licenseNFT.hasRights(licenseId, 2)

// Check if license has both
const hasBoth = await licenseNFT.hasRights(licenseId, 3)

getLicensesByOwner

Returns all license IDs owned by an address.

function getLicensesByOwner(address owner) external view returns (uint256[] memory)

getLicensesForModel

Returns all license IDs for a specific model.

function getLicensesForModel(uint256 modelId) external view returns (uint256[] memory)

Renewal

renew

Extends a subscription license. Only callable by Marketplace.

function renew(uint256 tokenId, uint256 additionalDuration) external onlyMarketplace

Requirements: - License must be subscription type - License must not be revoked


Revocation

revoke

Revokes a license (e.g., for terms violation). Only callable by Marketplace.

function revoke(uint256 tokenId) external onlyMarketplace

Effects: - Sets revoked = true - License becomes invalid immediately - Token can still be transferred but grants no access


Metadata

tokenURI

Returns the metadata URI for a license.

function tokenURI(uint256 tokenId) public view override returns (string memory)

Metadata Schema:

{
  "name": "WasiAI License #123",
  "description": "License for Crypto Sentiment Analyzer",
  "image": "ipfs://...",
  "attributes": [
    { "trait_type": "Model ID", "value": "1" },
    { "trait_type": "Type", "value": "Perpetual" },
    { "trait_type": "Rights", "value": "API + Download" },
    { "trait_type": "Expires", "value": "Never" }
  ]
}


Events

event LicenseMinted(
    uint256 indexed tokenId,
    uint256 indexed modelId,
    address indexed buyer,
    uint8 kind,
    uint8 rights,
    uint256 expiresAt
);

event LicenseRenewed(
    uint256 indexed tokenId,
    uint256 newExpiresAt
);

event LicenseRevoked(
    uint256 indexed tokenId
);

Errors

error OnlyMarketplace();
error LicenseNotFound();
error LicenseExpired();
error LicenseRevoked();
error InvalidKind();
error InvalidRights();

Rights Bitmask

Rights are stored as a bitmask, allowing multiple rights per license:

Value Binary Rights
0 00 None
1 01 API only
2 10 Download only
3 11 API + Download

Checking Rights:

// Check if license has API access
bool hasApi = (license.rights & RIGHTS_API) != 0;

// Check if license has download access
bool hasDownload = (license.rights & RIGHTS_DOWNLOAD) != 0;


Integration Examples

Check User's License for Model

// Get all licenses owned by user
const licenses = await licenseNFT.getLicensesByOwner(userAddress)

// Find valid license for specific model
for (const licenseId of licenses) {
  const license = await licenseNFT.licenses(licenseId)

  if (license.modelId === targetModelId) {
    const valid = await licenseNFT.isValid(licenseId)
    if (valid) {
      console.log('User has valid license:', licenseId)
      break
    }
  }
}

Verify License Before API Call

async function verifyLicense(userAddress: string, modelId: number): Promise<boolean> {
  const licenses = await licenseNFT.getLicensesByOwner(userAddress)

  for (const licenseId of licenses) {
    const license = await licenseNFT.licenses(licenseId)

    if (
      license.modelId === modelId &&
      await licenseNFT.isValid(licenseId) &&
      await licenseNFT.hasRights(licenseId, RIGHTS_API)
    ) {
      return true
    }
  }

  return false
}

Get License Details

const license = await publicClient.readContract({
  address: LICENSE_NFT_ADDRESS,
  abi: LicenseNFTV2ABI,
  functionName: 'licenses',
  args: [licenseId]
})

console.log({
  modelId: license.modelId,
  kind: license.kind === 0 ? 'Perpetual' : 'Subscription',
  rights: license.rights,
  expiresAt: license.expiresAt === 0n ? 'Never' : new Date(Number(license.expiresAt) * 1000),
  revoked: license.revoked
})

Security Considerations

  1. Minting Restricted: Only Marketplace can mint licenses
  2. Revocation: Licenses can be revoked for terms violations
  3. Expiration Check: Always verify isValid() before granting access
  4. Transfer Safe: Licenses can be transferred, but access follows the token
  5. No Burn by User: Users cannot burn their licenses (prevents abuse)