NIP: 13
Layer: Library
Title: Security Token Standard
Author: Gregory Saive <greg@nem.foundation>
Discussions-To: https://github.com/nemtech/NIP/issues/42
Comments-URI: https://github.com/nemtech/NIP/issues/42
Status: Draft
Type: Standards Track
Created: 2019-07-29
License: Apache-2.0
- Introduction
- Motivation
- Specification
- Design Decisions
- Implementation
- Backwards compatibility
- References
- History
This document aims to provide with a standard for issuance and management of security tokens on Symbol. This standard will be structured as such that standard interfaces will be defined to facilitate operations and interrogations related to security tokens.
Facilitate and accelerate the processes of issuance and management of security tokens on Symbol.
To issue and manage securities on Symbol, this standard will leverage several Symbol features. Implementation details will be described in a document jointly to this standard definition.
The security token standards to be described in this document will be based on features available with Symbol networks. Additionally, for each standard, we will define requirements that will apply to the underlying asset class.
- MUST have a standard interface to query feasibility of a transfer and return a reason for failures.
- MUST be able to perform forced transfer for legal action or fund recovery.
- MUST define standard notifications for issuance and redemption processes.
- MUST be able to attach metadata to a subset of a token holder's balance.
- MUST be able to modify metadata at time of transfer based on off-chain data, on-chain data and the parameters of the transfer.
- MUST support querying and subscribing to updates on any relevant documentation for the security.
- MAY require signed data to be passed into a transfer transaction in order to validate it on-chain.
- SHOULD NOT restrict the range of asset classes across jurisdictions which can be represented.
Source: ERC#1400 Security Token Standard
Following features table will be used to define mandatory features of the published software package:
| Feature | Motivation |
|---|---|
| Issuance | Issuance of security tokens is managed through owner |
| Issuer Power Delegation | Operators can be allowed specific management tasks of the security tokens |
| Transfer | Security tokens can be transferred (change ownership) |
| Batch Transfer | Security tokens can be transferred in batches (change ownership) |
| Transfer with Metadata | Metadata can be added to transfers of security tokens |
| Metadata management | Metadata can be managed for security tokens |
| Transfer restrictions | Transfers of security tokens can be restricted (constraints) |
| Freeze/Lock | Security token balances can be frozen/locked by operators |
| Operator enforcement | Operators can be allowed tasks enforcements (such as transfers) |
| Force Transfer | Transfers of security tokens can be enforced. |
| Error signaling | Error signaling is described for each Standard |
| Document management | Documents can be attached to security tokens [ownerships] |
Token Standards introduce the term of commands that are executed for specific tokens. For instance, the transfer of token happens by executing the command TransferOwnership for said token.
This concept of commands will be available for all Token Standards. As such we will define a list of commands that will also be technically defined in the implementation details.
| Token Command | Rationale |
|---|---|
| PublishToken | Issue/Publish a previously created token (non-reversable) |
| TransferOwnership | Transfer a token to a new owner |
| TransferOwnershipWithData | Transfer a token to a new owner, adding signed data as a message |
| BatchTransferOwnership | Batch Transfer tokens to new owners |
| ForcedTransfer | Force the transfer of a token to a new owner, with an operator account |
| LockBalance | Freeze / Lock a token balance |
| UnlockBalance | Unfreeze / Unlock a token balance |
| ModifyRestriction | Send modifications to account/mosaic restrictions |
| ModifyMetadata | Send modifications to account/mosaic metadata |
| DelegateIssuerPower | Delegate issuance authorizations to operator(s) account(s) |
| AttachDocument | Attach documents to a token instance |
- Operator accounts must be restricted to use only the following types of transactions:
- MultisigAccountModificationTransaction
- MosaicDefinitionTransaction
- MosaicSupplyChangeTransaction
- NamespaceRegistrationTransaction
- MosaicAliasTransaction
- MosaicGlobalRestrictionTransaction
- MosaicAddressRestrictionTransaction
- MosaicMetadataTransaction
-
Adding token restrictions must leverage Mosaic Restrictions features.
-
Adding token metadata must leverage Metadata features.
NIP#13 does not introduce any mandatory Metadata. Token operators are free to attach any type of metadata to security tokens.
NIP#13 introduces a Security Token Standard which will be implemented mostly with mosaics, multi-signature accounts and transactions. Work on this standard has not begun by the time of writing.
Following features are available with our Security Token Standard:
| Feature | NIP-13 |
|---|---|
| Issuance | ✅ |
| Issuer Power Delegation | ✅ |
| Transfer | ✅ |
| Batch Transfer | ✅ |
| Transfer with Metadata | ✅ |
| Metadata management | ✅ |
| Transfer restrictions | ✅ |
| Freeze/Lock | ✅ |
| Force Transfer | ✅ |
| Error signaling | ✅ |
| Operator enforcement | ✅ |
| Document management | ✅ |
Because each of the Token Standards will behave differently from others with regards to feature set integrations, we will implement commands in such a way that each standard can define its own extension to the standard command execution flow.
Commands will be grouped in namespaces such that each Token Standard can implement commands with its own feature set integration practises. Following examples MAY apply:
// Import NIP13 namespace
import { NIP13 } from 'symbol-security-tokens';
// prepare
const tokenId = new TokenIdentifier();
const command = new NIP13.PublishToken();
const options = [Option.create('tokenId', tokenId.toString())];
// now execute command
const result = command.execute(tokenId, options);This document defines a general purpose Token Standard. Those defined standards will leverage different feature sets of Symbol networks. More detailed implementation details will be described here.
In order to generalize and facilitate security token issuance and management with Symbol, we will publish one software package that integrates all Token Standards.
Still, each of our different standards will be implementing features differently as to be more flexible and allow a broader scope of integrations or use case implementations.
The released software package will be developed with Typescript, using the symbol-sdk@>=0.17.3 package to integrate latest Symbol features. The library MUST provide with interfaces and classes, which developers can leverage to manage issuance and redemptions of security tokens on Symbol.
As a proof of concept for this library, we MAY introduce either of a command line interface (CLI) or a basic web application to provide with out-of-the-box Security Token support for Symbol.
A token is represented by a multitude of features available with Symbol. This includes mosaics, multi-signature accounts, namespaces and metadata.
A token command can be executed by operators for their published tokens. A list of available token commands can be found here.
A token operator must have permissions to digitally govern published tokens. There can be many operators for one token and the list of operators can change with the execution of some token commands.
An operator account is a deterministically created account that must be converted to a multi-signature account where cosignatories are operators of the token.
A token owner is an account address that holds [part(s) of-] the available token balance. Owners can be managed with operator accounts.
A token partition represents a part of the available token balance and can be owned by a token owner. One token partition can always be owned by one token owner. The assignment of partitions can be tracked using a special transfer transaction signed by the operator account.
For each token partition, there will be one added cosignatory to the operator account.
| Command | Execution Flow |
|---|---|
| PublishToken | 1) Creates a deterministic operator account 2) Convert operator account to multi-signature 3) Create mosaic with operator account 4) Publish creation proof transaction with mosaic identifier on deterministic account |
| TransferOwnership | 1) In case of operator modification, change ownership of token through multi-signature modification. 2) In case of partition transfer, add new owner as one of the cosignatories of the operator account and register the partition information as a transfer transaction signed by the operator account. |
| TransferOwnershipWithData | 1) In case of operator modification, change ownership of token through multi-signature modification and attach signed data inside transfer transaction, bundled with the operator modification aggregate bonded transaction. 2) In case of partition transfer, add new owner as one of the cosignatories of the operator account, attach signed data inside transfer transaction, bundled with the operator modification aggregate bonded transaction and register the partition information as a transfer transaction signed by the operator account. |
| ForcedTransfer | 1) Change cosignatories of the operator account to be set to the new token owner address and remove the old token owner and all its owned partitions. |
| BatchTransferOwnership | 1) Bundled TransferOwnership with aggregate bonded transactions (Max. count of transaction is network dependent). |
| LockBalance | 1) Remove all token owner addresses from cosignatories of operator account. |
| UnlockBalance | 1) Add back all token owner addresses to cosignatories of operator account. |
| ModifyRestriction | 1) Configure mosaic restrictions for current token with operator account. |
| ModifyMetadata | 1) Add metadata to one of operator account, cosignatory (partitions) or mosaic, depending on type of metadata assignment. |
| DelegateIssuerPower | 1) Modify multi-signature account to add/remove cosignatories to operator account. |
| AttachDocument | 1) Publish cryptographic hash of document 2) Send message in transaction to operator account. |
A deliverable package will be produced along with this NIP. Naming for this package is yet to be defined.
Following are the available package name suggestions, more may be added at a later point:
-
symbol-security-tokens -
symbol-financial-instruments -
symbol-financial-tools -
symbol-derivatives -
symbol-instruments
The decision making process for this package name selection will be made internally, more suggestions are welcome.
Each of our Security Token Standards will implement features with a pre-defined feature set of Symbol. This is important to note as this knowledge will influence the architecture of the release package. Following solution proposal can be applied:
- package.json
> src/
> interfaces/
- Standard.ts
- Command.ts
> standards/
- NIP13.ts
- etc.
> commands/
- Option.ts
- Command.ts
> NIP13/
- PublishToken.ts
- TransferOwnership.ts
- AttachDocument.ts
- ...
> model/
- AccountMetadata.ts with properties `account: Address`, `field: string`, `value: string`
- AccountRestriction.ts with properties `account: Address`, `restriction: AddressRestriction`
- AllowanceResult.ts with properties `status: boolean`, `message: string | undefined`
- CommandResult.ts with properties `status: boolean`, `message: string | undefined`
- NotificationProof.ts with properties ``transactionHash: TransactionHash`
- PublicProof.ts with properties `tokenId: TokenIdentifier`, `transactionHash: TransactionHash`
- TokenIdentifier.ts with properties `id: UInt64`
- TokenSource.ts with properties `source: string` (network generationHash)
- TokenMetadata.ts with properties `tokenId: TokenIdentifier`, `field: string`, `value: string`
- TokenRestriction.ts with properties `tokenId: TokenIdentifier`, `restriction: MosaicRestriction`
- A
Standardinterface is defined that MUST contain following methods:
/**
* Creates a new Security Token with pre-defined Catapult feature set.
*
* @param {string} name
* @param {Address} owner
* @param {TokenSource} source
* @param {Array<Address>} operators
* @return {TokenIdentifier}
**/
public create(
name: string,
owner: Address,
source: TokenSource,
operators: [Address] = []): TokenIdentifier;
/**
* Publish a previously created Security Token with identifier `tokenId`.
*
* @internal This method MUST use the `PublishToken.execute()` method.
* @param {string} name
* @param {Address} owner
* @param {Array<Address>} operators
* @return {PublicProof}
**/
public publish(
tokenId: TokenIdentifier): PublicProof;
/**
* Notify an account `account` about
* Publish a previously created Security Token with identifier `tokenId`.
*
* @internal This method MUST use the `PublishToken` command.
* @param {string} name
* @param {Address} owner
* @param {Array<Address>} operators
* @return {PublicProof}
**/
public notify(
tokenId: TokenIdentifier,
account: Address,
notification: AccountNotification): NotificationProof;
/**
* Verifies **allowance** of `sender` to transfer `tokenId` security token.
*
* @param {Address} sender
* @param {TokenIdentifier} tokenId
* @return {AllowanceResult}
**/
public canTransfer(
sender: Address,
tokenId: TokenIdentifier): AllowanceResult;
/**
* Verifies **allowance** of `operator` to execute `command` with `tokenId` security token.
*
* @internal This method MUST use the `command.canExecute()` method.
* @param {Address} operator
* @param {Address} account
* @param {TokenIdentifier} tokenId
* @param {Command} command
* @param {Array<Option>} argv
* @return {AllowanceResult}
**/
public canExecute(
operator: Address,
account: Address,
tokenId: TokenIdentifier,
command: Command,
argv: [Option] = []): AllowanceResult;
/**
* Execute `command` for Security Token with identifier `tokenId`. Arguments
* the command execution can be passed in `argv`.
*
* @internal This method MUST use the `command.execute()` method.
* @param {Address} operator
* @param {Address} account
* @param {TokenIdentifier} tokenId
* @param {Command} command
* @param {Array<Option>} argv
* @return {CommandResult}
**/
public execute(
operator: Address,
account: Address,
tokenId: TokenIdentifier,
command: Command,
argv: [Option] = []): CommandResult;
/**
* Read metadata of a previously created Security Token with identifier `tokenId`.
*
* @param {TokenIdentifier} tokenId
* @return {Array<TokenMetadata>}
**/
public getMetadata(
tokenId: TokenIdentifier): [TokenMetadata];
/**
* Read restrictions of a previously created Security Token with identifier `tokenId`.
*
* @param {TokenIdentifier} tokenId
* @return {Array<TokenRestriction|AccountRestriction>}
**/
public getRestrictions(
tokenId: TokenIdentifier,
account: Address = null): [TokenRestriction|AccountRestriction];Classes implementing the Standard interface MUST make use of the symbol-sdk package to leverage Catapult features.
- A
Commandinterface is defined that MUST contain following methods:
/**
* Verifies **allowance** of `operator` to execute command with `tokenId` security token
* for `account`. Parameters `account` and `operators` will be the same when the account
* is supposed to execute the command directly.
*
* @param {Address} operator
* @param {Address} account
* @param {TokenIdentifier} tokenId
* @param {Array<Option>} argv
* @return {AllowanceResult}
**/
public canExecute(
operator: Address,
account: Address,
tokenId: TokenIdentifier,
argv: [Option] = []): AllowanceResult;
/**
* Execute `command` for Security Token with identifier `tokenId`. Arguments
* the command execution can be passed in `argv`. Parameters `account` and `operators`
* will be the same when the account is supposed to execute the command directly.
*
* @param {Address} operator
* @param {Address} account
* @param {TokenIdentifier} tokenId
* @param {Array<Option>} argv
* @return {CommandResult}
**/
public execute(
operator: Address,
account: Address,
tokenId: TokenIdentifier,
argv: [Option] = []): CommandResult;Classes implementing the Command interface MUST make use of the symbol-sdk package to leverage Catapult features.
Following the list of requirements, this section will define implementation options that MUST be available in described Token Standards.
-
MUST have a standard interface to query feasibility of a transfer and return a reason for failures.
-
canTransfer() -
canExecute() -
MUST be able to perform forced transfer for legal action or fund recovery.
-
execute(... new ForcedTransfer(), [Option.create('sender', '...'), Option.create('recipient', '...')]) -
MUST define standard notifications for issuance and redemption processes.
-
canExecute(... new PublishToken(), [Option.create('tokenId', '...')]) -
MUST be able to attach metadata to a subset of a token holder's balance.
-
execute(... new ModifyMetadata(), [Option.create('data', '...')]) -
MUST be able to modify metadata at time of transfer based on off-chain data, on-chain data and the parameters of the transfer.
-
execute(... new ModifyMetadata(), [Option.create('data', '...')]) -
MUST support querying and subscribing to updates on any relevant documentation for the security.
-
notify() -
MAY require signed data to be passed into a transfer transaction in order to validate it on-chain.
-
execute(... new TransferOwnershipWithData(), [Option.create('data', '...')]) -
SHOULD NOT restrict the range of asset classes across jurisdictions which can be represented.
| Story | Assigned | Progress |
|---|---|---|
| Describe NIP13 and draft NIP | @evias | |
| Define Implementation details | @evias | |
| Start NIP13 implementation | @evias |
This standard is backwards compatible with Symbol Mosaics as it relies solely on Symbol Mosaic and Accounts features.
- ERC#1400
Security Token Standard - NFT Library
nem2-nonfungible-asset - Symbol Typescript SDK
- Symbol protocol
- Symbol Developer Center
| Date | Version |
|---|---|
| July 29 2019 | Initial Draft |
| March 11 2019 | Revised NST-1 |
| March 25 2020 | Revised NIP13 |