Version: next

This documentation is for an unreleased version of Taquito. Features and APIs may change before the final release. View the latest stable version .

TZIP-16 Contract Metadata and Views

Written by Roxane Letourneau

The @taquito/tzip16 package allows retrieving metadata associated with a smart contract. This metadata can be stored on-chain (tezos-storage) or off-chain (HTTP(S) or IPFS). The package also provides a way to execute the MichelsonStorageView found in the metadata. More information about the TZIP-16 standard can be found here.

How to use the tzip16 package

The package can be used as an extension to the well known Taquito contract abstraction.

For production applications that retrieve IPFS metadata, configure a reliable gateway. The shared public default, ipfs.filebase.io, is provided for convenience and may be rate-limited or unavailable. Its public gateway is intended for testing and light usage, with a limit of 200 requests per minute. Use a dedicated or application-controlled gateway that can retrieve your content and supports CORS for browser applications. See the custom metadata provider example below.

A gateway override changes how Taquito retrieves ipfs:// metadata. It does not rewrite https://ipfs.io/... URIs stored in contracts or URLs inside the returned metadata.

  1. Create an instance of Tzip16Module and add it as an extension to our TezosToolkit

The constructor of the Tzip16Module takes an optional MetadataProvider as a parameter. When none is passed, the default MetadataProvider of Taquito is instantiated and the default handlers (HttpHandler, IpfsHandler, and TezosStorageHandler) are used.

import { TezosToolkit } from '@taquito/taquito';
import { Tzip16Module, tzip16 } from '@taquito/tzip16';

const Tezos = new TezosToolkit('rpcUrl');
Tezos.addExtension(new Tzip16Module());

In some cases, we may want to use a customized metadata provider. The constructor of the Tzip16Module class takes an optional metadata provider as a parameter. This allows you to inject a custom metadata provider with custom protocol handlers if desired.

The former default, ipfs.io, and dweb.link are transitioning to service-worker gateways. They can reject plain HTTP metadata requests with rate-limit responses. Taquito’s HTTP handler cannot execute a service-worker bootstrap. If your application explicitly configures either gateway, update that override as well.

To override the default, pass your gateway hostname to IpfsHttpHandler, without https:// or /ipfs/. Taquito constructs https://<hostname>/ipfs/<content-path>/. Existing explicit gateway overrides are preserved. The following example replaces the IPFS handler while retaining the default HTTP, HTTPS, and Tezos storage handlers:

import { TezosToolkit } from '@taquito/taquito';
import { DEFAULT_HANDLERS, IpfsHttpHandler, MetadataProvider, Tzip16Module, tzip16 } from '@taquito/tzip16';

const Tezos = new TezosToolkit('rpcUrl');

// The constructor of the `MetadataProvider` class takes a `Map<string, Handler>` as a parameter.
const customHandler = new Map(DEFAULT_HANDLERS);
customHandler.set('ipfs', new IpfsHttpHandler('ipfs.example.com')); // Replace with your gateway hostname

const customMetadataProvider = new MetadataProvider(customHandler);
Tezos.addExtension(new Tzip16Module(customMetadataProvider));

A list of public gateways is accessible here.

  1. Use the tzip16 function to extend a contract abstraction
const contract = await Tezos.contract.at('contractAddress', tzip16);
  1. Call the methods of the Tzip16ContractAbstraction class

The namespace tzip16() need to be specified when calling a method of the Tzip16ContractAbstraction class:

const metadata = await contract.tzip16().getMetadata();
const views = await contract.tzip16().metadataViews();

All other methods of the ContractAbstraction class can be called as usual on the contract object.

Get the metadata

The getMetadata method returns an object which contains the URI, the metadata in JSON format, an optional SHA256 hash of the metadata and an optional integrity check result.

A sequence diagram can be found here.

Metadata retrieval failures

Catch rejections from contract.tzip16().getMetadata() and let users retry or display that metadata is unavailable. The built-in IPFS handler adds gateway context and configuration guidance while retaining the HTTP error types and diagnostic fields:

ErrorDiagnostic fields
HttpResponseErrorstatus, statusText, body, and gateway url
HttpRequestFailedHTTP method, gateway url, original cause, and transportError when available
HttpTimeoutErrortimeout in milliseconds and gateway url
InvalidContractMetadataErrorOriginal response text in invalidMetadata; the metadata URI is included in the message

HTTP errors are exported by @taquito/http-utils; InvalidContractMetadataError is exported by @taquito/tzip16. Prefer error types and diagnostic fields over matching message text.

A non-JSON response can be malformed metadata or an HTML page returned by a gateway. For IPFS parse failures, the message identifies the original ipfs:// URI, not the HTTP gateway URL. JSON parsing happens in MetadataProvider; calling IpfsHttpHandler.getMetadata() directly still returns raw text.

Taquito does not automatically switch gateways or retry HTTP 429/5xx responses. A 404 may mean that content is unavailable; changing gateways does not ensure that the content exists or remains pinned. TZIP-12 retains its existing contract-metadata fallback behavior.

Tezos-storage example

// import { TezosToolkit } from '@taquito/taquito';
// import { Tzip16Module, tzip16 } from "@taquito/tzip16";
// const Tezos = new TezosToolkit('rpc_url');

Tezos.addExtension(new Tzip16Module());

const contractAddress = 'KT19tSnPKvej6C965PtP6SHWJKcverACF16i';

try {
  const contract = await Tezos.contract.at(contractAddress, tzip16);
  console.log(`Fetching the metadata for ${contractAddress}...`);
  const metadata = await contract.tzip16().getMetadata();
  console.log(JSON.stringify(metadata, null, 2));
} catch (error) {
  console.log(`Error: ${JSON.stringify(error, null, 2)}`);
}
// import { TezosToolkit } from '@taquito/taquito';
// import { Tzip16Module, tzip16 } from "@taquito/tzip16";
// const Tezos = new TezosToolkit('rpc_url');

Tezos.addExtension(new Tzip16Module());

const contractAddress = 'KT19tSnPKvej6C965PtP6SHWJKcverACF16i';

try {
  const wallet = await Tezos.wallet.at(contractAddress, tzip16);
  console.log(`Fetching the metadata for ${contractAddress}...`);
  const metadata = await wallet.tzip16().getMetadata();
  console.log(JSON.stringify(metadata, null, 2));
} catch (error) {
  console.log(`Error: ${JSON.stringify(error, null, 2)}`);
}

HTTPS example

// import { TezosToolkit } from '@taquito/taquito';
// import { Tzip16Module, tzip16 } from "@taquito/tzip16";
// const Tezos = new TezosToolkit('rpc_url');

Tezos.addExtension(new Tzip16Module());

const contractAddress = 'KT1SUaCViFjdTG18y4XHsE5RE9nUPcHWuT6X';

try {
  const contract = await Tezos.contract.at(contractAddress, tzip16);
  console.log(`Fetching the metadata for ${contractAddress}...`);
  const metadata = await contract.tzip16().getMetadata();
  console.log(JSON.stringify(metadata, null, 2));
} catch (error) {
  console.log(`Error: ${JSON.stringify(error, null, 2)}`);
}
// import { TezosToolkit } from '@taquito/taquito';
// import { Tzip16Module, tzip16 } from "@taquito/tzip16";
// const Tezos = new TezosToolkit('rpc_url');

Tezos.addExtension(new Tzip16Module());

const contractAddress = 'KT1SUaCViFjdTG18y4XHsE5RE9nUPcHWuT6X';

try {
  const wallet = await Tezos.wallet.at(contractAddress, tzip16);
  console.log(`Fetching the metadata for ${contractAddress}...`);
  const metadata = await wallet.tzip16().getMetadata();
  console.log(JSON.stringify(metadata, null, 2));
} catch (error) {
  console.log(`Error: ${JSON.stringify(error, null, 2)}`);
}

Example having a SHA256 hash:

// import { TezosToolkit } from '@taquito/taquito';
// import { Tzip16Module, tzip16 } from "@taquito/tzip16";
// const Tezos = new TezosToolkit('rpc_url');

Tezos.addExtension(new Tzip16Module());

const contractAddress = 'KT1XqaAgbjGtVtkmeERYvagcg2eJHFtsKBiE';

try {
  const contract = await Tezos.contract.at(contractAddress, tzip16);
  console.log(`Fetching the metadata for ${contractAddress}...`);
  const metadata = await contract.tzip16().getMetadata();
  console.log(JSON.stringify(metadata, null, 2));
} catch (error) {
  console.log(`Error: ${JSON.stringify(error, null, 2)}`);
}
// import { TezosToolkit } from '@taquito/taquito';
// import { Tzip16Module, tzip16 } from "@taquito/tzip16";
// const Tezos = new TezosToolkit('rpc_url');

Tezos.addExtension(new Tzip16Module());

const contractAddress = 'KT1XqaAgbjGtVtkmeERYvagcg2eJHFtsKBiE';

try {
  const wallet = await Tezos.wallet.at(contractAddress, tzip16);
  console.log(`Fetching the metadata for ${contractAddress}...`);
  const metadata = await wallet.tzip16().getMetadata();
  console.log(JSON.stringify(metadata, null, 2));
} catch (error) {
  console.log(`Error: ${JSON.stringify(error, null, 2)}`);
}

IPFS example

// import { TezosToolkit } from '@taquito/taquito';
// import { Tzip16Module, tzip16 } from "@taquito/tzip16";
// const Tezos = new TezosToolkit('rpc_url');

Tezos.addExtension(new Tzip16Module());

const contractAddress = 'KT1ED45Vb5xqg87fhiri2v4kMrXuNygUSwpX';

try {
  const contract = await Tezos.contract.at(contractAddress, tzip16);
  console.log(`Fetching the metadata for ${contractAddress}...`);
  const metadata = await contract.tzip16().getMetadata();
  console.log(JSON.stringify(metadata, null, 2));
} catch (error) {
  console.log(`Error: ${JSON.stringify(error, null, 2)}`);
}
// import { TezosToolkit } from '@taquito/taquito';
// import { Tzip16Module, tzip16 } from "@taquito/tzip16";
// const Tezos = new TezosToolkit('rpc_url');

Tezos.addExtension(new Tzip16Module());

const contractAddress = 'KT1ED45Vb5xqg87fhiri2v4kMrXuNygUSwpX';

try {
  const wallet = await Tezos.wallet.at(contractAddress, tzip16);
  console.log(`Fetching the metadata for ${contractAddress}...`);
  const metadata = await wallet.tzip16().getMetadata();
  console.log(JSON.stringify(metadata, null, 2));
} catch (error) {
  console.log(`Error: ${JSON.stringify(error, null, 2)}`);
}

Execute off-chain views

A sequence diagram can be found here.

In the next example, we will run a view named someJson that can be found in the metadata of the demo off-chain-view contract. When we inspect those metadata, we can see that this view takes no parameter, has a returnType of bytes and has the following code:

"code":
[
  {
    "prim": "DROP",
    "args": [],
    "annots": []
  },
  {
    "prim": "PUSH",
    "args": [
      {
        "prim": "bytes",
        "args": [],
        "annots": []
      },
      {
        "bytes": "7b2268656c6c6f223a22776f726c64222c226d6f7265223a7b226c6f72656d223a34322c22697073756d223a5b22222c226f6e65222c2232225d7d7d"
      }
    ],
    "annots": []
  }
]

Try to run the view:

// import { TezosToolkit } from '@taquito/taquito';
// import { Tzip16Module, tzip16, bytesToString } from "@taquito/tzip16";
// const Tezos = new TezosToolkit('rpc_url');

Tezos.addExtension(new Tzip16Module());

const contractAddress = 'KT1Nd4y65P7QeGtczzirMMjMm2MRS5TUWAGh';

try {
  const contract = await Tezos.contract.at(contractAddress, tzip16);
  console.log(`Initialising the views for ${contractAddress}...`);
  const views = await contract.tzip16().metadataViews();
  console.log(`The following view names were found in the metadata: ${Object.keys(views)}`);
  const result = await views.someJson().executeView();
  console.log(`Result of the view someJson: ${result}`);
  console.log(`Transform result to char: ${bytesToString(result)}`);
} catch (error) {
  console.log(`Error: ${JSON.stringify(error, null, 2)}`);
}
// import { TezosToolkit } from '@taquito/taquito';
// import { Tzip16Module, tzip16, bytesToString } from "@taquito/tzip16";
// const Tezos = new TezosToolkit('rpc_url');

Tezos.addExtension(new Tzip16Module());

const contractAddress = 'KT1Nd4y65P7QeGtczzirMMjMm2MRS5TUWAGh';

try {
  const wallet = await Tezos.wallet.at(contractAddress, tzip16);
  console.log(`Initialising the views for ${contractAddress}...`);
  const views = await wallet.tzip16().metadataViews();
  console.log(`The following view names were found in the metadata: ${Object.keys(views)}`);
  const result = await views.someJson().executeView();
  console.log(`Result of the view someJson: ${result}`);
  console.log(`Transform result to char: ${bytesToString(result)}`);
} catch (error) {
  console.log(`Error: ${JSON.stringify(error, null, 2)}`);
}

In the next example, we will run a view named multiply-the-nat-in-storage that can be found in the metadata of the demo off-chain-view contract. When we inspect those metadata, we can see that this view takes a nat parameter, has a returnType of nat and has the following instructions: DUP, CDR, CAR, SWAP, CAR, MUL.

Try to run the view:

// import { TezosToolkit } from '@taquito/taquito';
// import { Tzip16Module, tzip16 } from "@taquito/tzip16";
// const Tezos = new TezosToolkit('rpc_url');

Tezos.addExtension(new Tzip16Module());

const contractAddress = 'KT1McajsayLC5QoBtvFujGAbCQaGUx7NwMvU';

try {
  const contract = await Tezos.contract.at(contractAddress, tzip16);
  const storage = await contract.storage();
  console.log(`The nat in the storage of the contract is: ${storage[0]}`);
  console.log(`Initialising the views for ${contractAddress}...`);
  const views = await contract.tzip16().metadataViews();
  console.log(`The following view names were found in the metadata: ${Object.keys(views)}`);
  const result = await views['multiply-the-nat-in-storage']().executeView(10);
  console.log(`Result of the view 'multiply-the-nat-in-storage': ${result}`);
} catch (error) {
  console.log(`Error: ${JSON.stringify(error, null, 2)}`);
}
// import { TezosToolkit } from '@taquito/taquito';
// import { Tzip16Module, tzip16 } from "@taquito/tzip16";
// const Tezos = new TezosToolkit('rpc_url');

Tezos.addExtension(new Tzip16Module());

const contractAddress = 'KT1McajsayLC5QoBtvFujGAbCQaGUx7NwMvU';

try {
  const wallet = await Tezos.wallet.at(contractAddress, tzip16);
  const storage = await wallet.storage();
  console.log(`The nat in the storage of the contract is: ${storage[0]}`);
  console.log(`Initialising the views for ${contractAddress}...`);
  const views = await wallet.tzip16().metadataViews();
  console.log(`The following view names were found in the metadata: ${Object.keys(views)}`);
  const result = await views['multiply-the-nat-in-storage']().executeView(10);
  console.log(`Result of the view 'multiply-the-nat-in-storage': ${result}`);
} catch (error) {
  console.log(`Error: ${JSON.stringify(error, null, 2)}`);
}

Execute a custom view

In the next example we execute the view multiply-the-nat-in-storage in a custom way:

// import { TezosToolkit, RpcReadAdapter } from '@taquito/taquito';
// import { MichelsonStorageView } from "@taquito/tzip16";
// const Tezos = new TezosToolkit('rpc_url');

const contractAddress = 'KT1McajsayLC5QoBtvFujGAbCQaGUx7NwMvU';

try {
  const contract = await Tezos.contract.at(contractAddress);
  const view = new MichelsonStorageView(
    'test', // view name
    contract, // contract abstraction
    Tezos.rpc, // rpc
    new RpcReadAdapter(Tezos.rpc), // readProvider
    { prim: 'nat' }, // returnType
    [
      { prim: 'DUP' },
      { prim: 'CDR' },
      { prim: 'CAR' },
      { prim: 'SWAP' },
      { prim: 'CAR' },
      { prim: 'MUL' },
    ], // code of the view
    { prim: 'nat' } // parameter type
  );

  const result = await view.executeView(2);
  console.log(`Result of the custom view: ${result}`);
} catch (error) {
  console.log(error);
}
// import { TezosToolkit, RpcReadAdapter } from '@taquito/taquito';
// import { MichelsonStorageView } from "@taquito/tzip16";
// const Tezos = new TezosToolkit('rpc_url');

const contractAddress = 'KT1McajsayLC5QoBtvFujGAbCQaGUx7NwMvU';

try {
  const wallet = await Tezos.wallet.at(contractAddress);
  const view = new MichelsonStorageView(
    'test', // view name
    wallet, // contract abstraction
    Tezos.rpc, // rpc,
    new RpcReadAdapter(Tezos.rpc), // readProvider
    { prim: 'nat' }, // returnType
    [
      { prim: 'DUP' },
      { prim: 'CDR' },
      { prim: 'CAR' },
      { prim: 'SWAP' },
      { prim: 'CAR' },
      { prim: 'MUL' },
    ], // code of the view
    { prim: 'nat' } // parameter type
  );

  const result = await view.executeView(2);
  console.log(`Result of the custom view: ${result}`);
} catch (error) {
  console.log(`Error: ${JSON.stringify(error, null, 2)}`);
}