# ZTP-3525

## Introduction

### What is the Semi Fungible Token

This standard introduces semi-fungible tokens (SFTs), which allow for dynamic partitioning of tokens into slots, creating a hybrid model that bridges fungible and non-fungible assets. The proposed contract will enhance flexibility, efficiency, and scalability for managing tokenized assets across various use cases, including financial instruments, gaming, and digital ownership.

### What is ZTP-3525

ZTP-3525 introduces slot-based token management, where tokens are grouped based on shared characteristics but retain unique balances. This allows for more complex token designs and dynamic allocation of value within a single smart contract.

* Semi-Fungibility: Tokens within the same slot act as fungible, while tokens across different slots are non-fungible.
* Slot Metadata: Each slot can hold custom metadata, enabling innovative use cases such as dynamic financial instruments or evolving assets.
* Batch Operations: The standard supports efficient batch operations, reducing gas costs and improving scalability.

ZTP3525 combines features of fungible and non-fungible tokens, enabling innovative use cases like tokenized subscriptions, multi-asset collateralized loans, dynamic NFTs, shared asset ownership, and loyalty programs. This standard allows tokens to carry both a unique identity and a fungible balance, making it ideal for applications requiring personalized attributes alongside transferable or divisible values. ZTP3525 could power advanced blockchain solutions, such as tradable subscription tokens, customizable financial instruments, interactive game assets, fractionalized ownership, and interoperable reward systems, all while enhancing flexibility and utility in tokenized ecosystems.

## Methods

The list below outlines the required methods to be implemented in the contract:

Token definition:

Notes: Token definitions are defined during the *init* procedure.

### init

| Method   |                                                   |
| -------- | ------------------------------------------------- |
| name     | Token Name. Example: “Contract Token”             |
| symbol   | Token Symbol. Example: CTK                        |
| describe | Token description: “Contract token issued by XYZ” |
| version  | Token version                                     |

Example:

```javascript
function init(input_str) {
    let paramObj = JSON.parse(input_str).params;
    Utils.assert(paramObj.name !== undefined && paramObj.name.length > 0, 'Param obj has no name.');
    Utils.assert(paramObj.symbol !== undefined && paramObj.symbol.length > 0, 'Param obj has no symbol.');
    Utils.assert(paramObj.describe !== undefined && paramObj.describe.length > 0, 'Param obj has no describe.');
    Utils.assert(paramObj.version !== undefined && paramObj.version.length > 0, 'Param obj has no version.');

    
    let totalSupplyObject = {};
    totalSupplyObject.tokens = [];
    Chain.store(TOTAL_SUPPLY, JSON.stringify(totalSupplyObject));

    paramObj.protocol = ZTP_PROTOCOL;
    Chain.store(CONTRACT_PRE, JSON.stringify(paramObj));
    return;
}
```

Function implementation:

### safeTransferFromToken <a href="#transfer" id="transfer"></a>

| Method                                             | Description                                                                                                                                                                                                                                                                                                                          |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| function safeTransferFromToken(tokenId, toAddress) | The `safeTransferFromToken(tokenId, toAddress)` function facilitates the secure transfer of a specific token, identified by its `tokenId`, from the current owner to a designated `toAddress`. This function ensures that the transfer complies with the token's rules and validates that the recipient can safely handle the token. |

```javascript
function safeTransferFromToken(tokenId, toAddress) {
    let key = getKey(TOKEN_PRE, tokenId);
    let tokenData = Chain.load(key);
    Utils.assert(tokenData !== false, 'Failed to get storage data, key: ' + key);
    tokenData = JSON.parse(tokenData);
    Utils.assert(Chain.msg.sender === tokenData.owner || tokenData.operators.includes(Chain.msg.sender), 'Only owner of the token can call this function.');
    Utils.assert(Utils.addressCheck(toAddress) === true, 'Recipient address is invalid.');

    // remove the balance of existing owner
    let ownerKey = getKey(BALANCE_PRE, tokenData.owner);
    let ownerData = Chain.load(ownerKey);
    Utils.assert(ownerData !== false, 'Failed to get storage data, key: ' + ownerKey);
    ownerData = JSON.parse(ownerData);
    ownerData.tokens = ownerData.tokens.filter(function(tokenKey) {
        return tokenKey !== key;
    });
    Chain.store(ownerKey, JSON.stringify(ownerData));

    // add the balance to new owner
    let recipientKey = getKey(BALANCE_PRE, toAddress);
    let recipientData = Chain.load(recipientKey);
    // Utils.assert(recipientData !== false, 'Failed to get storage data, key: ' + recipientKey);
    if (recipientData === false) {
        recipientData = {};
        recipientData.tokens = [];
    } else {
        recipientData = JSON.parse(recipientData);
    }
    recipientData.tokens.push(key);
    Chain.store(recipientKey, JSON.stringify(recipientData));

    // update the token owner
    tokenData.owner = toAddress;
    Chain.store(key, JSON.stringify(tokenData));
}
```

### transferFromToken <a href="#transfer" id="transfer"></a>

| Method                                         | Description                                                                                                                                                                      |
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function transferFromToken(tokenId, toAddress) | The `transferFromToken(tokenId, toAddress)` function executes the transfer of a semi-fungible token identified by `tokenId` from its current owner to the specified `toAddress`. |

Example:

```javascript
function transferFromToken(tokenId, toAddress) {
    let key = getKey(TOKEN_PRE, tokenId);
    let tokenData = Chain.load(key);
    Utils.assert(tokenData !== false, 'Failed to get storage data, key: ' + key);
    tokenData = JSON.parse(tokenData);
    Utils.assert(Chain.msg.sender === tokenData.owner || tokenData.operators.includes(Chain.msg.sender), 'Only owner of the token can call this function.');
    Utils.assert(Utils.addressCheck(toAddress) === true, 'Recipient address is invalid.');

    // remove the balance of existing owner
    let ownerKey = getKey(BALANCE_PRE, tokenData.owner);
    let ownerData = Chain.load(ownerKey);
    Utils.assert(ownerData !== false, 'Failed to get storage data, key: ' + ownerKey);
    ownerData = JSON.parse(ownerData);
    ownerData.tokens = ownerData.tokens.filter(function(tokenKey) {
        return tokenKey !== key;
    });
    Chain.store(ownerKey, JSON.stringify(ownerData));

    // add the balance to new owner
    let recipientKey = getKey(BALANCE_PRE, toAddress);
    let recipientData = Chain.load(recipientKey);
    // Utils.assert(recipientData !== false, 'Failed to get storage data, key: ' + recipientKey);
    if (recipientData === false) {
        recipientData = {};
        recipientData.tokens = [];
    } else {
        recipientData = JSON.parse(recipientData);
    }
    recipientData.tokens.push(key);
    Chain.store(recipientKey, JSON.stringify(recipientData));

    // update the token owner
    tokenData.owner = toAddress;
    Chain.store(key, JSON.stringify(tokenData));
}
```

### safeTransferFromBalance

| Method                                                           | Description                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function safeTransferFromBalance(fromTokenId, toTokenId, amount) | The `safeTransferFromBalance(fromTokenId, toTokenId, amount)` function securely transfers a specified `amount` of balance from one semi-fungible token (`fromTokenId`) to another (`toTokenId`). Unlike transferring an entire token, this function focuses on the fungible value associated with the tokens, ensuring that the balance is accurately moved while preserving the unique identity of both the source and destination tokens. |

Example:

```javascript
function safeTransferFromBalance(fromTokenId, toTokenId, amount) {
    let senderTokenKey = getKey(TOKEN_PRE, fromTokenId);
    let senderTokenData = Chain.load(senderTokenKey);
    Utils.assert(senderTokenData !== false, 'Failed to get storage data, key: ' + senderTokenKey);
    senderTokenData = JSON.parse(senderTokenData);
    Utils.assert(Chain.msg.sender === senderTokenData.owner || senderTokenData.operators.includes(Chain.msg.sender), 'Only owner or operator of the token can call this function.');
    Utils.assert(Utils.int256Compare(senderTokenData.balance, amount) > 0, 'Insufficient balance to transfer amount.');

    let recipientTokenKey = getKey(TOKEN_PRE, toTokenId);
    let recipientTokenData = JSON.parse(Chain.load(recipientTokenKey));
    Utils.assert(recipientTokenData !== false, 'Failed to get storage data, key: ' + recipientTokenKey);
    recipientTokenData = JSON.parse(recipientTokenData);

    // subtract sender balance
    senderTokenData.balance = Utils.int256Sub(senderTokenData.balance, amount);
    Chain.store(senderTokenKey, JSON.stringify(senderTokenData));

    // add recipient balance
    recipientTokenData.balance = Utils.int256Add(recipientTokenData.balance, amount);
    Chain.store(recipientTokenKey, JSON.stringify(recipientTokenData));
}
```

### transferFromBalance

| Method                                                       | Description                                                                                                                                                                                                                                                                                                                                                                 |
| ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function transferFromBalance(fromTokenId, toTokenId, amount) | The `transferFromBalance(fromTokenId, toTokenId, amount)` function transfers a specified `amount` of balance from one semi-fungible token (`fromTokenId`) to another (`toTokenId`). Unlike the safer version, `safeTransferFromBalance`, this function does not include additional checks for the recipient's ability to handle the balance or compatibility of the tokens. |

Example:

```javascript
function transferFromBalance(fromTokenId, toTokenId, amount) {
    let senderTokenKey = getKey(TOKEN_PRE, fromTokenId);
    let senderTokenData = Chain.load(senderTokenKey);
    Utils.assert(senderTokenData !== false, 'Failed to get storage data, key: ' + senderTokenKey);
    senderTokenData = JSON.parse(senderTokenData);
    Utils.assert(Chain.msg.sender === senderTokenData.owner || senderTokenData.operators.includes(Chain.msg.sender), 'Only owner or operator of the token can call this function.');
    Utils.assert(Utils.int256Compare(senderTokenData.balance, amount) > 0, 'Insufficient balance to transfer amount.');

    let recipientTokenKey = getKey(TOKEN_PRE, toTokenId);
    let recipientTokenData = JSON.parse(Chain.load(recipientTokenKey));
    Utils.assert(recipientTokenData !== false, 'Failed to get storage data, key: ' + recipientTokenKey);
    recipientTokenData = JSON.parse(recipientTokenData);

    // subtract sender balance
    senderTokenData.balance = Utils.int256Sub(senderTokenData.balance, amount);
    Chain.store(senderTokenKey, JSON.stringify(senderTokenData));

    // add recipient balance
    recipientTokenData.balance = Utils.int256Add(recipientTokenData.balance, amount);
    Chain.store(recipientTokenKey, JSON.stringify(recipientTokenData));
}

```

### approve

| Method                              | Description                                                                                                                                                                                                                                                                                                                                                  |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| function approve(operator, tokenId) | The `approve(operator, tokenId)` function allows the token owner to grant approval for an `operator` to manage or transfer a specific token identified by `tokenId` on their behalf. This approval is specific to the given token, meaning the operator is authorized to perform actions like transferring the token, but only for that particular token ID. |

Example:

```javascript
function approve(operator, tokenId) {
    Utils.assert(Utils.addressCheck(operator) === true, 'Operator address is invalid.');
    let tokenData = Chain.load(getKey(TOKEN_PRE, tokenId));
    Utils.assert(tokenData !== false, 'Token does not exist.');
    tokenData = JSON.parse(tokenData);
    Utils.assert(Chain.msg.sender === tokenData.owner, 'Only owner of the token is allowed to call this function.');
    tokenData.operators.push(operator);
    Chain.store(getKey(TOKEN_PRE, tokenId), JSON.stringify(tokenData));
}

```

### approveForSlot

| Method                                    | Description                                                                                                                                                                                                                                                                                                                          |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| function approveForSlot(operator, slotId) | The `approveForSlot(operator, slotId)` function allows the token owner to grant permission to an `operator` to manage all tokens associated with a specific `slotId`. A slot in the ZTP-3525 standard often represents a grouping or categorization of tokens sharing common attributes, such as a shared purpose or financial terms |

Example:

```javascript
function approveForSlot(operator, slotId) {
    Utils.assert(Utils.addressCheck(operator) === true, 'Operator address is invalid.');
    let slotKey = getKey(SLOT_PRE, slotId);
    let slotData = Chain.load(slotKey);
    Utils.assert(slotData !== false, 'Slot key does not exist: ' + slotKey);
    slotData = JSON.parse(slotData);
    Utils.assert(Chain.msg.sender === slotData.owner, 'Only owner of the slot is allowed to call this function.');
    slotData.operators.push(operator);
    Chain.store(slotKey, JSON.stringify(slotData));
}
```

### approveForAll

| Method                                  | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function approveForAll(owner, operator) | The `approveForAll(owner, operator)` function allows the `owner` of tokens to grant an `operator` permission to manage all of their tokens without needing individual approvals for each one. This function is often used in scenarios where the owner wants to delegate broad control to an operator, enabling the operator to perform actions like transferring, staking, or interacting with tokens on behalf of the owner.(to), and MUST fire the Transfer event. |

Example:

```javascript
function approveForAll(owner, operator) {
    Utils.assert(Utils.addressCheck(owner) === true, 'Owner address is invalid.');
    Utils.assert(Utils.addressCheck(operator) === true, 'Operator address is invalid.');
    let balanceData = Chain.load(getKey(BALANCE_PRE, owner));
    Utils.assert(balanceData !== false, `Balance data for address "${owner}" does not exist.`);
    Utils.assert(Chain.msg.sender === owner, 'Only the owner is allowed to call this function.');
    balanceData = JSON.parse(balanceData);
    balanceData.tokens.forEach(function(tokenKey) {
        let tokenData = JSON.parse(Chain.load(tokenKey));
        Utils.assert(tokenData.owner === owner, `Token ID ${tokenKey} is not owned by ${owner}.`);
        tokenData.operators.push(operator);
        Chain.store(tokenKey, JSON.stringify(tokenData));    
    });
}
```

### setSlotMetadata

| Method                                                     | Description                                                                                                                                                                                                                                                                                                                                                    |
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function setSlotMetadata(slotId, tokens, operators, owner) | The `setSlotMetadata(slotId, tokens, operators, owner)` function allows for the configuration or update of metadata for a specific `slotId` in a token contract. It enables the assignment of tokens to a slot, designates authorized operators to manage or interact with the tokens, and specifies the owner or controlling address responsible for the slot |

Example:

```javascript
function setSlotMetadata(slotId, tokens, operators, owner) {
    Utils.assert(Utils.addressCheck(owner) === true, 'Owner address is invalid.');
    operators.forEach(function(operator) {
        Utils.assert(Utils.addressCheck(operator) === true, `Operator address "${operator}" is invalid.`);
    });

    let slotData = Chain.load(getKey(SLOT_PRE, slotId));
    // slot does not exist yet
    if (slotData === false) {
        let slotObject = {};
        slotObject.id = slotId;
        slotObject.tokens = tokens;
        slotObject.operators = operators;
        slotObject.owner = owner;

        Chain.store(getKey(SLOT_PRE, slotId), JSON.stringify(slotObject));
    } else {
        slotData = JSON.parse(slotData);
        slotData.tokens = tokens;
        slotData.operators = operators;
        slotData.owner = owner;

        Chain.store(getKey(SLOT_PRE, slotId), JSON.stringify(slotData));
    }
}
```

### mintToken

| Method                                                  | Description                                                                                                                                                                                                                                                                                                                                            |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| function mintToken(toAddress, slotId, tokenId, balance) | The `mintToken(toAddress, slotId, tokenId, balance)` function is used to mint a new semi-fungible token in a blockchain contract. It assigns a specific `tokenId` to the newly created token, associates it with a particular `slotId` (which groups the token with others that share common attributes), and sets the initial `balance` of the token. |

Example:

```javascript
function mintToken(toAddress, slotId, tokenId, balance) {
    Utils.assert(Utils.addressCheck(toAddress) === true, 'Recipient address is invalid.');
    let tokenKey = getKey(TOKEN_PRE, tokenId);
    let tokenData = Chain.load(tokenKey);
    Utils.assert(tokenData === false, 'Token ID already exists.');
    let slotKey = getKey(SLOT_PRE, slotId);
    let slotData = Chain.load(slotKey);
    // slot does not exist
    if (slotData === false) {
        // create slot
        slotData = {};
        slotData.id = slotId;
        slotData.tokens = [];
        slotData.operators = [];
        slotData.owner = toAddress;
    } else {
        slotData = JSON.parse(slotData);
    }

    // create object
    let tokenObject = {};
    tokenObject.id = tokenId;
    tokenObject.slotId = slotData.id;
    tokenObject.balance = balance;
    tokenObject.owner = toAddress;
    tokenObject.operators = [];

    // store token object
    Chain.store(tokenKey, JSON.stringify(tokenObject));

    // store token key in slot
    slotData.tokens.push(tokenKey);
    Chain.store(slotKey, JSON.stringify(slotData));

    // update balance for recipient
    let recipientKey = getKey(BALANCE_PRE, toAddress);
    let recipientData = Chain.load(recipientKey);

    if (recipientData === false) {
        recipientData = {};
        recipientData.tokens = [];
    } 
    else {
        recipientData = JSON.parse(recipientData);
    }
    recipientData.tokens.push(tokenKey);
    Chain.store(recipientKey, JSON.stringify(recipientData));

    // update total supply
    let currentSupply = totalSupply();
    currentSupply.tokens.push(tokenKey);
    Chain.store(TOTAL_SUPPLY, JSON.stringify(currentSupply));
}
```

### mintBalance

| Method                                | Description                                                                                                                                                                                                                                                                 |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function mintBalance(tokenId, amount) | The `mintBalance(tokenId, amount)` function is used to mint additional balance to an existing semi-fungible token identified by its `tokenId`. This function increases the token's balance by the specified `amount`, without changing the token's unique identity or slot. |

Example:

```javascript
function mintBalance(tokenId, amount) {
    let tokenKey = getKey(TOKEN_PRE, tokenId);
    let tokenData = Chain.load(tokenKey);
    Utils.assert(tokenData !== false, 'Token does not exist.');
    tokenData = JSON.parse(tokenData);
    Utils.assert(Chain.msg.sender === tokenData.owner || tokenData.operators.includes(Chain.msg.sender), 'Only owner of the token can call this function.');
    tokenData.balance = Utils.int256Add(tokenData.balance, amount);
    Chain.store(tokenKey, JSON.stringify(tokenData));
}
```

### burnToken

| Method                      | Description                                                                                                                                                                                                                                                                                              |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function burnToken(tokenId) | The `burnToken(tokenId)` function is used to permanently destroy or "burn" a specific semi-fungible token identified by its `tokenId`. This function reduces the total supply of tokens by removing the specified token from circulation, effectively making it inaccessible for future use or transfer. |

Example:

```javascript
function burnToken(tokenId) {
    let tokenKey = getKey(TOKEN_PRE, tokenId);
    let tokenData = Chain.load(tokenKey);
    Utils.assert(tokenData !== false, 'Token does not exist.');
    tokenData = JSON.parse(tokenData);
    Utils.assert(Chain.msg.sender === tokenData.owner || tokenData.operators.includes(Chain.msg.sender), 'Only owner of the token can call this function.');

    // remove token key from slot
    let slotKey = getKey(SLOT_PRE, tokenData.slotId);
    let slotData = Chain.load(slotKey);
    Utils.assert(slotData !== false, 'Slot does not exist.');
    slotData = JSON.parse(slotData);
    slotData.tokens = slotData.tokens.filter(function(token) {
        return token !== tokenKey;
    });
    Chain.store(slotKey, JSON.stringify(slotData));

    // remove token key from owner
    let ownerKey = getKey(BALANCE_PRE, tokenData.owner);
    let ownerData = Chain.load(ownerKey);
    Utils.assert(ownerData !== false, 'Owner data does not exist.');
    ownerData = JSON.parse(ownerData);
    ownerData.tokens = ownerData.tokens.filter(function(token) {
        return token !== tokenKey;
    });
    Chain.store(ownerKey, JSON.stringify(ownerData));

    // update total supply
    let currentSupply = totalSupply();
    currentSupply.tokens = currentSupply.tokens.filter(function(token) {
        return token !== tokenKey;
    });

    Chain.store(TOTAL_SUPPLY, JSON.stringify(currentSupply));

    // remove token from Chain.store
    Chain.del(tokenKey);
}

```

### burnBalance

| Method                                | Description                                                                                                                                                                                                                                                                                                                                                                                  |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function burnBalance(tokenId, amount) | The `burnBalance(tokenId, amount)` function is used to destroy or "burn" a specified amount of balance from an existing semi-fungible token identified by its `tokenId`. Unlike burning the entire token, this function reduces the token's balance by the given `amount`, effectively decreasing the fungible value associated with the token while retaining its unique identity and slot. |

Example:

```javascript
function burnBalance(tokenId, amount) {
    let tokenKey = getKey(TOKEN_PRE, tokenId);
    let tokenData = Chain.load(tokenKey);
    Utils.assert(tokenData !== false, 'Token does not exist.');
    tokenData = JSON.parse(tokenData);
    Utils.assert(Chain.msg.sender === tokenData.owner || tokenData.operators.includes(Chain.msg.sender), 'Only owner of the token can call this function.');
    tokenData.balance = Utils.int256Sub(tokenData.balance, amount);
    Chain.store(tokenKey, JSON.stringify(tokenData));
}
```

### balanceOf

| Method                      | Description                                                                                                                                                                                                                                   |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function balanceOf(address) | The `balanceOf(address)` function returns the total balance of tokens held by a specific address. In the context of token standards like ZTP-20 or ZTP-3525, it provides the amount of tokens (or the fungible balance) that an address owns. |

Example:

```javascript
function balanceOf(address) {
    let key = getKey(BALANCE_PRE, address);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    return data === false ? 0 : JSON.parse(data).tokens.length;
}
```

### ownerOf

| Method                    | Description                                                                                                                                                                                                                                                          |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function ownerOf(tokenId) | The `ownerOf(tokenId)` function returns the address of the owner of a specific token identified by its `tokenId`. This function is commonly used in ZTP-721 (NFT) and ZTP-3525 (semi-fungible) token contracts to determine the current owner of a particular token. |

Example:

```javascript
function ownerOf(tokenId) {
    let key = getKey(TOKEN_PRE, tokenId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    return data === false ? "Could not find owner." : JSON.parse(data).owner;
}

```

### getApproved

| Method                        | Description                                                                                                                                                                                             |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function getApproved(tokenId) | The `getApproved(tokenId)`function returns the address of the approved operator for a specific token identified by its `tokenId`. This address is allowed to transfer the token on behalf of the owner. |

Example:

```javascript
function getApproved(tokenId) {
    let key = getKey(TOKEN_PRE, tokenId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    return data === false ? "Could not find token." : JSON.parse(data).operators;
}
```

### isApprovedForAll

| Method                                     | Description                                                                                                                                                                                                                                                                    |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| functionis ApprovedForAll(owner, operator) | The `isApprovedForAll(owner, operator)` function checks whether an `operator` is approved to manage all tokens owned by the `owner`. This approval is global, meaning the operator can manage any token held by the owner without needing individual approvals for each token. |

Example:

```javascript
function isApprovedForAll(owner, operator) {
    let key = getKey(BALANCE_PRE, owner);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    data = JSON.parse(data);
    if (data.tokens.length !== 0) {
        let result = data.tokens.every(function(token) {
            let tokenData = Chain.load(getKey(TOKEN_PRE, token.id));
            if (tokenData !== false) {
                tokenData = JSON.parse(tokenData);
                if (tokenData.operator !== operator) {
                    return false; // Stop checking if operator does not match
                }
            }
            return true; // Continue checking
        });
        return result;
    } else {
        return false;
    }
}
```

### isApprovedForSlot

| Method                                                | Description                                                                                                                                                                                                                                                                                                                                                                                                             |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function isApprovedForSlot(slotId, operator) , value) | The `isApprovedForSlot(slotId, operator)` function checks whether an `operator` is authorized to manage or interact with all tokens associated with a specific `slotId`. In token standards like ZTP-3525, where tokens are grouped into slots with shared attributes, this function verifies if the operator has permission to perform actions on all tokens within that slot, such as transfers or other interactions |

Example:

```javascript
function isApprovedForSlot(slotId, operator) {
    let key = getKey(SLOT_PRE, slotId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    data = JSON.parse(data);
    let result = data.operators.includes(operator);
    return result;
}
```

### slotOf

| Method                   | Description                                                                                                                                                                                                                              |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function slotOf(tokenId) | The `slotOf(tokenId)` function retrieves the specific slot associated with a given `tokenId` in the ZTP-3525 token standard. A slot represents a category or grouping of tokens with shared attributes, such as value, purpose, or type. |

Example:

```javascript
function slotOf(tokenId) {
    let key = getKey(TOKEN_PRE, tokenId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    return data === false ? "Could not find token." : JSON.parse(data).slotId;
}
```

### contractInfo

| Method                  | Description                                                                                                                                                                                                                                                          |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function contractInfo() | The `contractInfo()` function typically returns metadata or key details about the smart contract that governs the token. This can include information such as the contract's name, symbol, total supply, version, or other relevant details defined by the contract. |

Example:

```javascript
function contractInfo() {
    let data = Chain.load(CONTRACT_PRE);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + CONTRACT_PRE);
    if (data === false) {
        return false;
    }
    return JSON.parse(data);
}
```

### tokenURI

| Method                     | Description                                                                                                                                                                                                                                       |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function tokenURI(tokenId) | The `tokenURI(tokenId)` function retrieves the Uniform Resource Identifier (URI) associated with a specific `tokenId` in a token contract. The URI typically points to a location where metadata about the token is stored, often in JSON format. |

Example:

```javascript
function tokenURI(tokenId) {
    let key = getKey(TOKEN_PRE, tokenId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    return data === false ? "Could not find token." : JSON.parse(data);
}

```

### slotURI

| Method                   | Description                                                                                                                                                                     |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function slotURI(slotId) | The `slotURI(slotId)` function retrieves the Uniform Resource Identifier (URI) associated with a specific `slotId` in a token contract, typically within the ZTP-3525 standard. |

Example:

```javascript
function slotURI(slotId) {
    let key = getKey(SLOT_PRE, slotId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    if (data === false) {
        return false;
    }
    return JSON.parse(data);
}
```

### totalSupply

| Method                 | Description                                                                                                                                                                    |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| function totalSupply() | The `totalSupply()` function returns the total number of tokens that exist within a specific contract or token standard, often used to represent the total circulating supply. |

Example:

```javascript
function totalSupply() {
    let data = Chain.load(TOTAL_SUPPLY);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + TOTAL_SUPPLY);
    return data === false ? [] : JSON.parse(data);
}
```

### tokensOfOwner

| Method                          | Description                                                                                                                                                                                                                                            |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| function tokensOfOwner(address) | The `tokensOfOwner(address)` function returns a list of token IDs owned by a specific address. In the context of token standards like ZTP-3525, ZTP-721, or ZTP-20, it allows users to query which tokens are owned by a particular wallet or address. |

Example:

```javascript
function tokensOfOwner(address) {
    let key = getKey(BALANCE_PRE, address);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    return data === false ? "Could not find address." : JSON.parse(data).tokens;
}
```

### tokensInSlot

| Method                        | Description                                                                                                                                                                                                                                                                 |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function tokensInSlot(slotId) | The `tokensInSlot(slotId)` function returns a list of all token IDs associated with a specific `slotId`. In token standards like ZTP-3525, a slot represents a grouping or categorisation of tokens that share common attributes, such as purpose, value, or functionality. |

Example:

```javascript
function tokensInSlot(slotId) {
    let key = getKey(SLOT_PRE, slotId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    if (data === false) {
        return false;
    }
    return JSON.parse(data).tokens;
}

```

### getSlotMetadata

| Method                           | Description                                                                                                                                                                                                                                                                                                        |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| function getSlotMetadata(slotId) | The `getSlotMetadata(slotId)` function retrieves the metadata associated with a specific `slotId` in a token contract. This metadata can include details about the attributes, rights, or characteristics that are common to all tokens within that slot, such as its purpose, associated values, or restrictions. |

Example:

```javascript
function getSlotMetadata(slotId) {
    let key = getKey(SLOT_PRE, slotId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    if (data === false) {
        return false;
    }
    return JSON.parse(data);
}
```

### query

| Method                     | Description                                                                                                                                                                                                                                                                                                 |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function query(input\_str) | The `query(input_str)`function is typically used to search or retrieve information based on a given input string (`input_str`). The function is designed to process the input, which can be a request or a query, and return relevant results or data from a database, smart contract, or external service. |

Example:

```javascript
function query(input_str) {
    let result = {};
    let input = JSON.parse(input_str);
    if (input.method === "balanceOf") {
        result.balance = balanceOf(input.params.address);
    } else if (input.method === "ownerOf") {
        result.owner = ownerOf(input.params.tokenId);
    } else if (input.method === "getApproved") {
        result.operators = getApproved(input.params.tokenId);
    } else if (input.method === "isApprovedForAll") {
        result.approvedForAll = isApprovedForAll(input.params.owner, input.params.operator);
    } else if (input.method === "isApprovedForSlot") {
        result.isApprovedForSlot = isApprovedForSlot(input.params.slotId, input.params.operator);
    } else if (input.method === "slotOf") {
        result.slot = slotOf(input.params.tokenId);
    } else if (input.method === "contractInfo") {
        result.contractInfo = contractInfo();
    } else if (input.method === "tokenURI") {
        result.tokenURI = tokenURI(input.params.tokenId);
    } else if (input.method === "slotURI") {
        result.slotURI = slotURI(input.params.slotId);
    } else if (input.method === "totalSupply") {
        result.totalSupply = totalSupply();
    } else if (input.method === "tokensOfOwner") {
        result.tokens = tokensOfOwner(input.params.address);
    } else if (input.method === "tokensInSlot") {
        result.tokens = tokensInSlot(input.params.slotId);
    } else if (input.method === "getSlotMetadata") {
        result.slotMetadata = getSlotMetadata(input.params.slotId);
    } else {
        throw `Error matching strings. Input: ${input}. Method: ${input.method}. Parameters: ${input.params}`;
    }

    return JSON.stringify(result);
}
```

### main

| Method                    | Description                                                                                                                                                                                                                                                             |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| function main(input\_str) | The `main(input_str)` function serves as the entry point for executing a process that takes an input string (`input_str`). This function processes the input, which could be a query, command, or data, and then executes a defined set of actions based on that input. |

Example:

```javascript
function main(input_str) {
    let input = JSON.parse(input_str);
    if (input.method === "safeTransferFromToken") {
        safeTransferFromToken(input.params.tokenId, input.params.toAddress);
    } else if (input.method === "transferFromToken") {
        transferFromToken(input.params.tokenId, input.params.toAddress);
    } else if (input.method === "safeTransferFromBalance") {
        safeTransferFromBalance(input.params.fromTokenId, input.params.toTokenId, input.params.amount);
    } else if (input.method === "transferFromBalance") {
        transferFromBalance(input.params.fromTokenId, input.params.toTokenId, input.params.amount);
    } else if (input.method === "approve") {
        approve(input.params.operator, input.params.tokenId);
    } else if (input.method === "approveForSlot") {
        approveForSlot(input.params.operator, input.params.slotId);
    } else if (input.method === "approveForAll") {
        approveForAll(input.params.owner, input.params.operator);
    } else if (input.method === "setSlotMetadata") {
        setSlotMetadata(input.params.slotId, input.params.tokens, input.params.operators, input.params.owner);
    } else if (input.method === "mintToken") {
        mintToken(input.params.toAddress, input.params.slotId, input.params.tokenId, input.params.balance);
    } else if (input.method === "mintBalance") {
        mintBalance(input.params.tokenId, input.params.amount);
    } else if (input.method === "burnToken") {
        burnToken(input.params.tokenId);
    } else if (input.method === "burnBalance") {
        burnBalance(input.params.tokenId, input.params.amount);
    } else {
        throw `Error matching strings. Input: ${input}. Method: ${input.method}. Parameters: ${input.params}`;
    }
}
```

## Full ZTP-3525 contract example:

```javascript
'use strict';

const SLOT_PRE = 'slot';
const TOKEN_PRE = 'token';
const BALANCE_PRE = 'balance';
const CONTRACT_PRE = 'contract_info';
const TOTAL_SUPPLY = 'total_supply';
const ZTP_PROTOCOL = 'ztp3525';

// key generating function
function getKey(first, second, third = '') {
    return (third === '') ? (first + '_' + second) : (first + '_' + second + '_' + third);
}

// get a list of all the tokens belonging to this contract
function totalSupply() {
    let data = Chain.load(TOTAL_SUPPLY);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + TOTAL_SUPPLY);
    return data === false ? [] : JSON.parse(data);
}

// returns the number of tokens for a given address
function balanceOf(address) {
    let key = getKey(BALANCE_PRE, address);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    return data === false ? 0 : JSON.parse(data).tokens.length;
}

// returns list of tokens owned by the given address
function tokensOfOwner(address) {
    let key = getKey(BALANCE_PRE, address);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    return data === false ? "Could not find address." : JSON.parse(data).tokens;
}

// get the owner of a given token id
function ownerOf(tokenId) {
    let key = getKey(TOKEN_PRE, tokenId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    return data === false ? "Could not find owner." : JSON.parse(data).owner;
}

// returns the slot ID of a given token
function slotOf(tokenId) {
    let key = getKey(TOKEN_PRE, tokenId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    return data === false ? "Could not find token." : JSON.parse(data).slotId;
}

// returns contract information
function contractInfo() {
    let data = Chain.load(CONTRACT_PRE);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + CONTRACT_PRE);
    if (data === false) {
        return false;
    }
    return JSON.parse(data);
}

// return token metadata
function tokenURI(tokenId) {
    let key = getKey(TOKEN_PRE, tokenId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    return data === false ? "Could not find token." : JSON.parse(data);
}


// get the approved operator addresses with the given token id
function getApproved(tokenId) {
    let key = getKey(TOKEN_PRE, tokenId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    return data === false ? "Could not find token." : JSON.parse(data).operators;
}

// function to check whether an operator is approved to manage all tokens owned by the owner
function isApprovedForAll(owner, operator) {
    let key = getKey(BALANCE_PRE, owner);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    data = JSON.parse(data);
    if (data.tokens.length !== 0) {
        let result = data.tokens.every(function(token) {
            let tokenData = Chain.load(getKey(TOKEN_PRE, token.id));
            if (tokenData !== false) {
                tokenData = JSON.parse(tokenData);
                if (tokenData.operator !== operator) {
                    return false; // Stop checking if operator does not match
                }
            }
            return true; // Continue checking
        });
        return result;
    } else {
        return false;
    }
}

// function to check whether an operator is approved to manage the slot owned by the owner
function isApprovedForSlot(slotId, operator) {
    let key = getKey(SLOT_PRE, slotId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    data = JSON.parse(data);
    let result = data.operators.includes(operator);
    return result;
}

// approves an operator to a single token 
function approve(operator, tokenId) {
    Utils.assert(Utils.addressCheck(operator) === true, 'Operator address is invalid.');
    let tokenData = Chain.load(getKey(TOKEN_PRE, tokenId));
    Utils.assert(tokenData !== false, 'Token does not exist.');
    tokenData = JSON.parse(tokenData);
    Utils.assert(Chain.msg.sender === tokenData.owner, 'Only owner of the token is allowed to call this function.');
    tokenData.operators.push(operator);
    Chain.store(getKey(TOKEN_PRE, tokenId), JSON.stringify(tokenData));
}

// approves an operator to a slot
function approveForSlot(operator, slotId) {
    Utils.assert(Utils.addressCheck(operator) === true, 'Operator address is invalid.');
    let slotKey = getKey(SLOT_PRE, slotId);
    let slotData = Chain.load(slotKey);
    Utils.assert(slotData !== false, 'Slot key does not exist: ' + slotKey);
    slotData = JSON.parse(slotData);
    Utils.assert(Chain.msg.sender === slotData.owner, 'Only owner of the slot is allowed to call this function.');
    slotData.operators.push(operator);
    Chain.store(slotKey, JSON.stringify(slotData));
}

// approves an operator to all the tokens owned by the given owner
function approveForAll(owner, operator) {
    Utils.assert(Utils.addressCheck(owner) === true, 'Owner address is invalid.');
    Utils.assert(Utils.addressCheck(operator) === true, 'Operator address is invalid.');
    let balanceData = Chain.load(getKey(BALANCE_PRE, owner));
    Utils.assert(balanceData !== false, `Balance data for address "${owner}" does not exist.`);
    Utils.assert(Chain.msg.sender === owner, 'Only the owner is allowed to call this function.');
    balanceData = JSON.parse(balanceData);
    balanceData.tokens.forEach(function(tokenKey) {
        let tokenData = JSON.parse(Chain.load(tokenKey));
        Utils.assert(tokenData.owner === owner, `Token ID ${tokenKey} is not owned by ${owner}.`);
        tokenData.operators.push(operator);
        Chain.store(tokenKey, JSON.stringify(tokenData));    
    });
}

// return slot metadata
function slotURI(slotId) {
    let key = getKey(SLOT_PRE, slotId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    if (data === false) {
        return false;
    }
    return JSON.parse(data);
}

// return slot metadata
function getSlotMetadata(slotId) {
    let key = getKey(SLOT_PRE, slotId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    if (data === false) {
        return false;
    }
    return JSON.parse(data);
}

// returns list of tokens in the slot
function tokensInSlot(slotId) {
    let key = getKey(SLOT_PRE, slotId);
    let data = Chain.load(key);
    Utils.assert(data !== false, 'Failed to get storage data, key: ' + key);
    if (data === false) {
        return false;
    }
    return JSON.parse(data).tokens;
}

// set slot metadata
function setSlotMetadata(slotId, tokens, operators, owner) {
    Utils.assert(Utils.addressCheck(owner) === true, 'Owner address is invalid.');
    operators.forEach(function(operator) {
        Utils.assert(Utils.addressCheck(operator) === true, `Operator address "${operator}" is invalid.`);
    });

    let slotData = Chain.load(getKey(SLOT_PRE, slotId));
    // slot does not exist yet
    if (slotData === false) {
        let slotObject = {};
        slotObject.id = slotId;
        slotObject.tokens = tokens;
        slotObject.operators = operators;
        slotObject.owner = owner;

        Chain.store(getKey(SLOT_PRE, slotId), JSON.stringify(slotObject));
    } else {
        slotData = JSON.parse(slotData);
        slotData.tokens = tokens;
        slotData.operators = operators;
        slotData.owner = owner;

        Chain.store(getKey(SLOT_PRE, slotId), JSON.stringify(slotData));
    }
}

// creates a new token under a slot
function mintToken(toAddress, slotId, tokenId, balance) {
    Utils.assert(Utils.addressCheck(toAddress) === true, 'Recipient address is invalid.');
    let tokenKey = getKey(TOKEN_PRE, tokenId);
    let tokenData = Chain.load(tokenKey);
    Utils.assert(tokenData === false, 'Token ID already exists.');
    let slotKey = getKey(SLOT_PRE, slotId);
    let slotData = Chain.load(slotKey);
    // slot does not exist
    if (slotData === false) {
        // create slot
        slotData = {};
        slotData.id = slotId;
        slotData.tokens = [];
        slotData.operators = [];
        slotData.owner = toAddress;
    } else {
        slotData = JSON.parse(slotData);
    }

    // create object
    let tokenObject = {};
    tokenObject.id = tokenId;
    tokenObject.slotId = slotData.id;
    tokenObject.balance = balance;
    tokenObject.owner = toAddress;
    tokenObject.operators = [];

    // store token object
    Chain.store(tokenKey, JSON.stringify(tokenObject));

    // store token key in slot
    slotData.tokens.push(tokenKey);
    Chain.store(slotKey, JSON.stringify(slotData));

    // update balance for recipient
    let recipientKey = getKey(BALANCE_PRE, toAddress);
    let recipientData = Chain.load(recipientKey);

    if (recipientData === false) {
        recipientData = {};
        recipientData.tokens = [];
    } 
    else {
        recipientData = JSON.parse(recipientData);
    }
    recipientData.tokens.push(tokenKey);
    Chain.store(recipientKey, JSON.stringify(recipientData));

    // update total supply
    let currentSupply = totalSupply();
    currentSupply.tokens.push(tokenKey);
    Chain.store(TOTAL_SUPPLY, JSON.stringify(currentSupply));
}

// increase balance of token by "amount"
function mintBalance(tokenId, amount) {
    let tokenKey = getKey(TOKEN_PRE, tokenId);
    let tokenData = Chain.load(tokenKey);
    Utils.assert(tokenData !== false, 'Token does not exist.');
    tokenData = JSON.parse(tokenData);
    Utils.assert(Chain.msg.sender === tokenData.owner || tokenData.operators.includes(Chain.msg.sender), 'Only owner of the token can call this function.');
    tokenData.balance = Utils.int256Add(tokenData.balance, amount);
    Chain.store(tokenKey, JSON.stringify(tokenData));
}

// burn an entire token
function burnToken(tokenId) {
    let tokenKey = getKey(TOKEN_PRE, tokenId);
    let tokenData = Chain.load(tokenKey);
    Utils.assert(tokenData !== false, 'Token does not exist.');
    tokenData = JSON.parse(tokenData);
    Utils.assert(Chain.msg.sender === tokenData.owner || tokenData.operators.includes(Chain.msg.sender), 'Only owner of the token can call this function.');

    // remove token key from slot
    let slotKey = getKey(SLOT_PRE, tokenData.slotId);
    let slotData = Chain.load(slotKey);
    Utils.assert(slotData !== false, 'Slot does not exist.');
    slotData = JSON.parse(slotData);
    slotData.tokens = slotData.tokens.filter(function(token) {
        return token !== tokenKey;
    });
    Chain.store(slotKey, JSON.stringify(slotData));

    // remove token key from owner
    let ownerKey = getKey(BALANCE_PRE, tokenData.owner);
    let ownerData = Chain.load(ownerKey);
    Utils.assert(ownerData !== false, 'Owner data does not exist.');
    ownerData = JSON.parse(ownerData);
    ownerData.tokens = ownerData.tokens.filter(function(token) {
        return token !== tokenKey;
    });
    Chain.store(ownerKey, JSON.stringify(ownerData));

    // update total supply
    let currentSupply = totalSupply();
    currentSupply.tokens = currentSupply.tokens.filter(function(token) {
        return token !== tokenKey;
    });

    Chain.store(TOTAL_SUPPLY, JSON.stringify(currentSupply));

    // remove token from Chain.store
    Chain.del(tokenKey);
}

// decrease a portion (specified by "amount") of the token's balance
function burnBalance(tokenId, amount) {
    let tokenKey = getKey(TOKEN_PRE, tokenId);
    let tokenData = Chain.load(tokenKey);
    Utils.assert(tokenData !== false, 'Token does not exist.');
    tokenData = JSON.parse(tokenData);
    Utils.assert(Chain.msg.sender === tokenData.owner || tokenData.operators.includes(Chain.msg.sender), 'Only owner of the token can call this function.');
    tokenData.balance = Utils.int256Sub(tokenData.balance, amount);
    Chain.store(tokenKey, JSON.stringify(tokenData));
}

// same as transferFrom
function safeTransferFromBalance(fromTokenId, toTokenId, amount) {
    let senderTokenKey = getKey(TOKEN_PRE, fromTokenId);
    let senderTokenData = Chain.load(senderTokenKey);
    Utils.assert(senderTokenData !== false, 'Failed to get storage data, key: ' + senderTokenKey);
    senderTokenData = JSON.parse(senderTokenData);
    Utils.assert(Chain.msg.sender === senderTokenData.owner || senderTokenData.operators.includes(Chain.msg.sender), 'Only owner or operator of the token can call this function.');
    Utils.assert(Utils.int256Compare(senderTokenData.balance, amount) > 0, 'Insufficient balance to transfer amount.');

    let recipientTokenKey = getKey(TOKEN_PRE, toTokenId);
    let recipientTokenData = JSON.parse(Chain.load(recipientTokenKey));
    Utils.assert(recipientTokenData !== false, 'Failed to get storage data, key: ' + recipientTokenKey);
    recipientTokenData = JSON.parse(recipientTokenData);

    // subtract sender balance
    senderTokenData.balance = Utils.int256Sub(senderTokenData.balance, amount);
    Chain.store(senderTokenKey, JSON.stringify(senderTokenData));

    // add recipient balance
    recipientTokenData.balance = Utils.int256Add(recipientTokenData.balance, amount);
    Chain.store(recipientTokenKey, JSON.stringify(recipientTokenData));
}

// transfer balance to another token of the same slot
function transferFromBalance(fromTokenId, toTokenId, amount) {
    let senderTokenKey = getKey(TOKEN_PRE, fromTokenId);
    let senderTokenData = Chain.load(senderTokenKey);
    Utils.assert(senderTokenData !== false, 'Failed to get storage data, key: ' + senderTokenKey);
    senderTokenData = JSON.parse(senderTokenData);
    Utils.assert(Chain.msg.sender === senderTokenData.owner || senderTokenData.operators.includes(Chain.msg.sender), 'Only owner or operator of the token can call this function.');
    Utils.assert(Utils.int256Compare(senderTokenData.balance, amount) > 0, 'Insufficient balance to transfer amount.');

    let recipientTokenKey = getKey(TOKEN_PRE, toTokenId);
    let recipientTokenData = JSON.parse(Chain.load(recipientTokenKey));
    Utils.assert(recipientTokenData !== false, 'Failed to get storage data, key: ' + recipientTokenKey);
    recipientTokenData = JSON.parse(recipientTokenData);

    // subtract sender balance
    senderTokenData.balance = Utils.int256Sub(senderTokenData.balance, amount);
    Chain.store(senderTokenKey, JSON.stringify(senderTokenData));

    // add recipient balance
    recipientTokenData.balance = Utils.int256Add(recipientTokenData.balance, amount);
    Chain.store(recipientTokenKey, JSON.stringify(recipientTokenData));
}

// same as transferFrom
function safeTransferFromToken(tokenId, toAddress) {
    let key = getKey(TOKEN_PRE, tokenId);
    let tokenData = Chain.load(key);
    Utils.assert(tokenData !== false, 'Failed to get storage data, key: ' + key);
    tokenData = JSON.parse(tokenData);
    Utils.assert(Chain.msg.sender === tokenData.owner || tokenData.operators.includes(Chain.msg.sender), 'Only owner of the token can call this function.');
    Utils.assert(Utils.addressCheck(toAddress) === true, 'Recipient address is invalid.');

    // remove the balance of existing owner
    let ownerKey = getKey(BALANCE_PRE, tokenData.owner);
    let ownerData = Chain.load(ownerKey);
    Utils.assert(ownerData !== false, 'Failed to get storage data, key: ' + ownerKey);
    ownerData = JSON.parse(ownerData);
    ownerData.tokens = ownerData.tokens.filter(function(tokenKey) {
        return tokenKey !== key;
    });
    Chain.store(ownerKey, JSON.stringify(ownerData));

    // add the balance to new owner
    let recipientKey = getKey(BALANCE_PRE, toAddress);
    let recipientData = Chain.load(recipientKey);
    // Utils.assert(recipientData !== false, 'Failed to get storage data, key: ' + recipientKey);
    if (recipientData === false) {
        recipientData = {};
        recipientData.tokens = [];
    } else {
        recipientData = JSON.parse(recipientData);
    }
    recipientData.tokens.push(key);
    Chain.store(recipientKey, JSON.stringify(recipientData));

    // update the token owner
    tokenData.owner = toAddress;
    Chain.store(key, JSON.stringify(tokenData));
}

// transfer ownership of token
function transferFromToken(tokenId, toAddress) {
    let key = getKey(TOKEN_PRE, tokenId);
    let tokenData = Chain.load(key);
    Utils.assert(tokenData !== false, 'Failed to get storage data, key: ' + key);
    tokenData = JSON.parse(tokenData);
    Utils.assert(Chain.msg.sender === tokenData.owner || tokenData.operators.includes(Chain.msg.sender), 'Only owner of the token can call this function.');
    Utils.assert(Utils.addressCheck(toAddress) === true, 'Recipient address is invalid.');

    // remove the balance of existing owner
    let ownerKey = getKey(BALANCE_PRE, tokenData.owner);
    let ownerData = Chain.load(ownerKey);
    Utils.assert(ownerData !== false, 'Failed to get storage data, key: ' + ownerKey);
    ownerData = JSON.parse(ownerData);
    ownerData.tokens = ownerData.tokens.filter(function(tokenKey) {
        return tokenKey !== key;
    });
    Chain.store(ownerKey, JSON.stringify(ownerData));

    // add the balance to new owner
    let recipientKey = getKey(BALANCE_PRE, toAddress);
    let recipientData = Chain.load(recipientKey);
    // Utils.assert(recipientData !== false, 'Failed to get storage data, key: ' + recipientKey);
    if (recipientData === false) {
        recipientData = {};
        recipientData.tokens = [];
    } else {
        recipientData = JSON.parse(recipientData);
    }
    recipientData.tokens.push(key);
    Chain.store(recipientKey, JSON.stringify(recipientData));

    // update the token owner
    tokenData.owner = toAddress;
    Chain.store(key, JSON.stringify(tokenData));
}

function init(input_str) {
    let paramObj = JSON.parse(input_str).params;
    Utils.assert(paramObj.name !== undefined && paramObj.name.length > 0, 'Param obj has no name.');
    Utils.assert(paramObj.symbol !== undefined && paramObj.symbol.length > 0, 'Param obj has no symbol.');
    Utils.assert(paramObj.describe !== undefined && paramObj.describe.length > 0, 'Param obj has no describe.');
    Utils.assert(paramObj.version !== undefined && paramObj.version.length > 0, 'Param obj has no version.');

    
    let totalSupplyObject = {};
    totalSupplyObject.tokens = [];
    Chain.store(TOTAL_SUPPLY, JSON.stringify(totalSupplyObject));

    paramObj.protocol = ZTP_PROTOCOL;
    Chain.store(CONTRACT_PRE, JSON.stringify(paramObj));
    return;
}
  
function main(input_str) {
    let input = JSON.parse(input_str);
    if (input.method === "safeTransferFromToken") {
        safeTransferFromToken(input.params.tokenId, input.params.toAddress);
    } else if (input.method === "transferFromToken") {
        transferFromToken(input.params.tokenId, input.params.toAddress);
    } else if (input.method === "safeTransferFromBalance") {
        safeTransferFromBalance(input.params.fromTokenId, input.params.toTokenId, input.params.amount);
    } else if (input.method === "transferFromBalance") {
        transferFromBalance(input.params.fromTokenId, input.params.toTokenId, input.params.amount);
    } else if (input.method === "approve") {
        approve(input.params.operator, input.params.tokenId);
    } else if (input.method === "approveForSlot") {
        approveForSlot(input.params.operator, input.params.slotId);
    } else if (input.method === "approveForAll") {
        approveForAll(input.params.owner, input.params.operator);
    } else if (input.method === "setSlotMetadata") {
        setSlotMetadata(input.params.slotId, input.params.tokens, input.params.operators, input.params.owner);
    } else if (input.method === "mintToken") {
        mintToken(input.params.toAddress, input.params.slotId, input.params.tokenId, input.params.balance);
    } else if (input.method === "mintBalance") {
        mintBalance(input.params.tokenId, input.params.amount);
    } else if (input.method === "burnToken") {
        burnToken(input.params.tokenId);
    } else if (input.method === "burnBalance") {
        burnBalance(input.params.tokenId, input.params.amount);
    } else {
        throw `Error matching strings. Input: ${input}. Method: ${input.method}. Parameters: ${input.params}`;
    }
}
  
function query(input_str) {
    let result = {};
    let input = JSON.parse(input_str);
    if (input.method === "balanceOf") {
        result.balance = balanceOf(input.params.address);
    } else if (input.method === "ownerOf") {
        result.owner = ownerOf(input.params.tokenId);
    } else if (input.method === "getApproved") {
        result.operators = getApproved(input.params.tokenId);
    } else if (input.method === "isApprovedForAll") {
        result.approvedForAll = isApprovedForAll(input.params.owner, input.params.operator);
    } else if (input.method === "isApprovedForSlot") {
        result.isApprovedForSlot = isApprovedForSlot(input.params.slotId, input.params.operator);
    } else if (input.method === "slotOf") {
        result.slot = slotOf(input.params.tokenId);
    } else if (input.method === "contractInfo") {
        result.contractInfo = contractInfo();
    } else if (input.method === "tokenURI") {
        result.tokenURI = tokenURI(input.params.tokenId);
    } else if (input.method === "slotURI") {
        result.slotURI = slotURI(input.params.slotId);
    } else if (input.method === "totalSupply") {
        result.totalSupply = totalSupply();
    } else if (input.method === "tokensOfOwner") {
        result.tokens = tokensOfOwner(input.params.address);
    } else if (input.method === "tokensInSlot") {
        result.tokens = tokensInSlot(input.params.slotId);
    } else if (input.method === "getSlotMetadata") {
        result.slotMetadata = getSlotMetadata(input.params.slotId);
    } else {
        throw `Error matching strings. Input: ${input}. Method: ${input.method}. Parameters: ${input.params}`;
    }

    return JSON.stringify(result);
}
```

## Use Case

A use case for the **ZTP-3525** standard, which is designed for semi-fungible tokens (SFTs) on the Zetrix blockchain, involves creating a tokenized subscription service. Here's how it works:

#### **Tokenized Subscription Service with ZTP-3525**

In this scenario, **ZTP-3525** tokens are used to represent subscription plans with both unique and fungible properties. Each subscription plan is represented by a token (`tokenId`) that holds a balance (fungible) to represent usage, duration, or remaining service credits.

**Steps for Implementation:**

1. **Minting Subscription Tokens**:\
   The service provider mints **ZTP-3525** tokens for a user, assigning them a `tokenId` linked to a specific subscription plan (e.g., "Premium Plan" or "Basic Plan"). This token is linked to a `slotId`, representing the type or category of the subscription (e.g., "Premium", "Basic").
2. **Tracking Balance**:\
   The `balance` associated with the `tokenId` keeps track of the subscription's usage, such as how many hours of service or credits remain. For example, a "Premium Plan" token might have a balance of 30, representing 30 days of service left.
3. **Transferring or Selling**:\
   If the user decides to transfer their remaining subscription balance to someone else (for example, selling part of the remaining credits), they can use `safeTransferFromBalance` or `transferFromBalance` to move a portion of their subscription balance to another user's token, allowing partial transfers of service.
4. **Slot Management**:\
   The service can define different slots for various types of subscriptions (e.g., “Basic”, “Premium”). This allows the operator to manage all tokens within a particular category or plan using the `approveForSlot` function, where an authorized operator can transfer, manage, or adjust all tokens in the “Premium” slot, for example.
5. **Burning Subscription Tokens**:\
   When the subscription expires or the service is fully used, the token can be "burned" using the `burnToken` or `burnBalance` function, removing the subscription token from circulation, reflecting that the user has used or forfeited their subscription.

**Benefits of Using ZTP-3525 for Subscription Services:**

* **Flexibility**: A token can represent both the unique identity of a subscription and the fungible balance of remaining service, allowing seamless management of credits or time-based services.
* **Partial Transfers**: Users can transfer portions of their subscription or balance to others, allowing flexible use of services.
* **Efficient Slot Management**: Subscription plans can be grouped into slots, simplifying management and control for operators (e.g., managing the entire Premium plan in one action).
* **Transparency**: Smart contracts ensure the automatic and transparent management of tokens, including subscription duration and usage.

This use case demonstrates how **ZTP-3525**’s combination of non-fungible and fungible elements can be leveraged to manage services that require both unique attributes and divisible, transferable balances on the Zetrix blockchain.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.zetrix.com/en/developer-resources/smart-contract/ztp-standard/ztp-3525.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
