> For the complete documentation index, see [llms.txt](https://docs.zetrix.com/en/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zetrix.com/en/developer-resources/smart-contract/ztp-standard/ztp-173.md).

# ZTP-173

**ZTP-173** is the Zetrix Contract Ownership Standard, inspired by Ethereum's [ERC-173](https://eips.ethereum.org/EIPS/eip-173). It provides a standard interface for querying and transferring ownership of a smart contract on the Zetrix blockchain.

### Overview

| Property     | Detail             |
| ------------ | ------------------ |
| **Standard** | ZTP-173            |
| **Category** | Contract Ownership |
| **Network**  | Zetrix Blockchain  |

### Abstract

ZTP-173 defines a standard interface for **contract ownership** on the Zetrix blockchain. It specifies how a smart contract declares, queries, transfers, and renounces a single owner address enabling block explorers, tooling, and other contracts to interact with ownership in a predictable and interoperable way.

The ownership model follows a **two-step transfer** pattern: the current owner initiates a transfer, and the new owner must explicitly accept it. This prevents accidental or malicious transfers to inaccessible addresses.

### Motivation

Many smart contracts deployed on Zetrix require an administrative owner — an account that can adjust parameters, mint tokens, pause functionality, or manage funds. Without a common interface:

* Block explorers cannot uniformly display or verify contract ownership.
* Tooling cannot generically transfer or renounce ownership across contracts.
* Other contracts cannot safely query the owner of an on-chain resource.
* Security audits cannot follow a predictable, auditable ownership lifecycle.

ZTP-173 solves this by standardising five entry points so every conforming contract exposes the same ownership API, consistent with existing Zetrix contract conventions (`Chain.load`, `Chain.store`, `Chain.tlog`, `contractInfo.issuer`).

### Specification

#### Interface

A ZTP-173-compliant contract **MUST** implement the following functions:

| Function                      | Access     | Description                             |
| ----------------------------- | ---------- | --------------------------------------- |
| `owner()`                     | Public     | Returns the current owner address       |
| `transferOwnership(newOwner)` | Owner only | Initiates a two-step ownership transfer |

#### Functions

**owner**

```javascript
function owner()
```

Returns the address of the current contract owner. Reads from `contractInfo.issuer` stored under the `CONTRACT_PRE` key.

| Items       | Description                                                   |
| ----------- | ------------------------------------------------------------- |
| **Access**  | Public (query)                                                |
| **Returns** | `String` — current owner Zetrix address, or `''` if renounced |
| **Throws**  | Never                                                         |

**transferOwnership**

```javascript
function transferOwnership(newOwner)
```

Initiates a two-step ownership transfer. The proposed new owner must call `acceptOwnership()` to finalise the transfer. This function does **not** immediately change `contractInfo.issuer`.

| Items          | Description                                                                                                |
| -------------- | ---------------------------------------------------------------------------------------------------------- |
| **Access**     | Owner only                                                                                                 |
| **Parameters** | `newOwner` — target Zetrix address                                                                         |
| **Emits**      | `OwnershipTransferStarted(newOwner)`                                                                       |
| **Throws if**  | Caller is not owner; address is invalid or empty; address is same as current owner; address is blacklisted |

#### Invoking the Contract

#### Query — owner

```json
{ "method": "owner" }
```

**Response**

```json
{ "result": "ZTXabc123..." }
```

#### transferOwnership

json

```json
{
  "method": "transferOwnership",
  "params": { "newOwner": "ZTXxyz789..." }
}
```

### Sample Code

```javascript
'use strict';

// ============================================================================
// CONSTANTS
// ============================================================================

const CONTRACT_PRE = 'contract_info';

// ============================================================================
// INTERNAL HELPERS
// ============================================================================

function getOwner() {
  let contractInfo = JSON.parse(Chain.load(CONTRACT_PRE));
  return contractInfo.issuer;
}

function validateOwner() {
  Utils.assert(Chain.msg.sender === getOwner(), 'Only the contract owner can perform this action.');
}

function validateNotBlacklisted(address) {
  // Plug in your blacklist logic here if needed.
  // Example: Utils.assert(!isBlacklisted(address), 'Address is blacklisted.');
}

// ============================================================================
// OWNERSHIP MANAGEMENT (ZTP-173)
// ============================================================================

function owner() {                           
  return getOwner();
}

function transferOwnership(newOwner) {
  validateOwner();
  Utils.assert(                              
    newOwner !== undefined && newOwner.length > 0,
    'New owner address is required.'
  );
  Utils.assert(                              
    Utils.addressCheck(newOwner),
    'New owner address is invalid.'
  );
  validateNotBlacklisted(newOwner);

  let contractInfo  = JSON.parse(Chain.load(CONTRACT_PRE));
  let previousOwner = contractInfo.issuer;
  Utils.assert(previousOwner !== newOwner, 'New owner must be different from current owner.');

  contractInfo.issuer = newOwner;
  Chain.store(CONTRACT_PRE, JSON.stringify(contractInfo));
  Chain.tlog('OwnershipTransferred', previousOwner, newOwner);
  return true;
}

// ============================================================================
// CONTRACT LIFECYCLE
// ============================================================================

function init(input_str) {
  let params = JSON.parse(input_str).params || {};

  Utils.assert(params.name   !== undefined, 'Contract name is required.');
  Utils.assert(params.symbol !== undefined, 'Contract symbol is required.');

  let initialOwner = params.owner || Chain.msg.sender;
  Utils.assert(Utils.addressCheck(initialOwner), 'Invalid initial owner address.');

  let contractInfo = {
    name       : params.name,
    symbol     : params.symbol,
    issuer     : initialOwner,
    createTime : Chain.block.timestamp
  };

  Chain.store(CONTRACT_PRE, JSON.stringify(contractInfo));
  Chain.tlog('OwnershipTransferred', '', initialOwner);
  return true;
}

function main(input_str) {
  let input  = JSON.parse(input_str);
  let method = input.method;
  let params = input.params || {};

  Utils.assert(method !== undefined && method.length > 0, 'Method is required.');

  if (method === 'transferOwnership') {
    return transferOwnership(params.newOwner);
  }

  Utils.assert(false, 'Unknown method: ' + method);
}

function query(input_str) {
  let input  = JSON.parse(input_str);
  let method = input.method;

  Utils.assert(method !== undefined && method.length > 0, 'Query method is required.');

  if (method === 'owner') {
    return JSON.stringify({ result: owner() });   // ✅ owner() now defined
  }

  Utils.assert(false, 'Unknown query method: ' + method);
}

```
