├── .prettierignore ├── .prettierrc.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── PSPs ├── drafts │ ├── psp-13.md │ ├── psp-2.md │ ├── psp-3.md │ ├── psp-30.md │ ├── psp-31.md │ ├── psp-48.md │ ├── psp-6.md │ └── psp-7.md ├── psp-22.md ├── psp-33.md ├── psp-34.md ├── psp-37.md └── psp-template.md ├── README.md ├── package.json └── src ├── logo-polkadot.svg ├── psp-7 ├── dss_sequence_diagram.png └── sequence_wallet_diagram.png └── psp-sign-in.png /.prettierignore: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "proseWrap": "always" 4 | } 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at legal@web3.foundation. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /PSPs/drafts/psp-13.md: -------------------------------------------------------------------------------- 1 | # Voting Standard 2 | 3 | - **PSP Number:** 13 4 | - **Authors:** @ETeissonniere 5 | - **Status:** Draft 6 | - **Created:** 2021-03-31 7 | - **Reference Implementation** https://github.com/NucleiStudio/governance-os 8 | 9 | ## Summary 10 | 11 | Uniformize various voting systems implementations behind a common trait to allow for easy change between runtimes or support for decentralized organizations with pluggable voting systems. 12 | 13 | ## Motivation 14 | 15 | Thanks to a set of W3F grant, I was able to focus on building a set of pallets to supports multiple decentralized organizations in one runtime (instead of the traditional "chain dao" model as common in the substrate ecosystem). Once I started building various voting pallets I realized that a uniformized standard would be needed to allow for the easy integration of multiple voting systems in one runtime without having to create yet a new pallet with yet a new set of extrinsics or coupling traits every time. 16 | 17 | ## Specification 18 | 19 | We propose the creation of a coupling trait that must be supported by any voting implementations. Other pallets can require this trait to be loosely coupled with any voting pallet. 20 | 21 | Our proposed trait is as follows, we have included the relevant documentation in the function's comments. We have also included the definition of a `ProposalResult` type to correctly flag a proposal's result. 22 | ```rust 23 | use codec::{Decode, Encode}; 24 | #[cfg(feature = "std")] 25 | use serde::{Deserialize, Serialize}; 26 | use sp_runtime::{DispatchError, DispatchResult, RuntimeDebug}; 27 | use sp_std::result; 28 | 29 | /// End result of a proposal being closed. 30 | #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] 31 | #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] 32 | pub enum ProposalResult { 33 | Passing, 34 | Failing, 35 | } 36 | 37 | /// A common trait accross all voting implementations to make it easy to change 38 | /// between voting models or implementations. 39 | /// A pallet implementing this trait is not necessarily in charge of storing 40 | /// proposals but could stick to only to support the actual decison making 41 | /// code while proposal storage is delegated to another pallet. 42 | pub trait StandardizedVoting { 43 | /// How we represent the a proposal as passed to the underlying functions. 44 | /// This can be used to fetch any state associated to the proposal. 45 | type ProposalId; 46 | 47 | /// How the parameters of a voting system are represented and set at the 48 | /// organization level. 49 | type Parameters; 50 | 51 | /// How voting data is passed to the underlying pallet. 52 | type VoteData; 53 | 54 | /// How accounts are represented, used to identify voters. 55 | type AccountId; 56 | 57 | /// A proposal is being created. Handle any eventual registration and trigger 58 | /// an error if any preconditions are not met. Shall be called before any other 59 | /// state changes so that it is safe to fail here. It is the caller's responsibility 60 | /// to try and prevent overwrites or duplicated proposals. 61 | fn initiate(proposal: Self::ProposalId, parameters: Self::Parameters) -> DispatchResult; 62 | 63 | /// Special function to handle the case when a proposal is being vetoed. This 64 | /// should clean any storage or state associated to the given proposal. 65 | fn veto(proposal: Self::ProposalId) -> DispatchResult; 66 | 67 | /// Handle the reception of a new vote for the given proposal. This should mutate any 68 | /// state linked to the proposal accordingly. 69 | fn vote( 70 | proposal: Self::ProposalId, 71 | voter: &Self::AccountId, 72 | data: Self::VoteData, 73 | ) -> DispatchResult; 74 | 75 | /// Handle the closure of a proposal or return an error if it cannot be closed because 76 | /// some conditions are not met. Shall return an indicator on wether the proposal is 77 | /// passing (should be executed) or not (should be discarded). 78 | fn close(proposal: Self::ProposalId) -> result::Result; 79 | } 80 | ``` 81 | 82 | ## Tests 83 | 84 | [To be created once more feedback is collected] 85 | 86 | ## Copyright 87 | 88 | This document is placed in the 89 | [public domain](https://creativecommons.org/publicdomain/zero/1.0/). 90 | -------------------------------------------------------------------------------- /PSPs/drafts/psp-2.md: -------------------------------------------------------------------------------- 1 | # Substrate Uniform Resource Identifier 2 | 3 | * **PSP Number:** 2 4 | * **Authors:** @xlc 5 | * **Status:** Draft 6 | * **Created:** 2019-10-01 7 | * **Reference Implementation** [To be implemented] 8 | 9 | ## Summary 10 | 11 | A URI format that identifies a resource in the Polkadot ecosystem, which can be an independent chain, a module in a chain or one of the resource provided by a module in a chain (e.g. A smart contract in a chain with contracts module). 12 | 13 | ## References 14 | 15 | - https://hackmd.io/gQKQGf42TeOODid3hM4_1w#Identifying-and-utilising-a-fungible-asset-on-Polkadot 16 | - https://tools.ietf.org/html/rfc3986 17 | 18 | ## Motivation 19 | 20 | In the cross-chain Substrate / Polkadot ecosystem, there will be multiple chains interacting with each other. Therefore it is esstenital to have a uniformed way to address each chain and the resources / services hosted by different chain. 21 | 22 | Substrate Uniform Resource Identifier (SURI) provides a way to uniformally address a cross-chain resource. 23 | 24 | A resource can be: 25 | 26 | - A blockchain. e.g. Polkadot, Ethereum 27 | - A runtime module in a chain. e.g. Balances in Polkadot (DOT), Crowdfunding module in Polkadot 28 | - A resource provided by a runtime module in a chain. e.g. An instance of a smart contract in a contracts module in a smart contract chain 29 | 30 | More examples: 31 | 32 | - A native fungible token of a chain. e.g. DOT on Polkadot, BTC on Bitcoin, ETH on Ethereum 33 | - A non-fungible token provided by a chain. 34 | - An instance of a non-fungible token. e.g. A particular Crypto Kitty 35 | - A bridged token provided by a chain. e.g. XBTC on ChainX 36 | - A utility token provided by runtime modules or smart contracts. e.g. MKR on Ethereum, SDOT on ChainX 37 | - A service provided by a chain. e.g. DEX on exchange chain 38 | - A trading pair in a exchange that provided by a chain. e.g. BTCETH pair on a DEX chain 39 | 40 | ## Specification 41 | 42 | ### URI format 43 | 44 | A URI is made of various parts: 45 | 46 | ``` 47 | URI = scheme:[//authority]path[?query][#fragment] 48 | authority = [userinfo@]host[:port] 49 | ``` 50 | 51 | For SURI, the format is defined as 52 | 53 | - schema: TBD 54 | - `polkadot` or `substrate` or something more generalized? 55 | - port: N/A 56 | - userinfo: the parachain ID 57 | - host: the relay chain index or address 58 | - Leave empty to point to current chain 59 | - path: the resource path in a chain 60 | - Begin with the runtime module name and followed by runtime module specific path 61 | - query & fragment: unspecified 62 | 63 | ### Encoding 64 | 65 | There are two encoding formats for SURI. 66 | 67 | - Standard encoding 68 | - Encoded as a URI string 69 | 70 | - Custom SCALE encoding to avoid unnecessary space usage 71 | ```rust 72 | struct SURI { 73 | userinfo: Option, 74 | host: Option, 75 | path_components: Vec, 76 | query: Option, 77 | fragment: Option 78 | }; 79 | type RuntimeStr = Vec; 80 | type ParaId = u32; // per Polkadot implementation 81 | enum ChainId { 82 | Id(u32), // numeric chain id registry is out of scope of this proposol 83 | Name(RuntimeStr) 84 | }; 85 | ``` 86 | 87 | ## Tests 88 | 89 | (using `polkadot` as the schema) 90 | 91 | - Polkadot DOT: `polkadot://polkadot/balances` 92 | - The Default instance Balances token on parachain 1: `polkadot://1@polkadot/srml-balances` 93 | - The Instance2 Balances token on parachain 2: `polkadot://2@polkadot/balances.2` 94 | - Generic Asset Asset ID 5 token on a independent chain: `polkadot://chain-name/srml-generic-asset/5` 95 | - The smart contract module on parachain 3 on Kusama: `polkadot://3@kusama/srml-contracts` 96 | - A NFT with ID of `UniqueWeapon001` with provided by a smart contract with address of `0x1234abcd` on a Instance2 contracts module on parachain 4 on Kusama: `polkadot://4@kusama/srml-contracts.2/0x1234abcd/UniqueWeapon001` 97 | - The balances of user `0x1111aaaa` of a ERC20-like token contract with address of `0x2222bbbb` on a smart contract chain: `polkadot://smart-contract-chain-name/srml-contracts/0x2222bbbb/0x1111aaaa` 98 | 99 | 100 | ## Copyright 101 | 102 | Each PSP must be labeled as placed in the [public domain](https://creativecommons.org/publicdomain/zero/1.0/). 103 | -------------------------------------------------------------------------------- /PSPs/drafts/psp-3.md: -------------------------------------------------------------------------------- 1 | # Fungible Asset Wallet Metadata 2 | 3 | * **PSP Number:** 3 4 | * **Authors:** @xlc 5 | * **Status:** Draft 6 | * **Created:** 2019-10-08 7 | * **Reference Implementation** [To be implemented] 8 | 9 | ## Summary 10 | 11 | Standard to allow Substrate chains to advertise metadata allowing off-chain logic to interact with their state, events and transaction dispatches in order to query or manipulate fungible currencies existing on the chain. 12 | 13 | ## References 14 | 15 | - https://hackmd.io/gQKQGf42TeOODid3hM4_1w 16 | - https://eips.ethereum.org/EIPS/eip-20 17 | - https://eips.ethereum.org/EIPS/eip-777 18 | - https://eips.ethereum.org/EIPS/eip-1155 19 | - https://github.com/paritytech/xcm-format 20 | 21 | ## Motivation 22 | 23 | A wallet-metadata standard is required to allow off-chain clients (e.g. wallet, browser extension) to offer a unified support of all assets provided by different chains without any pre-knowledge of the chain. 24 | 25 | ## Specification 26 | 27 | A fungible asset is mainly made on two parts: Descriptions and Operations. 28 | 29 | ### Asset Descriptions 30 | 31 | Asset descriptions offers a universal way to describe, display the details about a asset. This information may not be stored on-chain to reduce usage of on-chain storages. A centralized or decentralized asset registry may be used to discovery the asset description, which is out of the scope here. 32 | 33 | ### Asset Operations 34 | 35 | An Asset implementation should provide set of operations to achieve common functionalities such as transfer and query balances. 36 | 37 | Write operations can be dispatched via extrinsic (i.e. transaction), smart contract call (on a chain with contracts module), or runtime module call (via public Rust methods). Query operations can be dispatched via RPC (`state_storage`), smart contract call, or runtime module call. 38 | 39 | ## Specification 40 | 41 | ### Asset Descriptions 42 | 43 | With TypeScript syntax: 44 | 45 | ```typescript 46 | interface AssetDescription { 47 | name: string; 48 | symbol: string; 49 | decimals: number; 50 | uri?: string; // Substrate URI. See psp-uri 51 | description?: string; 52 | icon?: string; // HTTP URL or IPFS hash or dataURI 53 | } 54 | ``` 55 | 56 | Asset description can also be embedded in genesis file as additional properties. It is expected to be immutable but maybe changed under special circumstances (e.g. project rename / rebrand). 57 | 58 | ### Asset Operations 59 | 60 | #### Data Types 61 | 62 | With TypeScript syntax: 63 | 64 | ```typescript 65 | interface AssetOperationDescription { 66 | types: TypeRegistry; 67 | queries: { 68 | [name: string]: { 69 | parameterTypes?: Record; 70 | returnType: string; 71 | path: Path; 72 | }; 73 | }; 74 | operations: { 75 | [name: string]: { 76 | parameterTypes: Record; 77 | path: Path; 78 | }; 79 | }; 80 | events: { 81 | [name: string]: { 82 | types: Record; 83 | path: Path; 84 | } 85 | }; 86 | } 87 | 88 | interface TypeWithHasher { 89 | type: TypeName; 90 | hasher: Hasher; 91 | } 92 | type TypeName = string; 93 | type Hasher = 'Blake2_128' | 'Blake2_256' | 'Blake2_128Concat' | 'Twox128' | 'Twox256' | 'Twox64Concat' | 'Identity'; 94 | type Path = string | [ModuleName, MethodName, ...string[]] 95 | ``` 96 | 97 | Asset operation description stores the necessary information to perform asset operations and might need to be updated with new runtime versions. There should be a way to fetch the corresponding `AssetOperationDescription` . e.g. From a registry or a commonly known location. 98 | 99 | Note: Substrate Runtime Metadata is required to encode / decode dispatchable calls and events. The details of Substrate Runtime Metadata is out of scope. 100 | 101 | #### TypeRegistry 102 | 103 | Type registry stores the necessary type information to interact with the runtime. This is inspired by polkadot.js type registry. 104 | 105 | ```typescript 106 | interface TypeRegistry { 107 | [typename: string]: TypeDefinition; 108 | } 109 | 110 | type TypeDefinition = string | // primitives or buildin types 111 | Record | // struct 112 | { _enum: EnumDefinition } | // enum 113 | Array // tuple 114 | 115 | type EnumDefinition = Array | // C like enum 116 | Record // Algebraic data types like enum, the order of the key matches to the variant index 117 | ``` 118 | 119 | Currently the type registry needs to be manually maintained and at later stage, https://github.com/paritytech/scale-info/ could be used to automatically generate the type inforamtion. 120 | 121 | #### Queries 122 | 123 | The initial query ability will be limited on accessing the storage directly without any additional computation work. This means all the assets that confirming the standard must have the same underlying storage format. This limitation will be lift on later version, by allowing execute a WASM code in a sandbox to process the storage. 124 | 125 | - totalIssuance: Balance 126 | - parameters 127 | - assetId: AssetId 128 | - description 129 | - The total amount of issuance in the system for a given asset 130 | 131 | - freeBalance: Balance 132 | - parameters 133 | - assetId: AssetId 134 | - who: AccountId 135 | - description 136 | - Query the free balance of a given account for a given asset 137 | 138 | #### Operations 139 | 140 | - transfer 141 | - parameters 142 | - assetId: AssetId 143 | - to: AccountId 144 | - amount: Balance 145 | - description 146 | - Transfer some liquid free balance to another account 147 | 148 | #### Events 149 | 150 | - Transfer 151 | - parameters 152 | - assetId: AssetId 153 | - from: AccountId 154 | - to: AccountId 155 | - amount: Balance 156 | - description 157 | - Transfer succeeded 158 | 159 | ## Examples 160 | 161 | Polkadot: 162 | 163 | ```typescript 164 | const PolkadotAssetDescription: AssetDescription = { 165 | name: 'Polkadot', 166 | symbol: 'DOT', 167 | decimals: 12, 168 | // uri: ?? 169 | description: 'Polkadot token description', 170 | icon: 'https://ipfs.io/ipfs/QmY7mkAnaX4JNJWiLEeAgrbhe5MPkEmeuW83PmafAtL9pi' 171 | } 172 | 173 | const PolkadotAssetOperationDescription: AssetOperationDescription = { 174 | types: { 175 | Balance: 'u128', 176 | AccountId: 'GenericAccountId', 177 | }, 178 | queries: { 179 | totalIssuance: { 180 | returnType: 'Balance', 181 | path: 'Balances.TotalIssuance', // or ['Balances', 'TotalIssuance'] 182 | }, 183 | freeBalance: { 184 | parameterTypes: { 185 | account: { 186 | type: 'AccountId', 187 | hasher: 'Blake2_128Concat', 188 | }, 189 | }, 190 | returnType: 'Balance', 191 | path: 'System.Account.$account.data.free', // or ['System', 'Account', '$account', 'data', 'free'] 192 | }, 193 | }, 194 | operations: { 195 | transfer: { 196 | parameterTypes: { 197 | to: 'AccountId', 198 | amount: 'Balance', 199 | }, 200 | path: 'Balances.transfer', // or ['Balances', 'transfer'] 201 | } 202 | }, 203 | events: { 204 | Transfer: { 205 | types: { 206 | from: 'AccountId', 207 | to: 'AccountId', 208 | value: 'Balance', 209 | }, 210 | path: 'Balances.Transfer', // or ['Balances', 'Transfer'] 211 | }, 212 | }, 213 | } 214 | 215 | ``` 216 | 217 | Acala: 218 | 219 | ```typescript 220 | const AcalaAssetDescriptions: Record = { 221 | ACA: { 222 | name: 'Acala', 223 | symbol: 'ACA', 224 | decimals: 18, 225 | // uri: ?? 226 | description: 'Acala token description', 227 | icon: 'https://ipfs.io/ipfs/QmX1ESvSLJMai5DuAcU6MXyaDHboBCUBe3VqM7zUqgJ6kj' 228 | }, 229 | aUSD: { 230 | name: 'Acala Dollar', 231 | symbol: 'aUSD', 232 | decimals: 18, 233 | // uri: ?? 234 | description: 'Acala Dollar description', 235 | icon: 'https://ipfs.io/ipfs/QmQrSbMLvxHqgzi8E2Qy4r65GD94mbyPHWbTYVrT4gDT2e' 236 | }, 237 | LDOT: { 238 | name: 'Acala Liquid DOT', 239 | symbol: 'LDOT', 240 | decimals: 18, 241 | // uri: ?? 242 | description: 'Acala Liquid Dollar description', 243 | icon: 'https://ipfs.io/ipfs/QmR1uCTRkSQsBhhL66JJWTZjRk3E6JfEJ7cegc46vZZMVF' 244 | }, 245 | } 246 | 247 | const AcalaAssetOperationDescriptions: AssetOperationDescription = { 248 | types: { 249 | AssetId: 'CurrencyId', // Acala internally use CurrencyId instead of AssetId 250 | CurrencyId: { 251 | _enum: ['ACA', 'aUSD', 'LDOT'] 252 | }, 253 | Balance: 'u128', 254 | AccountId: 'GenericAccountId', 255 | }, 256 | queries: { 257 | totalIssuance: { 258 | parameterTypes: { 259 | assetId: { 260 | type: 'CurrencyId', 261 | hasher: 'Twox64Concat', 262 | }, 263 | }, 264 | returnType: 'Balance', 265 | path: 'Tokens.TotalIssuance.$assetId', // or ['Tokens', 'TotalIssuance', '$assetId'] 266 | }, 267 | freeBalance: { 268 | parameterTypes: { 269 | assetId: { 270 | type: 'CurrencyId', 271 | hasher: 'Blake2_128Concat', 272 | } 273 | account: { 274 | type: 'AccountId', 275 | hasher: 'Blake2_128Concat', 276 | }, 277 | }, 278 | returnType: 'Balance', 279 | path: 'Tokens.Accounts.$account.$assetId.free', // or ['System', 'Account', '$account', '$assetId', 'free'] 280 | }, 281 | }, 282 | operations: { 283 | transfer: { 284 | parameterTypes: { 285 | to: 'AccountId', 286 | assetId: 'CurrencyId', 287 | amount: 'Balance', 288 | }, 289 | path: 'Tokens.transfer', // or ['Tokens', 'transfer'] 290 | } 291 | }, 292 | events: { 293 | Transfer: { 294 | types: { 295 | from: 'AccountId', 296 | to: 'AccountId', 297 | value: 'Balance', 298 | }, 299 | path: 'Tokens.Transferred', // or ['Tokens', 'Transferred'] 300 | }, 301 | }, 302 | } 303 | 304 | const AcalaAssetOperationDescriptions: Record = { 305 | ACA: PolkadotAssetOperationDescription, // Same as DOT 306 | aUSD: AcalaAssetOperationDescriptions, // Implemented by orml-tokens 307 | LDOT: AcalaAssetOperationDescriptions, 308 | } 309 | 310 | ``` 311 | 312 | ## Tests 313 | 314 | [To be created] 315 | 316 | ## Copyright 317 | 318 | Each PSP must be labeled as placed in the [public domain](https://creativecommons.org/publicdomain/zero/1.0/). 319 | -------------------------------------------------------------------------------- /PSPs/drafts/psp-30.md: -------------------------------------------------------------------------------- 1 | # Augmented ERC20s 2 | 3 | - **PSP Number:** 30 4 | - **Authors:** Hyungsuk Kang(@hskang9) 5 | - **Status:** Draft, Call For Feedback 6 | - **Created:** 2021-11-25 7 | - **Reference Implementation** https://github.com/digitalnativeinc/eip-4469 8 | 9 | ## Summary 10 | 11 | Augmented ERC20s propose a wrapper method to bring ethereum token primitives into xcm-compatible asset class. 12 | 13 | ## Motivation 14 | 15 | There is definitely a friction for developers when it comes to making erc20 compatible to substrate native runtime use cases. This can be solved via a precompile contract which calls transfer functions in evm, but this just provides a virtual controller, not a compatible primitive which it can use full potential of each runtime. Hence, I propose a wrapper to provide each primitives. 16 | 17 | ## Specification 18 | 19 | Similar to the design of [reference implementation](https://github.com/digitalnativeinc/eip-4469), an entry point contract which creates augmentor contract for each erc20 token is provided with `create2` opcode. An augmentor links to the ethereum precompile contract where it executes issuing asset primitives in `pallet-asset` module. 20 | 21 | An evm precompile module is provided with three functions. 22 | 23 | `createAsset(name, symbol, decimal)`: `createAsset` function executes `pallet-asset` module to register an asset with arguments received from the evm smart contract(e.g. name, symbol, decimal, etc) 24 | 25 | `mint(account, amount)`: `mint` function requires the caller to be the augmentor contract registered in the entry point contract and mints certain amount of asset in given account's balance. 26 | 27 | `burn(account, amount)`: `burn` function requires the caller to be the augmentor contract registered in the entry point contract and burns certain amount of asset in given account's balance. 28 | 29 | An entry point contract has storage 30 | ``` 31 | mapping(address -> address) augmentors; 32 | ``` 33 | An entry point contract for augmentors should have one function. 34 | 35 | `createAugment(token) returns (address augmentor)`: `createAugment` function checks whether the sender is the owner of the erc20: 36 | ``` 37 | require(msg.sender == IERC20(token).owner(), "ACCESS_INVALID"); 38 | ``` 39 | then it executes `createAsset` function from `pallet-asset` after registering augmentor contract in `augmentors` storage. augmentor contract address is returned. 40 | 41 | An augmentor contract has two functions to augment/decrease tokens: 42 | 43 | `augment(amount)`: Through an augmentor contract, an ERC20 token holder deposits token into the augmentor contract, then the augmentor contract executes `mint` function to the sender with the same amount deposited. 44 | 45 | `decrease(amount)`: Through an augmentor contract, the augmentor executes `burn` function to the sender account with the given amount, then transfers ERC20 tokens to the sender account 46 | 47 | ## Tests 48 | 49 | # Test case 1: CreateAugment is initiated by only owner of the contract 50 | 51 | 52 | # Test case 2: Augment/Decrease function works within a parachain runtime 53 | 54 | 55 | # Test case 3: Test with xcm tx to store token information in `pallet-asset` module in statemint 56 | 57 | ## Copyright 58 | Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). 59 | -------------------------------------------------------------------------------- /PSPs/drafts/psp-31.md: -------------------------------------------------------------------------------- 1 | # Standard Proposal and workflow for Polkadot Sign In 2 | 3 | - **PSP Number:** 31 4 | - **Authors:** hanwencheng (hanwen@litentry.com) 5 | - **Status:** Draft 6 | - **Created:** 2021-11-20 7 | - **Reference Implementation:** https://github.com/litentry/PolkaSignIn (WIP) 8 | 9 | ## Summary 10 | 11 | A standard for the workflow about OAuth based on Polkadot accounts. It describes how to integrate Polkadot accounts with multiple applications including dApps and traditional Web2 world. 12 | 13 | This proposal aims to define the standard interface and workflow for the click-once signin action. 14 | 15 | ## Motivation 16 | 17 | Currently, while there is no standard , every dApps will have different signin action. 18 | 19 | In a Web2 world, When signing in to popular non-blockchain services today, users will typically use identity providers (IdPs) that are centralized entities with ultimate control over users' identifiers, for example, large internet companies and email providers. Incentives are often misaligned between these parties. 20 | 21 | Sign-In with Polkadot offers a new self-custodial option for users who wish to assume more control and responsibility over their own digital identity. 22 | 23 | In Web3, injected signers are widely used for connecting accounts with dApps. Already, many services support workflows to authenticate Polkadot accounts using message signing, such as to establish a cookie-based web session which can manage privileged metadata about the authenticating address. 24 | 25 | This is an opportunity to standardize the sign-in workflow and improve interoperability across existing services, while also providing wallet vendors a reliable method to identify signing requests as Sign-In with Polkadot requests for improved UX. 26 | 27 | ## Specification 28 | 29 | ### Workflow 30 | 31 | Sign-In with Polkadot works as follows: 32 | 33 | 1. The client sends the sign-in request **SignIn-Message** to the target service. 34 | 35 | 2. The target service will return the information named as **Challenge-Message**. 36 | 37 | 3. The client connects to Injected Signer(Injected Signer like : Polkadot.js Extension, Metamask, Parity Signer ...). 38 | 39 | 4. The Injected Signer will display the UI to show challenge message detail to notice client. 40 | 41 | 5. (If confirmed by step 4) The Injected Signer will sign the message as **Signed-Message** and return it to client. 42 | 43 | 6. The client sends **Signed-Message** to the target service. 44 | 45 | 7. The target service validates the **Signed-Message**. If success, the target service should return the identity info to the client. The identity info SHOULD contain the token or session info so that the client can interaction with server later. 46 | 47 | 8. The target service may further fetch data associated with the Polkadot address, such as from the Polkadot blockchain (e.g., PNS, Parachain), or other data sources that may or may not be permissioned. 48 | 49 | ![Sign-in Flow](../../src/psp-sign-in.png) 50 | 51 | # 52 | 53 | ### Message Template 54 | 55 | A JSON template of the full message is presented below for readability and ease of understanding. Field descriptions are provided in the following section. 56 | 57 | #### SignIn-Message 58 | 59 | ``` 60 | { 61 | "identityNetwork": "${network}" 62 | } 63 | ``` 64 | 65 | ##### Sample message: 66 | 67 | ``` 68 | { 69 | "identityNetwork": "polkadot" 70 | } 71 | ``` 72 | 73 | #### Challenge-Message 74 | 75 | ``` 76 | { 77 | "identityNetwork": "${network}" 78 | "callbackEndpoint": "${callbackEndpoint}", 79 | "scope": [${scope}], 80 | "challenge": "${challenge}" 81 | } 82 | ``` 83 | 84 | ##### Sample message: 85 | 86 | ``` 87 | { 88 | "identityNetwork": "polkadot", 89 | "callbackEndpoint": "http://dapp.com/sign-in/callback", 90 | "scope": ["address","balance"], 91 | "challenge": "challenge message at 20210-11-21 10:00:00" 92 | } 93 | ``` 94 | 95 | #### Signed-Message 96 | 97 | ``` 98 | { 99 | "identityNetwork": "${network}" 100 | "scope": [${scope}], 101 | "challenge": "${challenge}", 102 | "signature": "${signature}", 103 | "address": "${address}", 104 | } 105 | ``` 106 | 107 | ##### Sample message: 108 | 109 | ``` 110 | { 111 | "identityNetwork": "polkadot", 112 | "scope": ["address","balance"], 113 | "challenge": "challenge message at 20210-11-21 10:00:00", 114 | "signature": "0x58baf5e0cca04d71646662aeac8ab3254193a7fff86344dbf11d1addce996768136f3a5b08381299f99b972d6a0f5fed26c65ccca97f9ceeccea61449e4f9281", 115 | "address": "5GsNYEXtBfGBN4buJEqRtGtonJkgq8Uo2dex3zqLcnwwfy3n", 116 | } 117 | 118 | ``` 119 | 120 | ### Message Field Descriptions 121 | 122 | - `identityNetwork` is network name, the payload is required since each chain may has its own algorithmic mechanism. the network name should be the official name known by the wallet and server-side. 123 | 124 | - `callbackEndpoint` is a url path, the **Signed-Message** will be sent to the path. 125 | 126 | - `scope` is used to require the access privilege for user's account resource.It is a list of information that user wishes to have resolved as part of authentication. 127 | 128 | - `challenge` is simple text from server-side , contains some unique information to indicate the current sign-in request. 129 | 130 | - `signature` is the value signed by Injected Signer with challenge and account address. 131 | 132 | - `address` is the account address which connected to the Injected Signer. 133 | 134 | # 135 | 136 | ### Resolving external identity Data 137 | 138 | - The server or wallet MAY additionally perform resolution of identity data, as this can improve the user experience by displaying a human-friendly information that is related to the `address`. Resolvable identity data include: 139 | - Identity related-information in identity pallet 140 | - The [Polkadot Name System](https://www.pns.link/). 141 | - If resolution of PNS data is performed, implementers SHOULD take precautions to preserve user privacy and consent, as their `address` could be forwarded to third party services as part of the resolution process. 142 | 143 | # 144 | 145 | ### Wallet Implementer Guidelines 146 | 147 | #### Display `Challenge-Message` 148 | 149 | - Wallet implementers MUST display the full `Challenge-Message` to the user. 150 | - Must request confirmation by user. 151 | 152 | #### Sign to `Signed-Message` 153 | 154 | - Wallet implementers MUST support sign the challenge message with account address, return the signed message. 155 | 156 | #### Supporting internationalization (i18n) 157 | 158 | - After successfully parsing the message, translation may happen at the UX level per human language. 159 | 160 | # 161 | 162 | ### Server-Side Implementer Guidelines 163 | 164 | #### Generate `Challenge-Message` 165 | 166 | - The message MUST be generated for sign-in request by server-side, the challenge message MUST be included. 167 | 168 | #### Support `Callback Endpoint` 169 | 170 | - The callback endpoint MUST be supported by server-side to receive and verify the signed message. 171 | 172 | #### Support verify `Signed-Message` 173 | 174 | - The server-side MUST support the verification of the signed message. 175 | 176 | #### Support return identity info 177 | 178 | - The server-side MUST support the identity info to the client. The identity info SHOULD contain the token or session info so that the client can interaction with server later. 179 | 180 | ## Copyright 181 | 182 | This PSP is placed in the [public domain](https://creativecommons.org/publicdomain/zero/1.0/). 183 | -------------------------------------------------------------------------------- /PSPs/drafts/psp-48.md: -------------------------------------------------------------------------------- 1 | # Node Telemetry Protocol 2 | 3 | - **PSP Number:** 48 4 | - **Authors:** Fabio Lama 5 | - **Status:** Draft 6 | - **Created:** 2022-07-06 7 | - **Reference Implementation** https://github.com/paritytech/substrate-telemetry 8 | 9 | ## Summary 10 | 11 | This document described the protocol to send and receive telemetry information 12 | produced by Polkadot Host implemenations. This information can be processed and 13 | displayed as seen on https://telemetry.polkadot.io, for example. 14 | 15 | ## Motivation 16 | 17 | Polkadot Host implemenations can send telemetry information about their current 18 | state and activity to one or more endpoints that collect and process that 19 | information. This gives observers an insight on the current state of the 20 | network, which includes block imports, finalization, ressource requirements and 21 | so on. Sending telemetry information is a voluntary act and is not required for Polkadot 22 | and Kusama to function properly, respectively it's not part of the consensus protocol. 23 | 24 | All telemetry messages are serialized in JSON format. 25 | 26 | ## Note on Security 27 | 28 | All telemetry information sent or collected is entirely **subjective** and its 29 | origin is untrusted. The telemetry information should offer an insight into the 30 | network and should not be interpreted at face value. Impersonations of 31 | participants cannot be prevented. 32 | 33 | ## Transport 34 | 35 | Telemetry messages are send over the websocket procotol. Conventially, the 36 | websocket stream connects on port `8000` to the endpoint `/submit`, which is not 37 | a requirement. Servers that collect telemetry data might have different 38 | requirements. 39 | 40 | An example of such an endpoint might look like `wss://telemetry.example.com:8000/submit`. 41 | 42 | ## Versioning 43 | 44 | All telemetry messages are versioned, allowing for more message types in the future. 45 | This standard introduces two versions represented as JSON messages, `V1` and `V2`. 46 | The payloads are documented in [JSON Messagew](#json-messages). 47 | 48 | ### V2 49 | 50 | | Name | Type | Required | Description | 51 | |---------|------|----------|---------------------| 52 | | id | UINT | YES | | 53 | | payload | ANY | YES | The payload message | 54 | 55 | Example: 56 | 57 | ```json 58 | { 59 | "id": 1, 60 | "payload": { 61 | "msg":"notify.finalized", 62 | "best":"0x031c3521ca2f9c673812d692fc330b9a18e18a2781e3f9976992f861fd3ea0cb", 63 | "height":"50" 64 | } 65 | } 66 | ``` 67 | 68 | ### V1 69 | 70 | In this version, the message payload is sent directly. This version can be 71 | detected if it cannot be decoded as `V2`. This version serves primarily for 72 | backwards-compatibility reasons. 73 | 74 | | Name | Type | Required | Description | 75 | |------|------|----------|----------------------------| 76 | | - | ANY | YES | Message is passed directly | 77 | 78 | Example: 79 | 80 | ```json 81 | { 82 | "msg":"notify.finalized", 83 | "best":"0x031c3521ca2f9c673812d692fc330b9a18e18a2781e3f9976992f861fd3ea0cb", 84 | "height":"50" 85 | } 86 | ``` 87 | 88 | ## JSON Messages 89 | 90 | All passed on information is subjective and in accordance with the implemenator 91 | of this protocol. 92 | 93 | ### System Connected 94 | 95 | Information about the system environment. 96 | 97 | | Name | Type | Required | Description | 98 | |---------------|---------------|----------|---------------------------------| 99 | | msg | STRING | YES | Constant "**system.connected**" | 100 | | genesis_hash | HEX_32 | YES | Genesis hash of the chain | 101 | | chain | STRING | YES | Name of the chain | 102 | | name | STRING | YES | Name of the node | 103 | | implemenation | STRING | YES | Name of the node implemenation | 104 | | version | STRING | YES | Node version, e.g. `0.9.17-75dd6c7d0`| 105 | | network_id | STRING_64 | YES | Network Id, e.g. `polkadot` or `ksmcc3`| 106 | | startup_time | STRING | NO | Startup time of the node | 107 | | target_os | STRING | NO | Operating system, e.g. `linux` | 108 | | target_arch | STRING | NO | CPU architecture, e.g `x86_64` | 109 | | target_env | STRING | NO | OS environment, e.g. `gnu` | 110 | | sysinfo | _NodeSysInfo_ | NO | | 111 | 112 | The fields `version`, `target_arch`, `target_os` and `target_env` concatenate 113 | to, for example, `0.9.17-75dd6c7d0-x86-linux-gnu`. 114 | 115 | The structure _NodeSysInfo_ is structured as: 116 | 117 | | Name | Type | Required | Description | 118 | |--------------------|---------|----------|--------------------------------------------------| 119 | | cpu | STRING | NO | Name of the CPU | 120 | | memory | UINT | NO | Memory in bytes | 121 | | core_count | UINT | NO | Number of cores | 122 | | linux_kernel | STRING | NO | Name of the Linux kernel | 123 | | linux_distro | STRING | NO | Name of the Linux distro | 124 | | is_virtual_machine | BOOLEAN | NO | Whether the node is running in a virtual machine | 125 | 126 | ### System Interval 127 | 128 | Information about the state of the system. 129 | 130 | | Name | Type | Required | Description | 131 | |-----------------------|--------|----------|-----------------------------------| 132 | | msg | STRING | YES | Constant "**system.interval**" | 133 | | peers | UINT | NO | Number of connected peers | 134 | | txcount | UINT | NO | Number of pending transactions | 135 | | bandwidth_upload | FLOAT | NO | Upload speed in kB/s | 136 | | bandwith_download | FLOAT | NO | Download speed in kB/s | 137 | | finalized_height | UINT | NO | Latest finalized block number | 138 | | finalized_hash | HEX_32 | NO | Latest finalized block hash | 139 | | height | UINT | NO* | Latest non-finalized block number _(* required if `best` is specified)_ | 140 | | best | HEX_32 | NO* | Latest non-finalized block hash _(* required if `height` is specified)_ | 141 | | used_state_cache_size | FLOAT | NO | Size of the node's state cache in MB/s | 142 | 143 | ### Block Import 144 | 145 | Information about an imported block. 146 | 147 | | Name | Type | Required | Description | 148 | |--------|--------|----------|-----------------------------| 149 | | msg | STRING | YES | Constant "**block.import**" | 150 | | height | UINT | YES | Block number | 151 | | best | HEX_32 | YES | Block hash | 152 | 153 | ### Notify Finalized 154 | 155 | Information about an imported, finalized block. 156 | 157 | | Name | Type | Required | Description | 158 | |--------|--------|----------|---------------------------------| 159 | | msg | STRING | YES | Constant "**notify.finalized**" | 160 | | height | STRING | YES | Block number | 161 | | hash | HEX_32 | YES | Block hash | 162 | 163 | ### (Afg) Authority Set 164 | 165 | Information about the GRANDPA authority set. 166 | 167 | | Name | Type | Required | Description | 168 | |------------------|--------|----------|----------------------------------| 169 | | msg | STRING | YES | Constant "**afg.authority_set**" | 170 | | authority_id | STRING | YES | The public key if the local node is an elected authority, empty value otherwise | 171 | | authority_set_id | STRING | YES | The Set Id of the current authority list | 172 | 173 | ### Hardware Bench 174 | 175 | Information about the hardware performance of the system. 176 | 177 | | Name | Type | Required | Description | 178 | |-----------------------------|--------|----------|--------------------------------| 179 | | msg | STRING | YES | Constant "**sysinfo.hwbench**" | 180 | | cpu_hashrate_score | UINT | YES | The number of Blake2 hashes it can generate on 32kB of data per second | 181 | | memory_memcpy_score | UINT | YES | How many MBs of data `memcpy` can copy per second | 182 | 183 | ## Recommended Behavior 184 | 185 | The telemetry protocol of a Polkadot Host implementation can behave according to 186 | its own rules. However, this section describes the recommended behavior that the 187 | implementation _should_ abide by, as is reflected in the 188 | [Substrate](https://github.com/paritytech/substrate) implementation. 189 | 190 | * The [`system.connected`](#system-connected) message should be sent once when 191 | the telemetry client starts, respectively on (re-)connection to the telemetry 192 | server. 193 | * The [`system.interval`](#system-interval) message should be sent every five 194 | seconds. 195 | * The [`block.import`](#block-import) message should be sent everytime a block 196 | is imported when the client is _fully synced_. The message should **NOT** be 197 | sent during sync in order not to spam the telemetry server. While the client 198 | is syncing, this message should be sent every 10 blocks. 199 | * The [`notify.finalized`](#notify-finalized) message should be sent everytime a 200 | _finalized_ block is imported when the client is _fully synced_. This message 201 | should **NOT** be sent during sync in order not to spam the telemetry server. 202 | This message should also trigger a [`block.import`](#block-import) message. 203 | * The [`afg.authority_set`](#afg-authority-set) message should be sent everytime 204 | the telemetry client starts, respectively on (re-)connection to the telemetry 205 | server and everytime the authority set changes (start of a new Era). 206 | * The [`sysinfo.hwbench`](#hardware-bench) message should be sent everytime the 207 | telemetry client starts, respectively on (re-)connection to the telemetry 208 | server. 209 | 210 | ## Copyright 211 | 212 | This PSP is placed in the [public domain](https://creativecommons.org/publicdomain/zero/1.0/). 213 | -------------------------------------------------------------------------------- /PSPs/drafts/psp-7.md: -------------------------------------------------------------------------------- 1 | # PSP-7: Polkadot DKG Threshold Multisig Wallet 2 | 3 | * **PSP Number:** 7 4 | * **Author(s):** Everstake 5 | * **Status:** Call for Feedback 6 | * **Created:** 2020-02-13 7 | * **Reference Implementation** [-] 8 | 9 | 10 | ## Summary 11 | Multisignature (multisig) refers to requiring more than one key to authorize a transaction. It is generally used to divide up responsibility for possession of assets. Using a multisig wallet users are able to prevent the loss or theft of their private keys. But still if one of the keys is compromised, the funds are safe. 12 | A multisig wallet can be implemented using cryptographic methods or non cryptographic ones with the help of smart contracts. With cryptography there are also couple of ways to do it: 13 | - Shamir Secret Sharing 14 | - Verifiable Secret Sharing 15 | - Naive multisignatures(like in Bitcoin) 16 | - Aggregated signatures 17 | - Distributed Key Generation 18 | 19 | Distributed Key Generation (DKG) is a way for a group of nodes to collectively agree on a public/private key pair without any single party knowing the private key. Everyone just knows the public key. 20 | This is actually very hard to achieve but it relies on the fact that lagrange interpolated shares are homomorphic (in that operations can be performed on shares even without knowing the full value). For example, you can add A{share1}+B{share1} to get C{share1} that you can add to someone else’s C{share2} to get the full value of C (assuming A and B were split into 2 shares each). 21 | 22 | 23 | ## Motivation 24 | So far Polkadot has two implementations: one is a non-cryptographic multisignature pallet and the other is the cryptographic Schnorrkel multisignature implementation that sits in the Polkadot Host. The goal is to expand the list of multsignature options within Polkadot and to eventually agree upon a standard. Eventually this will allow for the creation of wallets that have robust and well-reviewed code. 25 | 26 | If possible, it would be ideal to investigate the possibilities of standarising the use of DKG, such that a wallet can take advantage of DKG for the creation of Threshold Multisignature addresses. 27 | 28 | As compared to Shamir Secret Sharing or Verifiable Secret Sharing, by using DKG we avoid a single point of failure problem since all the participants generate their keys themselves and no one, except for a creator knows them. 29 | 30 | 31 | ### Feedback 32 | 33 | The timing of our PSP was imperfect to say the least since we initiated call for feedback at the time when most of the devs are preparing for the Polkadot mainnet launch and simply didn’t have time to review our PSP. So, I’m not sure how useful our feedback is going to be. 34 | 35 | In general, since this process is still new and not yet established, it lacks clarity in terms of goals, deliverables and mechanics of the process overall. Here are some of the questions we had to answer for ourselves when working on the PSP. Note, this is our experience while yours can be different; we’re posting it here because we think it might help someone in the same situation. 36 | 1. **Who are the audience of our PSP, regular users or devs? Should the language of the PSP be more general or technical?** Ours was purely technical and I think we should have explained certain things in a simpler fashion. 37 | 2. **What is the best way media channels and format for sharing with community?** We ended up with googledocs and medium and shared it in several Riot groups, TG chat and twitter. 38 | 3. **How long do we collect the feedback for and how often do we ‘remind’ people of our PSP?** We did one month and posted our call for feedback 5 times during this period. 39 | 4. **What are the criteria for the amount of feedback collected?** Because of the afore-mentioned reasons we were happy with any feedback. 40 | 5. **How do decide which feedback is the most relevant?** We were guided by common sense, just trying to understand the amount of value added by suggestions. 41 | 42 | We ended up calling our proposal PSP-2 because most of the existing PSPs don’t have a number attached to them. This can also be confusing a bit especially for somebody doing it for the first time. 43 | 44 | 45 | ## Specification 46 | For the key generation we want to implement the Rabin DKG protocol. That’s a secure protocol for distributed key generation in discrete-log based cryptosystems with threshold *t*, for any *t < n/2*. 47 | 48 | For signing we decided to use Distributed Schnorr Signature(DSS) which also gives us threshold functionality. 49 | 50 | Here are links on papers to learn more about these protocols: 51 | - DKG - https://www.researchgate.net/publication/227327292_Secure_Distributed_Key_Generation_for_Discrete-Log_Based_Cryptosystems 52 | - DSS - http://cacr.uwaterloo.ca/techreports/2001/corr2001-13.ps 53 | 54 | To implement these protocols we can have asynchronous communication but with adding some term bound to sending messages to avoid situations when transaction is stuck because of one participant is offline during days. We can achieve this with a web server through which participants will exchange information and where will store wallet and transactions statuses. 55 | 56 | All the commitments and partial signatures will go through the server, so the web server in our case is like a bridge between all the participants. Since the server doesn’t hold any private keys or other secret information it won’t reveal a distributed secret key even if it’s corrupted. All the secret information will be stored on the client side. 57 | 58 | In general procedure will look like this: 59 | 1. Everyone generate own priv and pub keys; 60 | 2. All participants exchange pub keys to everyone have list of all participant’s pub keys; 61 | 3. Users start to generate general distributed secret key by broadcasting commitments and responses; 62 | 4. When general secret is generated and everyone have pub key of secret, users can create multisig transaction. 63 | 64 | To make communication between users secure we will use AES256-GCM encryption to encode some messages. 65 | 66 | Here is sequence diagram to see all the steps in distributed key generating process. Suppose that wallet client connect to web-server through websocket and continuously receive new status. "Rust DKG lib" on diagram - Rust code compiled to WebAssembly to use it in JS. Also there are on diagrams "found message" symbols, it means that Wallet APP got new status from web-server through the websocket. 67 | 68 | ![](/src/psp-7/sequence_wallet_diagram.png) 69 | 70 | To sign transactions we will use the DSS protocol that was mentioned above. It’s secure 3 - round Schnorr signature scheme where signature is creating distributively. Final signature is compatible with the EdDSA verification function against the distributed secret key. When transaction is signed there are will be couple of options to send it. Transaction can be send by the last partisipant who add his partial signature or by the first who initiate transaction. 71 | 72 | ![](/src/psp-7/dss_sequence_diagram.png) 73 | 74 | ## Web-server wallet API 75 | 76 | Here is short web-server's API specification. Some endpoints can be changed with time. 77 | 78 | Version: 1.0.0 79 | 80 | ### /dkg/wallet/new 81 | 82 | #### POST 83 | ##### Summary: 84 | 85 | Create new multisig wallet 86 | *** 87 | ### /dkg/wallet/join 88 | 89 | #### POST 90 | ##### Summary: 91 | 92 | Join to multisig wallet 93 | *** 94 | ### /dkg/deal/broadcast 95 | 96 | #### POST 97 | ##### Summary: 98 | 99 | Broadcast Deal for some participant 100 | *** 101 | ### /dkg/deal/response 102 | 103 | #### POST 104 | ##### Summary: 105 | 106 | Broadcast Response of processed Deal 107 | *** 108 | ### /dkg/secretCommit/broadcast 109 | 110 | #### POST 111 | ##### Summary: 112 | 113 | Broadcast secretCommit for participants 114 | *** 115 | ### /dkg/secretCommit/response 116 | 117 | #### POST 118 | ##### Summary: 119 | 120 | Broadcast correctness signature of secretCommit 121 | *** 122 | ### /dss/transaction/new 123 | 124 | #### POST 125 | ##### Summary: 126 | 127 | Create(initiate) new multisig transaction 128 | *** 129 | ### /dss/transaction/status/randPub/generated 130 | 131 | #### POST 132 | ##### Summary: 133 | 134 | Announce that random key was generated and send to server pub key 135 | *** 136 | ### /dss/transaction/status/randKey/generated 137 | 138 | #### POST 139 | ##### Summary: 140 | 141 | Announce that random key was generated with DKG protocol 142 | *** 143 | ### /dss/transaction/partSignature/broadcast 144 | 145 | #### POST 146 | ##### Summary: 147 | 148 | Broadcast partial signature that was generated with DSS protocol 149 | *** 150 | ### /dss/transaction/partSignature/check 151 | 152 | #### POST 153 | ##### Summary: 154 | 155 | Broadcast check of partial signature 156 | *** 157 | ### /dss/transaction/status/finalSignature/reconstruct 158 | 159 | #### POST 160 | ##### Summary: 161 | 162 | Broadcast status of reconstruction final signature 163 | *** 164 | ### /dss/transaction/status/sent 165 | 166 | #### POST 167 | ##### Summary: 168 | 169 | Announce that transaction was sent 170 | *** 171 | ## Tests 172 | *If applicable, please include a list of potential test cases to validate an implementation.* 173 | 174 | ## Copyright 175 | 176 | Each PSP must be labeled as placed in the [public domain](https://creativecommons.org/publicdomain/zero/1.0/). 177 | 178 | ## Have feedback? 179 | 180 | You can let us know what you think using one of the channels below: 181 | 182 | Email: inbox@everstake.one 183 | 184 | Telegram: @vit_park 185 | 186 | Riot: @mrvatka32:matrix.org 187 | -------------------------------------------------------------------------------- /PSPs/psp-22.md: -------------------------------------------------------------------------------- 1 | # Fungible Token Standard for Substrate's `contracts` pallet 2 | 3 | - **PSP Number:** 22 4 | - **Authors:** Green Baneling , Markian , Pierre , Sven , Varg 5 | - **Status:** Published 6 | - **Created:** 2021-06-19 7 | - **Reference Implementation:** [OpenBrush](https://github.com/Brushfam/openbrush-contracts/blob/main/contracts/src/token/psp22/psp22.rs) 8 | 9 | 10 | ## Summary 11 | 12 | A standard for a fungible token interface for WebAssembly smart contracts which run on Substrate's [`contracts` pallet](https://github.com/paritytech/substrate/tree/master/frame/contracts). 13 | 14 | This proposal aims to define the standard fungible token interface for WebAssembly smart contracts, just like [EIP-20](https://github.com/ethereum/EIPs/edit/master/EIPS/eip-20.md) for the Ethereum ecosystem. 15 | 16 | ## Motivation 17 | 18 | Currently, while there is no standard, every contract will have different a signature. Thus, no interoperability is possible. This proposal aims to resolve that by defining one **interface** that shares the same **ABI** between all implementations. 19 | 20 | The goal is to have a standard contract interface that allows tokens on Polkadot/Kusama which implement it to be re-used by other applications: from wallets to decentralized exchanges. 21 | 22 | ## Implementations 23 | Examples of implementations: 24 | 25 | - [OpenBrush](https://github.com/Brushfam/openbrush-contracts/blob/main/contracts/src/token/psp22/psp22.rs), written in [ink!](https://github.com/paritytech/ink). 26 | 27 | ## Motivation for having a standard separate from ERC-20 28 | Due to the different nature of WebAssembly smart contracts and the difference between EVM and the [`contracts` pallet](https://github.com/paritytech/substrate/tree/master/frame/contracts) in Substrate, this standard proposal has specific rules and methods, 29 | therefore PSP-22 differs from ERC-20 in its implementation. 30 | 31 | Also, this standard proposal defines an extensive method list in the interface. Unlike ERC-20, it includes `increase_allowance` and `decrease_allowance`, and defines metadata fields as part of a separate interface. 32 | 33 | # This standard is at ABI level 34 | 35 | Substrate's [`contracts` pallet](https://github.com/paritytech/substrate/tree/master/frame/contracts) can execute any WebAssembly contract that implements its defined API; we do not want to restrain this standard to only Rust and [the ink! language](https://github.com/paritytech/ink), but make it possible to be implemented by any language/framework that compiles to WebAssembly. 36 | 37 | ## Specification 38 | 1. [Interfaces](#Interfaces) 39 | 2. [Events](#Events) 40 | 3. [Types](#Types) 41 | 4. [Errors](#Errors) 42 | 43 | ### Interfaces 44 | 45 | #### PSP-22 Interface 46 | 47 | This section defines the required interface for this standard. 48 | 49 | ##### **total_supply()** ➔ Balance 50 | Selector: `0x162df8c2` - first 4 bytes of `blake2b_256("PSP22::total_supply")` 51 | ```json 52 | { 53 | "args": [], 54 | "docs": [ 55 | "Returns the total token supply." 56 | ], 57 | "mutates": false, 58 | "name": [ 59 | "PSP22", 60 | "total_supply" 61 | ], 62 | "returnType": { 63 | "displayName": [ 64 | "Balance" 65 | ], 66 | "type": "Balance" 67 | }, 68 | "selector": "0x162df8c2" 69 | } 70 | ``` 71 | 72 | ##### **balance_of**(owner: AccountId) ➔ Balance 73 | Selector: `0x6568382f` - first 4 bytes of `blake2b_256("PSP22::balance_of")` 74 | ```json 75 | { 76 | "args": [ 77 | { 78 | "name": "owner", 79 | "type": { 80 | "displayName": [ 81 | "AccountId" 82 | ], 83 | "type": "AccountId" 84 | } 85 | } 86 | ], 87 | "docs": [ 88 | "Returns the account balance for the specified `owner`.", 89 | "", 90 | "Returns `0` if the account is non-existent." 91 | ], 92 | "mutates": false, 93 | "name": [ 94 | "PSP22", 95 | "balance_of" 96 | ], 97 | "returnType": { 98 | "displayName": [ 99 | "Balance" 100 | ], 101 | "type": "Balance" 102 | }, 103 | "selector": "0x6568382f" 104 | } 105 | ``` 106 | 107 | ##### **allowance**(owner: AccountId, spender: AccountId) ➔ Balance 108 | Selector: `0x4d47d921` - first 4 bytes of `blake2b_256("PSP22::allowance")` 109 | ```json 110 | { 111 | "args": [ 112 | { 113 | "name": "owner", 114 | "type": { 115 | "displayName": [ 116 | "AccountId" 117 | ], 118 | "type": "AccountId" 119 | } 120 | }, 121 | { 122 | "name": "spender", 123 | "type": { 124 | "displayName": [ 125 | "AccountId" 126 | ], 127 | "type": "AccountId" 128 | } 129 | } 130 | ], 131 | "docs": [ 132 | "Returns the amount which `spender` is still allowed to withdraw from `owner`.", 133 | "", 134 | "Returns `0` if no allowance has been set." 135 | ], 136 | "mutates": false, 137 | "name": [ 138 | "PSP22", 139 | "allowance" 140 | ], 141 | "returnType": { 142 | "displayName": [ 143 | "Balance" 144 | ], 145 | "type": "Balance" 146 | }, 147 | "selector": "0x4d47d921" 148 | } 149 | ``` 150 | 151 | ##### **transfer**(to: AccountId, value: Balance, data: [u8]) ➔ Result<(), PSP22Error> 152 | Selector: `0xdb20f9f5` - first 4 bytes of `blake2b_256("PSP22::transfer")` 153 | ```json 154 | { 155 | "args": [ 156 | { 157 | "name": "to", 158 | "type": { 159 | "displayName": [ 160 | "AccountId" 161 | ], 162 | "type": "AccountId" 163 | } 164 | }, 165 | { 166 | "name": "value", 167 | "type": { 168 | "displayName": [ 169 | "Balance" 170 | ], 171 | "type": "Balance" 172 | } 173 | }, 174 | { 175 | "name": "data", 176 | "type": { 177 | "displayName": [ 178 | "[u8]" 179 | ], 180 | "type": "[u8]" 181 | } 182 | } 183 | ], 184 | "docs": [ 185 | "Transfers `value` amount of tokens from the caller's account to account `to`", 186 | "with additional `data` in unspecified format.", 187 | "", 188 | "On success a `Transfer` event is emitted.", 189 | "", 190 | "# Errors", 191 | "", 192 | "Reverts with error `InsufficientBalance` if there are not enough tokens on", 193 | "the caller's account Balance.", 194 | "", 195 | "Reverts with error `ZeroSenderAddress` if sender's address is zero.", 196 | "", 197 | "Reverts with error `ZeroRecipientAddress` if recipient's address is zero." 198 | "Reverts with error `SafeTransferCheckFailed` if the recipient is a contract and rejected the transfer." 199 | ], 200 | "mutates": true, 201 | "name": [ 202 | "PSP22", 203 | "transfer" 204 | ], 205 | "returnType": { 206 | "displayName": [ 207 | "Result" 208 | ], 209 | "type": 1 210 | }, 211 | "selector": "0xdb20f9f5" 212 | } 213 | ``` 214 | 215 | ##### **transfer_from**(from: AccountId, to: AccountId, value: Balance, data: [u8]) ➔ Result<(), PSP22Error> 216 | Selector: `0x54b3c76e` - first 4 bytes of `blake2b_256("PSP22::transfer_from")` 217 | ```json 218 | { 219 | "args": [ 220 | { 221 | "name": "from", 222 | "type": { 223 | "displayName": [ 224 | "AccountId" 225 | ], 226 | "type": "AccountId" 227 | } 228 | }, 229 | { 230 | "name": "to", 231 | "type": { 232 | "displayName": [ 233 | "AccountId" 234 | ], 235 | "type": "AccountId" 236 | } 237 | }, 238 | { 239 | "name": "value", 240 | "type": { 241 | "displayName": [ 242 | "Balance" 243 | ], 244 | "type": "Balance" 245 | } 246 | }, 247 | { 248 | "name": "data", 249 | "type": { 250 | "displayName": [ 251 | "[u8]" 252 | ], 253 | "type": "[u8]" 254 | } 255 | } 256 | ], 257 | "docs": [ 258 | "Transfers `value` tokens on the behalf of `from` to the account `to`", 259 | "with additional `data` in unspecified format.", 260 | "", 261 | "This can be used to allow a contract to transfer tokens on ones behalf and/or", 262 | "to charge fees in sub-currencies, for example.", 263 | "", 264 | "On success a `Transfer` and `Approval` events are emitted.", 265 | "", 266 | "# Errors", 267 | "", 268 | "Reverts with error `InsufficientAllowance` if there are not enough tokens allowed", 269 | "for the caller to withdraw from `from`.", 270 | "", 271 | "Reverts with error `InsufficientBalance` if there are not enough tokens on", 272 | "the the account Balance of `from`.", 273 | "", 274 | "Reverts with error `ZeroSenderAddress` if sender's address is zero.", 275 | "", 276 | "Reverts with error `ZeroRecipientAddress` if recipient's address is zero." 277 | ], 278 | "mutates": true, 279 | "name": [ 280 | "PSP22", 281 | "transfer_from" 282 | ], 283 | "returnType": { 284 | "displayName": [ 285 | "Result" 286 | ], 287 | "type": 1 288 | }, 289 | "selector": "0x54b3c76e" 290 | } 291 | ``` 292 | 293 | ##### **approve**(spender: AccountId, value: Balance) ➔ Result<(), PSP22Error> 294 | Selector: `0xb20f1bbd` - first 4 bytes of `blake2b_256("PSP22::approve")` 295 | ```json 296 | { 297 | "args": [ 298 | { 299 | "name": "spender", 300 | "type": { 301 | "displayName": [ 302 | "AccountId" 303 | ], 304 | "type": "AccountId" 305 | } 306 | }, 307 | { 308 | "name": "value", 309 | "type": { 310 | "displayName": [ 311 | "Balance" 312 | ], 313 | "type": "Balance" 314 | } 315 | } 316 | ], 317 | "docs": [ 318 | "Allows `spender` to withdraw from the caller's account multiple times, up to", 319 | "the `value` amount.", 320 | "", 321 | "If this function is called again it overwrites the current allowance with `value`.", 322 | "", 323 | "An `Approval` event is emitted.", 324 | "", 325 | "# Errors", 326 | "", 327 | "Reverts with error `ZeroSenderAddress` if sender's address is zero.", 328 | "", 329 | "Reverts with error `ZeroRecipientAddress` if recipient's address is zero." 330 | ], 331 | "mutates": true, 332 | "name": [ 333 | "PSP22", 334 | "approve" 335 | ], 336 | "returnType": { 337 | "displayName": [ 338 | "Result" 339 | ], 340 | "type": 1 341 | }, 342 | "selector": "0xb20f1bbd" 343 | } 344 | 345 | ``` 346 | 347 | ##### **increase_allowance**(spender: AccountId, delta_value: Balance) ➔ Result<(), PSP22Error> 348 | Selector: `0x96d6b57a` - first 4 bytes of `blake2b_256("PSP22::increase_allowance")` 349 | ```json 350 | { 351 | "args": [ 352 | { 353 | "name": "spender", 354 | "type": { 355 | "displayName": [ 356 | "AccountId" 357 | ], 358 | "type": "AccountId" 359 | } 360 | }, 361 | { 362 | "name": "delta_value", 363 | "type": { 364 | "displayName": [ 365 | "Balance" 366 | ], 367 | "type": "Balance" 368 | } 369 | } 370 | ], 371 | "docs": [ 372 | "Atomically increases the allowance granted to `spender` by the caller.", 373 | "", 374 | "An `Approval` event is emitted.", 375 | "", 376 | "# Errors", 377 | "", 378 | "Reverts with error `ZeroSenderAddress` if sender's address is zero.", 379 | "", 380 | "Reverts with error `ZeroRecipientAddress` if recipient's address is zero." 381 | ], 382 | "mutates": true, 383 | "name": [ 384 | "PSP22", 385 | "increase_allowance" 386 | ], 387 | "returnType": { 388 | "displayName": [ 389 | "Result" 390 | ], 391 | "type": 1 392 | }, 393 | "selector": "0x96d6b57a" 394 | } 395 | ``` 396 | 397 | ##### **decrease_allowance**(spender: AccountId, delta_value: Balance) ➔ Result<(), PSP22Error> 398 | Selector: `0xfecb57d5` - first 4 bytes of `blake2b_256("PSP22::decrease_allowance")` 399 | ```json 400 | { 401 | "args": [ 402 | { 403 | "name": "spender", 404 | "type": { 405 | "displayName": [ 406 | "AccountId" 407 | ], 408 | "type": "AccountId" 409 | } 410 | }, 411 | { 412 | "name": "delta_value", 413 | "type": { 414 | "displayName": [ 415 | "Balance" 416 | ], 417 | "type": "Balance" 418 | } 419 | } 420 | ], 421 | "docs": [ 422 | "Atomically decreases the allowance granted to `spender` by the caller.", 423 | "", 424 | "An `Approval` event is emitted.", 425 | "", 426 | "# Errors", 427 | "", 428 | "Reverts with error `InsufficientAllowance` if there are not enough tokens allowed", 429 | "by owner for `spender`.", 430 | "", 431 | "Reverts with error `ZeroSenderAddress` if sender's address is zero.", 432 | "", 433 | "Reverts with error `ZeroRecipientAddress` if recipient's address is zero." 434 | ], 435 | "mutates": true, 436 | "name": [ 437 | "PSP22", 438 | "decrease_allowance" 439 | ], 440 | "returnType": { 441 | "displayName": [ 442 | "Result" 443 | ], 444 | "type": 1 445 | }, 446 | "selector": "0xfecb57d5" 447 | } 448 | ``` 449 | 450 | #### PSP22Metadata 451 | 452 | `PSP22Metadata` is an optional interface for metadata for this fungible token standard. 453 | 454 | ##### **token_name**() ➔ Option 455 | Selector: `0x3d261bd4` - first 4 bytes of `blake2b_256("PSP22Metadata::token_name")` 456 | ```json 457 | { 458 | "args": [], 459 | "docs": [ 460 | "Returns the token name." 461 | ], 462 | "mutates": false, 463 | "name": [ 464 | "PSP22Metadata", 465 | "token_name" 466 | ], 467 | "returnType": { 468 | "displayName": [ 469 | "Option" 470 | ], 471 | "type": "Option" 472 | }, 473 | "selector": "0x3d261bd4" 474 | } 475 | ``` 476 | 477 | ##### **token_symbol**() ➔ Option 478 | Selector: `0x34205be5` - first 4 bytes of `blake2b_256("PSP22Metadata::token_symbol")` 479 | ```json 480 | { 481 | "args": [], 482 | "docs": [ 483 | "Returns the token symbol." 484 | ], 485 | "mutates": false, 486 | "name": [ 487 | "PSP22Metadata", 488 | "token_symbol" 489 | ], 490 | "returnType": { 491 | "displayName": [ 492 | "Option" 493 | ], 494 | "type": "Option" 495 | }, 496 | "selector": "0x34205be5" 497 | } 498 | ``` 499 | 500 | ##### **token_decimals**() ➔ u8 501 | Selector: `0x7271b782` - first 4 bytes of `blake2b_256("PSP22Metadata::token_decimals")` 502 | ```json 503 | { 504 | "args": [], 505 | "docs": [ 506 | "Returns the token decimals." 507 | ], 508 | "mutates": false, 509 | "name": [ 510 | "PSP22Metadata", 511 | "token_decimals" 512 | ], 513 | "returnType": { 514 | "displayName": [ 515 | "u8" 516 | ], 517 | "type": "u8" 518 | }, 519 | "selector": "0x7271b782" 520 | } 521 | ``` 522 | 523 | ### Events 524 | 525 | #### Transfer 526 | When a contract creates (mints) new tokens, `from` will be `None`. 527 | When a contract deletes (burns) tokens, `to` will be `None`. 528 | ```json 529 | { 530 | "args": [ 531 | { 532 | "docs": [], 533 | "indexed": true, 534 | "name": "from", 535 | "type": { 536 | "displayName": [ 537 | "Option" 538 | ], 539 | "type": "Option" 540 | } 541 | }, 542 | { 543 | "docs": [], 544 | "indexed": true, 545 | "name": "to", 546 | "type": { 547 | "displayName": [ 548 | "Option" 549 | ], 550 | "type": "Option" 551 | } 552 | }, 553 | { 554 | "docs": [], 555 | "indexed": false, 556 | "name": "value", 557 | "type": { 558 | "displayName": [ 559 | "Balance" 560 | ], 561 | "type": "Balance" 562 | } 563 | } 564 | ], 565 | "docs": [ 566 | "Event emitted when a token transfer occurs." 567 | ], 568 | "name": "Transfer" 569 | } 570 | ``` 571 | 572 | ### Approval 573 | ```json 574 | { 575 | "args": [ 576 | { 577 | "docs": [], 578 | "indexed": true, 579 | "name": "owner", 580 | "type": { 581 | "displayName": [ 582 | "AccountId" 583 | ], 584 | "type": "AccountId" 585 | } 586 | }, 587 | { 588 | "docs": [], 589 | "indexed": true, 590 | "name": "spender", 591 | "type": { 592 | "displayName": [ 593 | "AccountId" 594 | ], 595 | "type": "AccountId" 596 | } 597 | }, 598 | { 599 | "docs": [], 600 | "indexed": false, 601 | "name": "value", 602 | "type": { 603 | "displayName": [ 604 | "Balance" 605 | ], 606 | "type": "Balance" 607 | } 608 | } 609 | ], 610 | "docs": [ 611 | "Event emitted when an approval occurs that `spender` is allowed to withdraw", 612 | "up to the amount of `value` tokens from `owner`." 613 | ], 614 | "name": "Approval" 615 | } 616 | ``` 617 | 618 | ### Types 619 | ```rust 620 | // AccountId is a 32 bytes Array, like in Substrate-based blockchains. 621 | type AccountId = [u8; 32]; 622 | 623 | // `u128` must be enough to cover most of the use-cases of standard tokens. 624 | type Balance = u128; 625 | ``` 626 | 627 | #### Return types 628 | ```json 629 | { 630 | "types": { 631 | "1": { 632 | "def": { 633 | "variant": { 634 | "variants": [ 635 | { 636 | "fields": [ 637 | { 638 | "type": { 639 | "def": { 640 | "tuple": [] 641 | } 642 | } 643 | } 644 | ], 645 | "name": "Ok" 646 | }, 647 | { 648 | "fields": [ 649 | { 650 | "type": { 651 | "def": { 652 | "variant": { 653 | "variants": [ 654 | { 655 | "fields": [ 656 | { 657 | "type": "string" 658 | } 659 | ], 660 | "name": "Custom" 661 | }, 662 | { 663 | "name": "InsufficientBalance" 664 | }, 665 | { 666 | "name": "InsufficientAllowance" 667 | }, 668 | { 669 | "name": "ZeroRecipientAddress" 670 | }, 671 | { 672 | "name": "ZeroSenderAddress" 673 | }, 674 | { 675 | "fields": [ 676 | { 677 | "type": "string" 678 | } 679 | ], 680 | "name": "SafeTransferCheckFailed" 681 | } 682 | ] 683 | } 684 | }, 685 | "path": [ 686 | "PSP22Error" 687 | ] 688 | } 689 | } 690 | ], 691 | "name": "Err" 692 | } 693 | ] 694 | } 695 | } 696 | } 697 | } 698 | } 699 | ``` 700 | 701 | ### Errors 702 | The suggested methods revert the transaction and return a [SCALE-encoded](https://github.com/paritytech/parity-scale-codec) `Result` type with one of the following `Error` enum variants: 703 | 704 | ```rust 705 | enum PSP22Error { 706 | /// Custom error type for cases in which an implementation adds its own restrictions. 707 | Custom(String), 708 | /// Returned if not enough balance to fulfill a request is available. 709 | InsufficientBalance, 710 | /// Returned if not enough allowance to fulfill a request is available. 711 | InsufficientAllowance, 712 | /// Returned if recipient's address is zero. 713 | ZeroRecipientAddress, 714 | /// Returned if sender's address is zero. 715 | ZeroSenderAddress, 716 | /// Returned if a safe transfer check fails (e.g. if the receiving contract does not accept tokens). 717 | SafeTransferCheckFailed(String), 718 | } 719 | ``` 720 | 721 | ## Copyright 722 | 723 | This PSP is placed in the [public domain](https://creativecommons.org/publicdomain/zero/1.0/). 724 | -------------------------------------------------------------------------------- /PSPs/psp-33.md: -------------------------------------------------------------------------------- 1 | ### XBI Format as a standard for Polkadot's XCM Transact 2 | PSP Number: 33 3 | Authors: Maciej Baj , Jacob Kowalewski 4 | Status: Published 5 | Created: 2022-08-08 6 | 7 | #### Summary 8 | XBI Format is an XCM-based high-level interface that each Parachain can optionally implement and enable others to use, while defining the error and result handling using an asynchronous, promise-like solution. XBI specifically focuses on setting the standards between cross-chain smart contract execution. 9 | 10 | 11 | XBI Format consists of two parts: 12 | 1. XBI Instruction ordered on destination Parachain to execute, over XCM Transact. 13 | 2. XBI Metadata specifies details of interoperable execution and receives asynchronous results. 14 | 15 | All XBI traffic goes over XCM Transact, therefore deriving its security from XCM. 16 | 17 | #### Motivation 18 | Set high-level format XBI standard for interfaces implementing interactions between Parachains, specifically EVM and WASM based contracts. 19 | 20 | XBI focuses on usability. It will recognise the difference between WASM and EVM, the most popular smart contract byte code in the Polkadot ecosystem today. 21 | 22 | The XBI interface offers contingencies against runtime upgrades while allowing Parachains to define and expose their functionalities without needing runtime upgrades upon introducing new XBI Instructions distinct for selected Parachains. 23 | 24 | XBI Metadata provides coherent controls over cross-chain execution, leaving dispatching origin in complete control over the execution costs and results format. 25 | 26 | 27 | #### Specification 28 | ##### Global XBI Types 29 | ```rust 30 | pub type AccountId32 = sp_runtime::AccountId32; 31 | pub type AccountId20 = sp_core::H160; 32 | pub type AssetId = u32; 33 | pub type Data = Vec; 34 | pub type Id = sp_core::H256; 35 | pub type Gas = u64; 36 | pub type Value = u128; 37 | pub type ValueEvm = sp_core::U256; 38 | pub type Target = u32; 39 | pub type Timeout = u32; 40 | ``` 41 | 42 | ##### XBI Metadata 43 | ```rust 44 | pub struct XBIMetadata { 45 | pub id: Id, 46 | pub dest_para_id: Target, 47 | pub src_para_id: Target, 48 | pub sent: Timeout, 49 | pub delivered: Timeout, 50 | pub executed: Timeout, 51 | pub max_exec_cost: Value, 52 | pub max_notifications_cost: Value, 53 | pub maybe_known_origin: Option, 54 | pub maybe_fee_asset_id: Option, 55 | } 56 | ``` 57 | ###### id 58 | Assign your ID to XBI, enabling the search engines to scrape through the XBI Orders across the Parachains Storage 59 | Only XBI Orders with distinct IDs are dispatched via XCM. 60 | 61 | ###### dest_para_id 62 | Parachain ID targeted to execute XBI. 63 | 64 | ###### src_para_id 65 | Parachain ID dispatching the XBI. 66 | 67 | ###### sent 68 | Timeout in seconds (hence independent of source Parachain block time) before XBI is sent from source Parachain via XCM. 69 | Resolves `XBI::Result` with `ErrorSentTimeoutExceeded` if dispatch queues from the source took too long to dispatch. 70 | 71 | ###### delivered 72 | Timeout in seconds (hence independent of target/transition Parachain block time) before XBI is delivered and reaches destination Parachain. 73 | Resolves `XBI::Result` with `ErrorDeliveryTimeoutExceeded` if before the dispatch to XBI check-in queue on the target Parachain the delivery timeout measured relatively to the sent timeout was exceeded. 74 | 75 | ###### executed 76 | Timeout in seconds (hence independent of target Parachain block time) before XBI starts executing. 77 | Resolves `XBI::Result` with `ErrorExecutionTimeoutExceeded` if before the execution on target Parachain the executed timeout measured relatively to the delivered timeout was exceeded. 78 | 79 | ###### max_exec_cost 80 | Maximum allowed execution costs on the destination Parachain. 81 | If no `maybe_fee_asset_id` is set, it's measured as the Parachain native currency units. 82 | Injects automatically into `gas_limit` for Smart Contract calls. 83 | 84 | ###### max_notifications_cost 85 | Maximum allowed notification costs of the spending to retrieve a result from either source or destination Parachains. 86 | If no `maybe_fee_asset_id` is set, it's measured in the units of native to Parachain currency. 87 | 88 | ###### maybe_known_origin 89 | Optional Metadata field allowing to specify the dispatching Origin. 90 | 91 | ###### maybe_fee_asset_id 92 | Optional Metadata field that changes `max_exec_cost` and `max_notifications_cost` as well as `actual_aggregated_costs` of XBI::Result to different currency with defined trade mechanics on source Parachain. 93 | 94 | ##### XBI Instructions 95 | Dynamically defined (without the necessity of runtime upgrades to all Parachains that communicate using XBI) set of Instructions to dispatch on target using XBI Format. It is expected for Parachains to only support a selected set of XBI Instructions, defaulting to `XBI::Unknown` if a given XBI Instruction isn't supported by the target. This is enabled thanks to the custom XBI codec. 96 | 97 | ###### 0 - Unknown 98 | Default to all of the unknown to target Parachain XBI Instructions 99 | ```rust 100 | // 0 101 | Unknown { 102 | identifier: u8, 103 | params: Vec, 104 | } 105 | ``` 106 | 107 | ###### 1 - CallNative 108 | Very generic Instruction assuming target Parachain implements the rules of decoding `payload` into dispatchable arguments to any Substrate Runtime Call. 109 | 110 | ```rust 111 | // 1 112 | CallNative { 113 | payload: Data, 114 | } 115 | ``` 116 | 117 | ###### 2 - CallEvm 118 | Call an EVM smart contract on the target Parachain. 119 | ```rust 120 | // 2 121 | CallEvm { 122 | source: AccountId20, 123 | target: AccountId20, 124 | value: ValueEvm, 125 | input: Data, 126 | gas_limit: Gas, 127 | max_fee_per_gas: ValueEvm, 128 | max_priority_fee_per_gas: Option, 129 | nonce: Option, 130 | access_list: Vec<(AccountId20, Vec)>, 131 | } 132 | ``` 133 | ###### 3 - CallWasm 134 | Call a WASM smart contract (implemented by Substrate's Pallet Contracts) on the target Parachain. 135 | ```rust 136 | // 3 137 | CallWasm { 138 | dest: AccountId32, 139 | value: Value, 140 | gas_limit: Gas, 141 | storage_deposit_limit: Option, 142 | data: Data, 143 | } 144 | ``` 145 | 146 | ###### 4 - CallCustomVM 147 | Call a custom smart contract Virtual Machine on target Parachain. 148 | ```rust 149 | // 4 150 | CallCustomVM { 151 | caller: AccountId32, 152 | dest: AccountId32, 153 | value: Value, 154 | input: Data, 155 | limit: Gas, 156 | additional_params: Data, 157 | } 158 | ``` 159 | 160 | ###### 5 - Transfer 161 | Transfer native to source Parachain currency to target 162 | ```rust 163 | // 5 164 | Transfer { 165 | dest: AccountId32, 166 | value: Value, 167 | } 168 | ``` 169 | 170 | ###### 6 - TransferAssets 171 | Transfer fungible currency of given `asset_id` supported by source Parachain to a target supporting the same currency. 172 | ```rust 173 | // 6 174 | TransferAssets { 175 | currency_id: AssetId, 176 | dest: AccountId32, 177 | value: Value, 178 | }, 179 | ``` 180 | ###### 7 - Swap 181 | Swap fungible currency of the given `asset_in` to another currency of `asset_out`. Target Parachain implements asset ids' trade mechanics and conversions to fungible currencies. 182 | 183 | ```rust 184 | // 7 185 | Swap { 186 | asset_out: AssetId, 187 | asset_in: AssetId, 188 | amount: Value, 189 | max_limit: Value, 190 | discount: bool, 191 | }, 192 | ``` 193 | ###### 8 - Add Liquidity 194 | Add liquidity in two fungible currencies, A and B, to a DeFi pool implemented on target Parachain. Target Parachain implements the conversions of asset ids to fungible currencies. 195 | 196 | ```rust 197 | // 8 198 | AddLiquidity { 199 | asset_a: AssetId, 200 | asset_b: AssetId, 201 | amount_a: Value, 202 | amount_b_max_limit: Value, 203 | }, 204 | ``` 205 | 206 | ###### 9 - Remove Liquidity 207 | Remove liquidity of two fungible currencies, A and B, from a DeFi pool implemented on target Parachain based on the amount of LP-share (liquidity amount). Target Parachain implements the conversions of asset ids to fungible currencies. 208 | 209 | ```rust 210 | // 9 211 | RemoveLiquidity { 212 | asset_a: AssetId, 213 | asset_b: AssetId, 214 | liquidity_amount: Value, 215 | }, 216 | ``` 217 | 218 | ###### 10 - Get Price 219 | Gets the price exchanging `amount` of currency A to currency B on Target Parachain that implements the trade mechanics and conversions of asset ids to fungible currencies. 220 | 221 | ```rust 222 | // 10 223 | GetPrice { 224 | asset_a: AssetId, 225 | asset_b: AssetId, 226 | amount: Value, 227 | }, 228 | ``` 229 | 230 | ###### 255 - Result 231 | XBI Result accompanying each XBI Instruction. With XBI, users get the guarantee that each XBI order will be resolved with one of the following outcomes: 232 | 233 | ```rust 234 | pub enum XBICheckOutStatus { 235 | // Success scenario 236 | SuccessfullyExecuted, 237 | 238 | // Failed execution scenarios 239 | ErrorFailedExecution, 240 | ErrorFailedOnXCMDispatch, 241 | 242 | // Failed with exceeded costs scenarios 243 | ErrorExecutionCostsExceededAllowedMax, 244 | ErrorNotificationsCostsExceededAllowedMax, 245 | 246 | // Failed with exceeded timeout scenarios 247 | ErrorSentTimeoutExceeded, 248 | ErrorDeliveryTimeoutExceeded, 249 | ErrorExecutionTimeoutExceeded, 250 | } 251 | ``` 252 | 253 | The future extensions to XBI foresee communication with remote to Polkadot consensus systems. Therefore field `witness` is already included as part of XBI Result and defaults to an empty bytes vector for all executions that target Parachain. 254 | ```rust 255 | // 255 256 | Result { 257 | outcome: XBICheckOutStatus, 258 | output: Data, 259 | witness: Data, 260 | actual_aggregated_costs: Value, 261 | }, 262 | ``` 263 | 264 | ### XBI in the future 265 | 266 | Here we will lay out other foundations concerning improvements on XBI; we will likely submit further PSPs to support hardening the specification and fulfilling the goal of a specific, extensible smart contract messaging interface over XCM. 267 | 268 | #### Discoverable and Dynamically updated schemas 269 | 270 | Web2 presented several patterns that were an integral building block for developers, one of them being the rise of [OpenAPI](https://swagger.io/specification/) and API-driven design. We aim for XBI to facilitate the discoverability of such a dynamic API, focusing on schema upgrades without upgrading the runtime. This approach is much like the usability of [GraphQL](https://graphql.org/learn/schema/), where developers provide as many handlers in business logic as possible, whilst maintainers can modify the schema at runtime or even create new ways to join and handle the data. 271 | 272 | ### XBI-as-a-module 273 | 274 | Whilst XBI can and should easily be implemented as a pallet that developers can reuse. We also strive to introduce XBI as a set of modules. We think a general approach would allow as much adoption and ease of use as possible, with minimal code changes. Namely, a Transmitter(enter)/Receiver(exit) pair with a set of interfaces allowing as much customization as possible. 275 | 276 | Some examples are: 277 | - custom serialization layers 278 | - custom storage approaches 279 | - only transmitters 280 | - only receivers 281 | 282 | ### XBI Collaborators 283 | 284 | XBI is a standard created to improve the usability of XCM and provide meaningful value to all adopters. It was developed collaboratively with the participation of some of the most prominent teams working with Substrate today to ensure that XBI was tailored to their needs. 285 | Below is a list of inputs/functions recommended by the respective teams consulted. 286 | 287 | #### Acala 288 | Request for Optional ID added to be added metadata. 289 | 290 | 291 | #### Moonbeam 292 | Reviewed XBI and commended it as it was, without stipulating any further requirements for improving it. 293 | 294 | #### Astar 295 | Requested cross-environmental calls to different VMs were made possible through implementing the SCABI (Smart Contract Application Binary Interface) library in XBI. 296 | 297 | #### Subsquid 298 | Stipulated usability of custom ID assignment as it enables their search engines to browse through storage on Parachains. 299 | 300 | ### Acknowledgments 301 | - Tomasz Drwiega 302 | - Bryan Chen, Acala 303 | - Hoon Kim, Astar Network 304 | - Massimo Lurasch, Subsquid 305 | -------------------------------------------------------------------------------- /PSPs/psp-34.md: -------------------------------------------------------------------------------- 1 | # Non-Fungible Token Standard for Substrate's `contracts` pallet 2 | 3 | - **PSP Number:** 34 4 | - **Authors:** Pierre Ossun , Green Baneling , Markian 5 | - **Status:** Published 6 | - **Created:** 2021-11-22 7 | - **Reference Implementation** [OpenBrush](https://github.com/Brushfam/openbrush-contracts/blob/main/contracts/src/token/psp34/psp34.rs) 8 | 9 | ## Summary 10 | 11 | A standard for a Non-Fungible Token interface for WebAssembly smart contracts which run on Substrate's [`contracts` pallet](https://github.com/paritytech/substrate/tree/master/frame/contracts). 12 | 13 | This proposal aims to define the standard Non-Fungible Token interface for WebAssembly smart contracts, just like [EIP-721](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md) for the Ethereum ecosystem. 14 | 15 | ## Motivation 16 | 17 | Without a standard interface for Non-Fungible Token every contract will have different signature and types. Hence, no interoperability is possible. 18 | This proposal aims to resolve that by defining one **interface** that shares the same **ABI** of **permissionless** methods between all implementations. 19 | 20 | The goal is to have a standard contract interface that allows tokens deployed on Substrate's `contracts` pallet to be re-used by other applications: from wallets to decentralized exchanges. 21 | 22 | ## Implementations 23 | Examples of implementations: 24 | 25 | - [OpenBrush](https://github.com/Brushfam/openbrush-contracts/blob/main/contracts/src/token/psp34/psp34.rs), written in [ink!](https://github.com/paritytech/ink). 26 | 27 | ## Motivation for having a standard separate from ERC-721 28 | Due to the different nature of WebAssembly smart contracts and the difference between EVM and the [`contracts` pallet](https://github.com/paritytech/substrate/tree/master/frame/contracts) in Substrate, this standard proposal has specific rules and methods, 29 | therefore PSP-34 differs from ERC-721 in its implementation. Also the proposal contains new methods that should improve the usability of Non-Fungible Token. 30 | 31 | # This standard is at ABI level 32 | 33 | Substrate's [`contracts` pallet](https://github.com/paritytech/substrate/tree/master/frame/contracts) can execute any WebAssembly contract that implements its defined API; we do not want to restrain this standard to only Rust and [the ink! language](https://github.com/paritytech/ink), but make it possible to be implemented by any language/framework that compiles to WebAssembly. 34 | 35 | ## Specification 36 | 1. [Interfaces](#Interfaces) 37 | 2. [Extension](#Extension) 38 | 3. [Events](#Events) 39 | 4. [Types](#Types) 40 | 5. [Errors](#Errors) 41 | 42 | ### Interfaces 43 | 44 | #### PSP-34 Interface 45 | 46 | This section defines the required interface for this standard. 47 | 48 | ##### **collection_id**() ➔ Id 49 | Selector: `0xffa27a5f` - first 4 bytes of `blake2b_256("PSP34::collection_id")` 50 | ```json 51 | { 52 | "args": [], 53 | "docs": [ 54 | "Returns the collection `Id` of the NFT token.", 55 | "", 56 | "This can represents the relationship between tokens/contracts/pallets." 57 | ], 58 | "mutates": false, 59 | "label": "PSP34::collection_id", 60 | "payable": false, 61 | "returnType": { 62 | "displayName": [ 63 | "Id" 64 | ], 65 | "type": "Id" 66 | }, 67 | "selector": "0xffa27a5f" 68 | } 69 | ``` 70 | 71 | ##### **balance_of**(owner: AccountId) ➔ u32 72 | Selector: `0xcde7e55f` - first 4 bytes of `blake2b_256("PSP34::balance_of")` 73 | ```json 74 | { 75 | "args": [ 76 | { 77 | "label": "owner", 78 | "type": { 79 | "displayName": [ 80 | "AccountId" 81 | ], 82 | "type": "AccountId" 83 | } 84 | } 85 | ], 86 | "docs": [ 87 | "Returns the balance of the owner.", 88 | "", 89 | "This represents the amount of unique tokens the owner has." 90 | ], 91 | "mutates": false, 92 | "label": "PSP34::balance_of", 93 | "payable": false, 94 | "returnType": { 95 | "displayName": [ 96 | "u32" 97 | ], 98 | "type": 1 99 | }, 100 | "selector": "0xcde7e55f" 101 | } 102 | ``` 103 | 104 | ##### **owner_of**(id: Id) ➔ Option 105 | Selector: `0x1168624d` - first 4 bytes of `blake2b_256("PSP34::owner_of")` 106 | ```json 107 | { 108 | "args": [ 109 | { 110 | "label": "id", 111 | "type": { 112 | "displayName": [ 113 | "Id" 114 | ], 115 | "type": "Id" 116 | } 117 | } 118 | ], 119 | "docs": [ 120 | "Returns the owner of the token if any." 121 | ], 122 | "mutates": false, 123 | "label": "PSP34::owner_of", 124 | "payable": false, 125 | "returnType": { 126 | "displayName": [ 127 | "Option" 128 | ], 129 | "type": "Option" 130 | }, 131 | "selector": "0x1168624d" 132 | } 133 | ``` 134 | 135 | ##### **allowance**(owner: AccountId, operator: AccountId, id: Option) ➔ bool 136 | Selector: `0x4790f55a` - first 4 bytes of `blake2b_256("PSP34::allowance")` 137 | ```json 138 | { 139 | "args": [ 140 | { 141 | "label": "owner", 142 | "type": { 143 | "displayName": [ 144 | "AccountId" 145 | ], 146 | "type": "AccountId" 147 | } 148 | }, 149 | { 150 | "label": "operator", 151 | "type": { 152 | "displayName": [ 153 | "AccountId" 154 | ], 155 | "type": "AccountId" 156 | } 157 | }, 158 | { 159 | "label": "id", 160 | "type": { 161 | "displayName": [ 162 | "Option" 163 | ], 164 | "type": "Option" 165 | } 166 | } 167 | ], 168 | "docs": [ 169 | "Returns `true` if the operator is approved by the owner to withdraw `id` token.", 170 | "If `id` is `None`, returns `true` if the operator is approved to withdraw all owner's tokens." 171 | ], 172 | "mutates": false, 173 | "label": "PSP34::allowance", 174 | "payable": false, 175 | "returnType": { 176 | "displayName": [ 177 | "bool" 178 | ], 179 | "type": "bool" 180 | }, 181 | "selector": "0x4790f55a" 182 | } 183 | ``` 184 | 185 | ##### **approve**(operator: AccountId, id: Option, approved: bool) ➔ Result<(), PSP34Error> 186 | Selector: `0x1932a8b0` - first 4 bytes of `blake2b_256("PSP34::approve")` 187 | ```json 188 | { 189 | "args": [ 190 | { 191 | "label": "operator", 192 | "type": { 193 | "displayName": [ 194 | "AccountId" 195 | ], 196 | "type": "AccountId" 197 | } 198 | }, 199 | { 200 | "label": "id", 201 | "type": { 202 | "displayName": [ 203 | "Option" 204 | ], 205 | "type": "Option" 206 | } 207 | }, 208 | { 209 | "name": "approved", 210 | "type": { 211 | "displayName": [ 212 | "bool" 213 | ], 214 | "type": "bool" 215 | } 216 | } 217 | ], 218 | "docs": [ 219 | "Approves `operator` to withdraw the `id` token from the caller's account.", 220 | "If `id` is `None` approves or disapproves the operator for all tokens of the caller.", 221 | "", 222 | "An `Approval` event is emitted.", 223 | "", 224 | "# Errors", 225 | "", 226 | "Returns `SelfApprove` error if it is self approve.", 227 | "", 228 | "Returns `NotApproved` error if caller is not owner of `id`." 229 | ], 230 | "mutates": true, 231 | "label": "PSP34::approve", 232 | "payable": false, 233 | "returnType": { 234 | "displayName": [ 235 | "Result" 236 | ], 237 | "type": 1 238 | }, 239 | "selector": "0x1932a8b0" 240 | } 241 | ``` 242 | 243 | ##### **transfer**(to: AccountId, id: Id, data: [u8]) ➔ Result<(), PSP34Error> 244 | Selector: `0x3128d61b` - first 4 bytes of `blake2b_256("PSP34::transfer")` 245 | ```json 246 | { 247 | "args": [ 248 | { 249 | "label": "to", 250 | "type": { 251 | "displayName": [ 252 | "AccountId" 253 | ], 254 | "type": "AccountId" 255 | } 256 | }, 257 | { 258 | "label": "id", 259 | "type": { 260 | "displayName": [ 261 | "Id" 262 | ], 263 | "type": "Id" 264 | } 265 | }, 266 | { 267 | "label": "data", 268 | "type": { 269 | "displayName": [ 270 | "[u8]" 271 | ], 272 | "type": "[u8]" 273 | } 274 | } 275 | ], 276 | "docs": [ 277 | "Transfer approved or owned token from caller.", 278 | "", 279 | "On success a `Transfer` event is emitted.", 280 | "", 281 | "# Errors", 282 | "", 283 | "Returns `TokenNotExists` error if `id` does not exist.", 284 | "", 285 | "Returns `NotApproved` error if `from` doesn't have allowance for transferring.", 286 | "", 287 | "Returns `SafeTransferCheckFailed` error if `to` doesn't accept transfer." 288 | ], 289 | "mutates": true, 290 | "label": "PSP34::transfer", 291 | "payable": false, 292 | "returnType": { 293 | "displayName": [ 294 | "Result" 295 | ], 296 | "type": 1 297 | }, 298 | "selector": "0x3128d61b" 299 | } 300 | ``` 301 | 302 | ##### **total_supply**() ➔ Balance 303 | Selector: `0x628413fe` - first 4 bytes of `blake2b_256("PSP34::total_supply")` 304 | ```json 305 | { 306 | "args": [], 307 | "docs": [ 308 | "Returns the current total supply of the NFT." 309 | ], 310 | "mutates": false, 311 | "label": "PSP34::total_supply", 312 | "returnType": { 313 | "displayName": [ 314 | "Balance" 315 | ], 316 | "type": "Balance" 317 | }, 318 | "selector": "0x628413fe" 319 | } 320 | ``` 321 | 322 | ### Extension 323 | 324 | #### PSP34Metadata 325 | 326 | `PSP34Metadata` is a **recommended** extension for this Non-Fungible Token standard 327 | because it is main feature of the NFT. 328 | 329 | ##### **get_attribute**(id: Id, key: [u8]) ➔ Option<[u8]> 330 | Selector: `0xf19d48d1` - first 4 bytes of `blake2b_256("PSP34Metadata::get_attribute")` 331 | ```json 332 | { 333 | "args": [ 334 | { 335 | "label": "id", 336 | "type": { 337 | "displayName": [ 338 | "Id" 339 | ], 340 | "type": "Id" 341 | } 342 | }, 343 | { 344 | "label": "key", 345 | "type": { 346 | "displayName": [ 347 | "[u8]" 348 | ], 349 | "type": "[u8]" 350 | } 351 | } 352 | ], 353 | "docs": [ 354 | "Returns the attribute of `id` for the given `key`.", 355 | "", 356 | "If `id` is a collection id of the token, it returns attributes for collection." 357 | ], 358 | "mutates": false, 359 | "label": "PSP34Metadata::get_attribute", 360 | "payable": false, 361 | "returnType": { 362 | "displayName": [ 363 | "Option" 364 | ], 365 | "type": "Option<[u8]>" 366 | }, 367 | "selector": "0xf19d48d1" 368 | } 369 | ``` 370 | 371 | Attributes are more flexible than single `tokenURI` function like in [`Erc721`](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md). 372 | The list of required attributes for NFT should be defined in a separate 373 | proposal based on the scope of the usage. 374 | 375 | #### PSP34Enumerable 376 | 377 | `PSP34Enumerable` is an **optional** extension for this Multi Token standard to support enumeration of tokens. 378 | 379 | 380 | ##### **owners_token_by_index**(owner: AccountId, index: u128) ➔ Option 381 | Selector: `0x3bcfb511` - first 4 bytes of `blake2b_256("PSP34Enumerable::owners_token_by_index")` 382 | ```json 383 | { 384 | "args": [ 385 | { 386 | "label": "owner", 387 | "type": { 388 | "displayName": [ 389 | "psp34enumerable_external", 390 | "OwnersTokenByIndexInput1" 391 | ], 392 | "type": 8 393 | } 394 | }, 395 | { 396 | "label": "index", 397 | "type": { 398 | "displayName": [ 399 | "psp34enumerable_external", 400 | "OwnersTokenByIndexInput2" 401 | ], 402 | "type": 6 403 | } 404 | } 405 | ], 406 | "docs": [ 407 | " Returns a token `Id` owned by `owner` at a given `index` of its token list.", 408 | " Use along with `balance_of` to enumerate all of ``owner``'s tokens." 409 | ], 410 | "label": "PSP34Enumerable::owners_token_by_index", 411 | "mutates": false, 412 | "payable": false, 413 | "returnType": { 414 | "displayName": [ 415 | "psp34enumerable_external", 416 | "OwnersTokenByIndexOutput" 417 | ], 418 | "type": 26 419 | }, 420 | "selector": "0x3bcfb511" 421 | } 422 | ``` 423 | 424 | ##### **token_by_index**(index: u128) -> Option 425 | Selector: `0xcd0340d0` - first 4 bytes of `blake2b_256("PSP37Enumerable::owners_token_by_index")` 426 | ```json 427 | { 428 | "args": [ 429 | { 430 | "label": "index", 431 | "type": { 432 | "displayName": [ 433 | "psp34enumerable_external", 434 | "TokenByIndexInput1" 435 | ], 436 | "type": 6 437 | } 438 | } 439 | ], 440 | "docs": [ 441 | " Returns a token `Id` at a given `index` of all the tokens stored by the contract.", 442 | " Use along with `total_supply` to enumerate all tokens." 443 | ], 444 | "label": "PSP34Enumerable::token_by_index", 445 | "mutates": false, 446 | "payable": false, 447 | "returnType": { 448 | "displayName": [ 449 | "psp34enumerable_external", 450 | "TokenByIndexOutput" 451 | ], 452 | "type": 26 453 | }, 454 | "selector": "0xcd0340d0" 455 | } 456 | ``` 457 | 458 | ### Events 459 | 460 | #### Transfer 461 | When a contract creates (mints) new tokens, `from` will be `None`. 462 | When a contract deletes (burns) tokens, `to` will be `None`. 463 | ```json 464 | { 465 | "args": [ 466 | { 467 | "docs": [], 468 | "indexed": true, 469 | "label": "from", 470 | "type": { 471 | "displayName": [ 472 | "Option" 473 | ], 474 | "type": "Option" 475 | } 476 | }, 477 | { 478 | "docs": [], 479 | "indexed": true, 480 | "label": "to", 481 | "type": { 482 | "displayName": [ 483 | "Option" 484 | ], 485 | "type": "Option" 486 | } 487 | }, 488 | { 489 | "docs": [], 490 | "indexed": true, 491 | "label": "id", 492 | "type": { 493 | "displayName": [ 494 | "Id" 495 | ], 496 | "type": "Id" 497 | } 498 | } 499 | ], 500 | "docs": [ 501 | "Event emitted when a token transfer occurs." 502 | ], 503 | "label": "Transfer" 504 | } 505 | ``` 506 | 507 | ### Approval 508 | ```json 509 | { 510 | "args": [ 511 | { 512 | "docs": [], 513 | "indexed": true, 514 | "label": "owner", 515 | "type": { 516 | "displayName": [ 517 | "AccountId" 518 | ], 519 | "type": "AccountId" 520 | } 521 | }, 522 | { 523 | "docs": [], 524 | "indexed": true, 525 | "label": "operator", 526 | "type": { 527 | "displayName": [ 528 | "AccountId" 529 | ], 530 | "type": "AccountId" 531 | } 532 | }, 533 | { 534 | "docs": [], 535 | "indexed": true, 536 | "label": "id", 537 | "type": { 538 | "displayName": [ 539 | "Option" 540 | ], 541 | "type": "Option" 542 | } 543 | }, 544 | { 545 | "docs": [], 546 | "indexed": false, 547 | "label": "approved", 548 | "type": { 549 | "displayName": [ 550 | "bool" 551 | ], 552 | "type": "bool" 553 | } 554 | } 555 | ], 556 | "docs": [ 557 | "Event emitted when a token approve occurs." 558 | ], 559 | "label": "Approval" 560 | } 561 | ``` 562 | 563 | ### AttributeSet 564 | ```json 565 | { 566 | "args": [ 567 | { 568 | "docs": [], 569 | "indexed": false, 570 | "label": "id", 571 | "type": { 572 | "displayName": [ 573 | "Id" 574 | ], 575 | "type": "Id" 576 | } 577 | }, 578 | { 579 | "docs": [], 580 | "indexed": false, 581 | "label": "key", 582 | "type": { 583 | "displayName": [ 584 | "[u8]" 585 | ], 586 | "type": "[u8]" 587 | } 588 | }, 589 | { 590 | "docs": [], 591 | "indexed": false, 592 | "label": "data", 593 | "type": { 594 | "displayName": [ 595 | "[u8]" 596 | ], 597 | "type": "[u8]" 598 | } 599 | } 600 | ], 601 | "docs": [ 602 | "Event emitted when an attribute is set for a token.", 603 | ], 604 | "label": "AttributeSet" 605 | } 606 | ``` 607 | 608 | ### Types 609 | ```rust 610 | // Id is an Enum and its variant are types 611 | enum Id { 612 | U8(u8), 613 | U16(u16), 614 | U32(u32), 615 | U64(u64), 616 | U128(u128), 617 | Bytes(Vec), 618 | } 619 | 620 | // AccountId is a 32 bytes Array, like in Substrate-based blockchains. 621 | type AccountId = [u8; 32]; 622 | ``` 623 | 624 | #### Return types 625 | ```json 626 | { 627 | "types": { 628 | "1": { 629 | "def": { 630 | "variant": { 631 | "variants": [ 632 | { 633 | "fields": [ 634 | { 635 | "type": { 636 | "def": { 637 | "tuple": [] 638 | } 639 | } 640 | } 641 | ], 642 | "name": "Ok" 643 | }, 644 | { 645 | "fields": [ 646 | { 647 | "type": { 648 | "def": { 649 | "variant": { 650 | "variants": [ 651 | { 652 | "fields": [ 653 | { 654 | "type": "string" 655 | } 656 | ], 657 | "name": "Custom" 658 | }, 659 | { 660 | "name": "SelfApprove" 661 | }, 662 | { 663 | "name": "NotApproved" 664 | }, 665 | { 666 | "name": "TokenExists" 667 | }, 668 | { 669 | "name": "TokenNotExists" 670 | }, 671 | { 672 | "fields": [ 673 | { 674 | "type": "string" 675 | } 676 | ], 677 | "name": "SafeTransferCheckFailed" 678 | } 679 | ] 680 | } 681 | }, 682 | "path": [ 683 | "PSP34Error" 684 | ] 685 | } 686 | } 687 | ], 688 | "name": "Err" 689 | } 690 | ] 691 | } 692 | } 693 | } 694 | } 695 | } 696 | ``` 697 | 698 | ### Errors 699 | The suggested methods revert the transaction and return a [SCALE-encoded](https://github.com/paritytech/parity-scale-codec) `Result` type with one of the following `Error` enum variants: 700 | 701 | ```rust 702 | enum PSP34Error { 703 | /// Custom error type for cases if writer of traits added own restrictions 704 | Custom(String), 705 | /// Returned if owner approves self 706 | SelfApprove, 707 | /// Returned if the caller doesn't have allowance for transferring. 708 | NotApproved, 709 | /// Returned if the owner already own the token. 710 | TokenExists, 711 | /// Returned if the token doesn't exist 712 | TokenNotExists, 713 | /// Returned if safe transfer check fails 714 | SafeTransferCheckFailed(String), 715 | } 716 | ``` 717 | 718 | ## Copyright 719 | 720 | This PSP is placed in the [public domain](https://creativecommons.org/publicdomain/zero/1.0/). 721 | -------------------------------------------------------------------------------- /PSPs/psp-37.md: -------------------------------------------------------------------------------- 1 | # Multi Token Standard for Substrate's `contracts` pallet 2 | 3 | - **PSP Number:** 37 4 | - **Authors:** Pierre Ossun , Green Baneling , Markian 5 | - **Status:** Published 6 | - **Created:** 2022-03-01 7 | - **Reference Implementation** [OpenBrush](https://github.com/Brushfam/openbrush-contracts/blob/main/contracts/src/token/psp37/psp37.rs) 8 | 9 | ## Summary 10 | 11 | A standard for a Multi Token interface for WebAssembly smart contracts which run on Substrate's [`contracts` pallet](https://github.com/paritytech/substrate/tree/master/frame/contracts). 12 | 13 | This proposal aims to define the standard Multi Token interface for WebAssembly smart contracts, just like [EIP-1155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md) for the Ethereum ecosystem. 14 | 15 | ## Motivation 16 | 17 | Without a standard interface for Multi Token every contract will have different signature and types. Hence, no interoperability is possible. 18 | This proposal aims to resolve that by defining one **interface** that shares the same **ABI** of **permissionless** methods between all implementations. 19 | 20 | The goal is to have a standard contract interface that allows tokens deployed on Substrate's `contracts` pallet to be re-used by other applications: from wallets to decentralized exchanges. 21 | 22 | ## Implementations 23 | Examples of implementations: 24 | 25 | - [OpenBrush](https://github.com/Brushfam/openbrush-contracts/blob/main/contracts/src/token/psp37/psp37.rs), written in [ink!](https://github.com/paritytech/ink). 26 | 27 | ## Motivation for having a standard separate from ERC-1155 28 | Due to the different nature of WebAssembly smart contracts and the difference between EVM and the [`contracts` pallet](https://github.com/paritytech/substrate/tree/master/frame/contracts) in Substrate, this standard proposal has specific rules and methods, 29 | therefore PSP-37 differs from ERC-1155 in its implementation. 30 | 31 | Also the proposal contains new: 32 | - types to keep interoperability with Fungible and Non-Fungible tokens standard defined in previous PSPs. 33 | - methods to introduce new features 34 | 35 | # This standard is at ABI level 36 | 37 | Substrate's [`contracts` pallet](https://github.com/paritytech/substrate/tree/master/frame/contracts) can execute any WebAssembly contract that implements its defined API; we do not want to restrain this standard to only Rust and [the ink! language](https://github.com/paritytech/ink), but make it possible to be implemented by any language/framework that compiles to WebAssembly. 38 | 39 | ## Specification 40 | 1. [Interfaces](#Interfaces) 41 | 2. [Extension](#Extension) 42 | 3. [Events](#Events) 43 | 4. [Types](#Types) 44 | 5. [Errors](#Errors) 45 | 46 | ### Interfaces 47 | 48 | #### PSP-37 Interface 49 | 50 | This section defines the required interface for this standard. 51 | 52 | ##### **balance_of**(owner: AccountId, id: Option) ➔ Balance 53 | Selector: `0xc42919e2` - first 4 bytes of `blake2b_256("PSP37::balance_of")` 54 | ```json 55 | { 56 | "args": [ 57 | { 58 | "label": "owner", 59 | "type": { 60 | "displayName": [ 61 | "AccountId" 62 | ], 63 | "type": "AccountId" 64 | } 65 | }, 66 | { 67 | "label": "id", 68 | "type": { 69 | "displayName": [ 70 | "Option" 71 | ], 72 | "type": "Option" 73 | } 74 | } 75 | ], 76 | "docs": [ 77 | " Returns the amount of tokens of token type `id` owned by `account`.", 78 | "", 79 | " If `id` is `None` returns the total number of `owner`'s tokens." 80 | ], 81 | "label": "PSP37::balance_of", 82 | "mutates": false, 83 | "payable": false, 84 | "returnType": { 85 | "displayName": [ 86 | "Balance" 87 | ], 88 | "type": "Balance" 89 | }, 90 | "selector": "0xc42919e2" 91 | } 92 | ``` 93 | 94 | ##### **total_supply**(id: Option) ➔ Balance 95 | Selector: `0x9a49e85a` - first 4 bytes of `blake2b_256("PSP37::total_supply")` 96 | ```json 97 | { 98 | "args": [ 99 | { 100 | "label": "id", 101 | "type": { 102 | "displayName": [ 103 | "Option" 104 | ], 105 | "type": "Option" 106 | } 107 | } 108 | ], 109 | "docs": [ 110 | " Returns the total amount of token type `id` in the supply.", 111 | "", 112 | " If `id` is `None` returns the total number of tokens." 113 | ], 114 | "label": "PSP37::total_supply", 115 | "mutates": false, 116 | "payable": false, 117 | "returnType": { 118 | "displayName": [ 119 | "Balance" 120 | ], 121 | "type": "Balance" 122 | }, 123 | "selector": "0x9a49e85a" 124 | } 125 | ``` 126 | 127 | ##### **allowance**(owner: AccountId, operator: AccountId, id: Option) ➔ Balance 128 | Selector: `0xcb78a065` - first 4 bytes of `blake2b_256("PSP37::allowance")` 129 | ```json 130 | { 131 | "args": [ 132 | { 133 | "label": "owner", 134 | "type": { 135 | "displayName": [ 136 | "AccountId" 137 | ], 138 | "type": "AccountId" 139 | } 140 | }, 141 | { 142 | "label": "operator", 143 | "type": { 144 | "displayName": [ 145 | "AccountId" 146 | ], 147 | "type": "AccountId" 148 | } 149 | }, 150 | { 151 | "label": "id", 152 | "type": { 153 | "displayName": [ 154 | "Option" 155 | ], 156 | "type": "Option" 157 | } 158 | } 159 | ], 160 | "docs": [ 161 | " Returns amount of `id` token of `owner` that `operator` can withdraw", 162 | " If `id` is `None` returns allowance `Balance::MAX` of all tokens of `owner`" 163 | ], 164 | "label": "PSP37::allowance", 165 | "mutates": false, 166 | "payable": false, 167 | "returnType": { 168 | "displayName": [ 169 | "Balance" 170 | ], 171 | "type": "Balance" 172 | }, 173 | "selector": "0xcb78a065" 174 | } 175 | ``` 176 | 177 | ##### **approve**(operator: AccountId, id: Option, value: Balance) ➔ Result<(), PSP37Error> 178 | Selector: `0x31a1a453` - first 4 bytes of `blake2b_256("PSP37::approve")` 179 | ```json 180 | { 181 | "args": [ 182 | { 183 | "label": "operator", 184 | "type": { 185 | "displayName": [ 186 | "AccountId" 187 | ], 188 | "type": "AccountId" 189 | } 190 | }, 191 | { 192 | "label": "id", 193 | "type": { 194 | "displayName": [ 195 | "Option" 196 | ], 197 | "type": "Option" 198 | } 199 | }, 200 | { 201 | "label": "value", 202 | "type": { 203 | "displayName": [ 204 | "Balance" 205 | ], 206 | "type": "Balance" 207 | } 208 | } 209 | ], 210 | "docs": [ 211 | " Allows `operator` to withdraw the `id` token from the caller's account", 212 | " multiple times, up to the `value` amount.", 213 | " If this function is called again it overwrites the current allowance with `value`", 214 | " If `id` is `None` approves or disapproves the operator for all tokens of the caller.", 215 | "", 216 | " An `Approval` event is emitted." 217 | ], 218 | "label": "PSP37::approve", 219 | "mutates": true, 220 | "payable": false, 221 | "returnType": { 222 | "displayName": [ 223 | "Result" 224 | ], 225 | "type": 1 226 | }, 227 | "selector": "0x31a1a453" 228 | } 229 | ``` 230 | 231 | ##### **transfer**(to: AccountId, id: Id, value: Balance, data: [u8]) ➔ Result<(), PSP37Error> 232 | Selector: `0x04e09961` - first 4 bytes of `blake2b_256("PSP37::transfer")` 233 | ```json 234 | { 235 | "args": [ 236 | { 237 | "label": "to", 238 | "type": { 239 | "displayName": [ 240 | "AccountId" 241 | ], 242 | "type": "AccountId" 243 | } 244 | }, 245 | { 246 | "label": "id", 247 | "type": { 248 | "displayName": [ 249 | "Id" 250 | ], 251 | "type": "Id" 252 | } 253 | }, 254 | { 255 | "label": "value", 256 | "type": { 257 | "displayName": [ 258 | "Balance" 259 | ], 260 | "type": "Balance" 261 | } 262 | }, 263 | { 264 | "label": "data", 265 | "type": { 266 | "displayName": [ 267 | "[u8]" 268 | ], 269 | "type": "[u8]" 270 | } 271 | } 272 | ], 273 | "docs": [ 274 | " Transfers `value` of `id` token from `caller` to `to`", 275 | "", 276 | " On success a `TransferSingle` event is emitted.", 277 | "", 278 | " # Errors", 279 | "", 280 | " Returns `TransferToZeroAddress` error if recipient is zero account.", 281 | "", 282 | " Returns `NotAllowed` error if transfer is not approved.", 283 | "", 284 | " Returns `InsufficientBalance` error if `caller` doesn't contain enough balance.", 285 | "", 286 | " Returns `SafeTransferCheckFailed` error if `to` doesn't accept transfer." 287 | ], 288 | "label": "PSP37::transfer", 289 | "mutates": true, 290 | "payable": false, 291 | "returnType": { 292 | "displayName": [ 293 | "Result" 294 | ], 295 | "type": 1 296 | }, 297 | "selector": "0x04e09961" 298 | } 299 | ``` 300 | 301 | ##### **transfer_from**(from: AccountId, to: AccountId, id: Id, value: Balance, data: [u8]) ➔ Result<(), PSP37Error> 302 | Selector: `0x5cf8b7d4` - first 4 bytes of `blake2b_256("PSP37::transfer_from")` 303 | ```json 304 | { 305 | "args": [ 306 | { 307 | "label": "from", 308 | "type": { 309 | "displayName": [ 310 | "AccountId" 311 | ], 312 | "type": "AccountId" 313 | } 314 | }, 315 | { 316 | "label": "to", 317 | "type": { 318 | "displayName": [ 319 | "AccountId" 320 | ], 321 | "type": "AccountId" 322 | } 323 | }, 324 | { 325 | "label": "id", 326 | "type": { 327 | "displayName": [ 328 | "Id" 329 | ], 330 | "type": "Id" 331 | } 332 | }, 333 | { 334 | "label": "amount", 335 | "type": { 336 | "displayName": [ 337 | "Balance" 338 | ], 339 | "type": "Balance" 340 | } 341 | }, 342 | { 343 | "label": "data", 344 | "type": { 345 | "displayName": [ 346 | "[u8]" 347 | ], 348 | "type": "[u8]" 349 | } 350 | } 351 | ], 352 | "docs": [ 353 | " Transfers `amount` tokens of token type `id` from `from` to `to`. Also some `data` can be passed.", 354 | "", 355 | " On success a `TransferSingle` event is emitted.", 356 | "", 357 | " # Errors", 358 | "", 359 | " Returns `TransferToZeroAddress` error if recipient is zero account.", 360 | "", 361 | " Returns `NotAllowed` error if transfer is not approved.", 362 | "", 363 | " Returns `InsufficientBalance` error if `from` doesn't contain enough balance.", 364 | "", 365 | " Returns `SafeTransferCheckFailed` error if `to` doesn't accept transfer." 366 | ], 367 | "label": "PSP37::transfer_from", 368 | "mutates": true, 369 | "payable": false, 370 | "returnType": { 371 | "displayName": [ 372 | "Result" 373 | ], 374 | "type": 1 375 | }, 376 | "selector": "0x5cf8b7d4" 377 | } 378 | ``` 379 | 380 | ### Extension 381 | 382 | #### PSP37Metadata 383 | 384 | `PSP37Metadata` is an **optional** extension for this Multi Token standard to provide information regarding each token. 385 | 386 | ##### **get_attribute**(id: Id, key: [u8]) ➔ Option<[u8]> 387 | Selector: `0x61dda97c` - first 4 bytes of `blake2b_256("PSP37Metadata::get_attribute")` 388 | ```json 389 | { 390 | "args": [ 391 | { 392 | "label": "id", 393 | "type": { 394 | "displayName": [ 395 | "Id" 396 | ], 397 | "type": "Id" 398 | } 399 | }, 400 | { 401 | "label": "key", 402 | "type": { 403 | "displayName": [ 404 | "[u8]" 405 | ], 406 | "type": "[u8]" 407 | } 408 | } 409 | ], 410 | "docs": [], 411 | "label": "PSP37Metadata::get_attribute", 412 | "mutates": false, 413 | "payable": false, 414 | "returnType": { 415 | "displayName": [ 416 | "Option" 417 | ], 418 | "type": "Option<[u8]>" 419 | }, 420 | "selector": "0x61dda97c" 421 | } 422 | ``` 423 | 424 | Attributes are more flexible than single `uri` function like in [`Erc1155`](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md). 425 | The list of required attributes for Multi token should be defined in a separate 426 | proposal based on the scope of the usage. 427 | 428 | #### PSP37Batch 429 | 430 | `PSP37Batch` is an **optional** extension for this Multi Token standard to support batch operations. 431 | 432 | 433 | ##### **batch_transfer**(to: AccountId, ids_amounts: [(Id, Balance)], data: [u8]) ➔ Result<(), PSP37Error> 434 | Selector: `0x9bfb1d2b` - first 4 bytes of `blake2b_256("PSP37Batch::batch_transfer")` 435 | ```json 436 | { 437 | "args": [ 438 | { 439 | "label": "to", 440 | "type": { 441 | "displayName": [ 442 | "AccountId" 443 | ], 444 | "type": "AccountId" 445 | } 446 | }, 447 | { 448 | "label": "ids_amounts", 449 | "type": { 450 | "displayName": [ 451 | "[(Id, Balance)]" 452 | ], 453 | "type": "[(Id, Balance)]" 454 | } 455 | }, 456 | { 457 | "label": "data", 458 | "type": { 459 | "displayName": [ 460 | "[u8]" 461 | ], 462 | "type": "[u8]" 463 | } 464 | } 465 | ], 466 | "docs": [], 467 | "label": "PSP37Batch::batch_transfer", 468 | "mutates": true, 469 | "payable": false, 470 | "returnType": { 471 | "displayName": [ 472 | "Result" 473 | ], 474 | "type": 1 475 | }, 476 | "selector": "0x9bfb1d2b" 477 | } 478 | ``` 479 | 480 | ##### **batch_transfer_from**(from: AccountId, to: AccountId, ids_amounts: [(Id, Balance)], data: [u8]) ➔ Result<(), PSP37Error> 481 | Selector: `0xf4ebeed2` - first 4 bytes of `blake2b_256("PSP37Batch::batch_transfer_from")` 482 | ```json 483 | { 484 | "args": [ 485 | { 486 | "label": "from", 487 | "type": { 488 | "displayName": [ 489 | "AccountId" 490 | ], 491 | "type": "AccountId" 492 | } 493 | }, 494 | { 495 | "label": "to", 496 | "type": { 497 | "displayName": [ 498 | "AccountId" 499 | ], 500 | "type": "AccountId" 501 | } 502 | }, 503 | { 504 | "label": "ids_amounts", 505 | "type": { 506 | "displayName": [ 507 | "[(Id, Balance)]" 508 | ], 509 | "type": "[(Id, Balance)]" 510 | } 511 | }, 512 | { 513 | "label": "data", 514 | "type": { 515 | "displayName": [ 516 | "[u8]" 517 | ], 518 | "type": "[u8]" 519 | } 520 | } 521 | ], 522 | "docs": [], 523 | "label": "PSP37Batch::batch_transfer_from", 524 | "mutates": true, 525 | "payable": false, 526 | "returnType": { 527 | "displayName": [ 528 | "Result" 529 | ], 530 | "type": 1 531 | }, 532 | "selector": "0xf4ebeed2" 533 | } 534 | ``` 535 | 536 | #### PSP37Enumerable 537 | 538 | `PSP37Enumerable` is an **optional** extension for this Multi Token standard to support enumeration of tokens. 539 | 540 | 541 | ##### **owners_token_by_index**(owner: AccountId, index: u128) ➔ Option 542 | Selector: `0x4cc01ee0` - first 4 bytes of `blake2b_256("PSP37Enumerable::owners_token_by_index")` 543 | ```json 544 | { 545 | "args": [ 546 | { 547 | "label": "owner", 548 | "type": { 549 | "displayName": [ 550 | "AccountId" 551 | ], 552 | "type": "AccountId" 553 | } 554 | }, 555 | { 556 | "label": "index", 557 | "type": { 558 | "displayName": [ 559 | "u128" 560 | ], 561 | "type": "u128" 562 | } 563 | } 564 | ], 565 | "docs": [ 566 | " Returns a token `Id` owned by `owner` at a given `index` of its token list.", 567 | " Use along with `balance_of` to enumerate all of ``owner``'s tokens." 568 | ], 569 | "label": "PSP37Enumerable::owners_token_by_index", 570 | "mutates": false, 571 | "payable": false, 572 | "returnType": { 573 | "displayName": [ 574 | "Option" 575 | ], 576 | "type": "Option" 577 | }, 578 | "selector": "0x4cc01ee0" 579 | } 580 | ``` 581 | 582 | ##### **token_by_index**(index: u128) -> Option 583 | Selector: `0x127b5477` - first 4 bytes of `blake2b_256("PSP37Enumerable::token_by_index")` 584 | ```json 585 | { 586 | "args": [ 587 | { 588 | "label": "index", 589 | "type": { 590 | "displayName": [ 591 | "u128" 592 | ], 593 | "type": "u128" 594 | } 595 | } 596 | ], 597 | "docs": [ 598 | " Returns a token `Id` at a given `index` of all the tokens stored by the contract.", 599 | " Use along with `total_supply` to enumerate all tokens." 600 | ], 601 | "label": "PSP37Enumerable::token_by_index", 602 | "mutates": false, 603 | "payable": false, 604 | "returnType": { 605 | "displayName": [ 606 | "Option" 607 | ], 608 | "type": "Option" 609 | }, 610 | "selector": "0x127b5477" 611 | } 612 | ``` 613 | 614 | ### Events 615 | 616 | #### Transfer 617 | When a contract creates (mints) new tokens, `from` will be `None`. 618 | When a contract deletes (burns) tokens, `to` will be `None`. 619 | ```json 620 | { 621 | "args": [ 622 | { 623 | "docs": [], 624 | "indexed": true, 625 | "label": "from", 626 | "type": { 627 | "displayName": [ 628 | "Option" 629 | ], 630 | "type": "Option" 631 | } 632 | }, 633 | { 634 | "docs": [], 635 | "indexed": true, 636 | "label": "to", 637 | "type": { 638 | "displayName": [ 639 | "Option" 640 | ], 641 | "type": "Option" 642 | } 643 | }, 644 | { 645 | "docs": [], 646 | "indexed": true, 647 | "label": "id", 648 | "type": { 649 | "displayName": [ 650 | "Id" 651 | ], 652 | "type": "Id" 653 | } 654 | }, 655 | { 656 | "docs": [], 657 | "indexed": false, 658 | "label": "value", 659 | "type": { 660 | "displayName": [ 661 | "Balance" 662 | ], 663 | "type": "Balance" 664 | } 665 | } 666 | ], 667 | "docs": [ 668 | "Event emitted when a token transfer occurs." 669 | ], 670 | "label": "Transfer" 671 | } 672 | ``` 673 | 674 | #### TransferBatch 675 | When a contract creates (mints) new tokens, `from` will be `None`. 676 | When a contract deletes (burns) tokens, `to` will be `None`. 677 | ```json 678 | { 679 | "args": [ 680 | { 681 | "docs": [], 682 | "indexed": true, 683 | "label": "from", 684 | "type": { 685 | "displayName": [ 686 | "Option" 687 | ], 688 | "type": "Option" 689 | } 690 | }, 691 | { 692 | "docs": [], 693 | "indexed": true, 694 | "label": "to", 695 | "type": { 696 | "displayName": [ 697 | "Option" 698 | ], 699 | "type": "Option" 700 | } 701 | }, 702 | { 703 | "docs": [], 704 | "indexed": true, 705 | "label": "ids_amounts", 706 | "type": { 707 | "displayName": [ 708 | "[(Id, Balance)]" 709 | ], 710 | "type": "[(Id, Balance)]" 711 | } 712 | } 713 | ], 714 | "docs": [ 715 | "Event emitted when a batch token transfer occurs." 716 | ], 717 | "label": "Transfer" 718 | } 719 | ``` 720 | 721 | ### Approval 722 | ```json 723 | { 724 | "args": [ 725 | { 726 | "docs": [], 727 | "indexed": true, 728 | "label": "owner", 729 | "type": { 730 | "displayName": [ 731 | "AccountId" 732 | ], 733 | "type": "AccountId" 734 | } 735 | }, 736 | { 737 | "docs": [], 738 | "indexed": true, 739 | "label": "operator", 740 | "type": { 741 | "displayName": [ 742 | "AccountId" 743 | ], 744 | "type": "AccountId" 745 | } 746 | }, 747 | { 748 | "docs": [], 749 | "indexed": true, 750 | "label": "id", 751 | "type": { 752 | "displayName": [ 753 | "Option" 754 | ], 755 | "type": "Option" 756 | } 757 | }, 758 | { 759 | "docs": [], 760 | "indexed": false, 761 | "label": "value", 762 | "type": { 763 | "displayName": [ 764 | "Balance" 765 | ], 766 | "type": "Balance" 767 | } 768 | } 769 | ], 770 | "docs": [ 771 | "Event emitted when a token approve occurs." 772 | ], 773 | "label": "Approval" 774 | } 775 | ``` 776 | 777 | ### AttributeSet 778 | ```json 779 | { 780 | "args": [ 781 | { 782 | "docs": [], 783 | "indexed": false, 784 | "label": "id", 785 | "type": { 786 | "displayName": [ 787 | "Id" 788 | ], 789 | "type": "Id" 790 | } 791 | }, 792 | { 793 | "docs": [], 794 | "indexed": false, 795 | "label": "key", 796 | "type": { 797 | "displayName": [ 798 | "[u8]" 799 | ], 800 | "type": "[u8]" 801 | } 802 | }, 803 | { 804 | "docs": [], 805 | "indexed": false, 806 | "label": "data", 807 | "type": { 808 | "displayName": [ 809 | "[u8]" 810 | ], 811 | "type": "[u8]" 812 | } 813 | } 814 | ], 815 | "docs": [ 816 | "Event emitted when an attribute is set for a token.", 817 | ], 818 | "label": "AttributeSet" 819 | } 820 | ``` 821 | 822 | ### Types 823 | ```rust 824 | // Id is an Enum and its variant are types 825 | enum Id { 826 | U8(u8), 827 | U16(u16), 828 | U32(u32), 829 | U64(u64), 830 | U128(u128), 831 | Bytes(Vec), 832 | } 833 | 834 | // AccountId is a 32 bytes Array, like in Substrate-based blockchains. 835 | type AccountId = [u8; 32]; 836 | 837 | // `u128` must be enough to cover most of the use-cases of standard tokens. 838 | type Balance = u128; 839 | ``` 840 | 841 | #### Return types 842 | ```json 843 | { 844 | "types": { 845 | "1": { 846 | "def": { 847 | "variant": { 848 | "variants": [ 849 | { 850 | "fields": [ 851 | { 852 | "type": { 853 | "def": { 854 | "tuple": [] 855 | } 856 | } 857 | } 858 | ], 859 | "name": "Ok" 860 | }, 861 | { 862 | "fields": [ 863 | { 864 | "type": { 865 | "def": { 866 | "variant": { 867 | "variants": [ 868 | { 869 | "fields": [ 870 | { 871 | "type": "string" 872 | } 873 | ], 874 | "name": "Custom" 875 | }, 876 | { 877 | "name": "SelfApprove" 878 | }, 879 | { 880 | "name": "NotApproved" 881 | }, 882 | { 883 | "name": "TokenExists" 884 | }, 885 | { 886 | "name": "TokenNotExists" 887 | }, 888 | { 889 | "fields": [ 890 | { 891 | "type": "string" 892 | } 893 | ], 894 | "name": "SafeTransferCheckFailed" 895 | } 896 | ] 897 | } 898 | }, 899 | "path": [ 900 | "PSP37Error" 901 | ] 902 | } 903 | } 904 | ], 905 | "name": "Err" 906 | } 907 | ] 908 | } 909 | } 910 | } 911 | } 912 | } 913 | ``` 914 | 915 | ### Errors 916 | The suggested methods revert the transaction and return a [SCALE-encoded](https://github.com/paritytech/parity-scale-codec) `Result` type with one of the following `Error` enum variants: 917 | 918 | ```rust 919 | enum PSP37Error { 920 | /// Custom error type for cases if writer of traits added own restrictions 921 | Custom(String), 922 | /// Returned if owner approves self 923 | SelfApprove, 924 | /// Returned if the caller doesn't have allowance for transferring. 925 | NotApproved, 926 | /// Returned if the owner already own the token. 927 | TokenExists, 928 | /// Returned if the token doesn't exist 929 | TokenNotExists, 930 | /// Returned if safe transfer check fails 931 | SafeTransferCheckFailed(String), 932 | } 933 | ``` 934 | 935 | ## Copyright 936 | 937 | This PSP is placed in the [public domain](https://creativecommons.org/publicdomain/zero/1.0/). 938 | -------------------------------------------------------------------------------- /PSPs/psp-template.md: -------------------------------------------------------------------------------- 1 | # Title 2 | 3 | - **PSP Number:** [To be assigned (=number of the initial PR to the PSPs repo)] 4 | - **Authors:** [Name and Email or GitHub Username] 5 | - **Status:** [Draft, Call for Feedback, Accepted, Integrated] 6 | - **Created:** [yyyy-mm-dd] 7 | - **Reference Implementation** [Link to a first reference implementation] 8 | 9 | ## Summary 10 | 11 | A summary of the standard and the addressed issue. 12 | 13 | ## Motivation 14 | 15 | The motivation should describe what motivated the development of the standard as well as why 16 | particular decisions were made. 17 | 18 | ## Specification 19 | 20 | The specification should describe the feature as detailed as possible. The proposal should be 21 | complete, consistent, unambiguous, quantitative, and feasible. 22 | 23 | ## Tests 24 | 25 | If applicable, please include a list of potential test cases to validate an implementation. 26 | 27 | ## Copyright 28 | 29 | Each PSP must be labeled as placed in the 30 | [public domain](https://creativecommons.org/publicdomain/zero/1.0/). 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Polkadot Smart Contract Proposals (PSPs) 2 | 3 | 4 | A Polkadot Smart Contract Proposal (PSP) describes standards for **smart contracts in the Polkadot ecosystem**. The Polkadot Smart Contract Proposal GitHub is a community-based initiative. The PSP process is not supposed to be a substitute for the Polkadot Governance process or the [RFCs process](https://github.com/polkadot-fellows/RFCs). 5 | 6 | > __Disclaimer__: The Polkadot Standards Proposals have been renamed to Polkadot Smart Contract Proposals (PSPs). Everything else should be discussed as part of the [RFCs process](https://github.com/polkadot-fellows/RFCs). 7 | 8 | --- 9 | - [:clipboard: Process](#clipboard-process) 10 | - [:pencil: Contributing](#pencil-contributing) 11 | - [:bulb: Help](#bulb-help) 12 | 13 | ## :clipboard: Process 14 | 15 | Below is the workflow of a successful PSPs: 16 | ``` 17 | 1. Draft -> 2. Call for Feedback -> 3. Published -> 4. Integrated 18 | ``` 19 | 1. **Draft:** A valid draft, which is merged into the [draft 20 | subfolder](./PSPs/drafts) and actively improved together with the community. 21 | 2. **Call for Feedback:** The PSP will be shared on different public channels for 22 | additional feedback for at least two weeks. The result of this step is either 23 | an acceptance of the standard (->Published) or a rejection (->Draft). 24 | 3. **Published:** Any further changes are unlikely, and developers can start 25 | integrating the PSP. 26 | 4. **Integrated:** The PSP is actively used, and a reference implementation 27 | exists. 28 | 29 | In order to be **merged or accepted** for the different stages, reviewers need to approve a PR. Reviewers should be known experts in the topic covered by the PSP. 30 | 31 | ## :pencil: Contributing 32 | 33 | Before you start writing a formal PSP, you should discuss an idea in the various community public channels (see the [Polkadot community website](https://polkadot.network/community/)). A PSP should provide the motivation as well as a technical specification for the feature. 34 | 35 | 1. Fork this repository. 36 | 2. In the newly created fork, create a copy of the template. 37 | 3. Fill out the [template](./PSPs/psp-template.md) with the details of your PSP. If your PSP requires images, the images should be integrated into a subdirectory of the src folder, which has your PSP number as the name. 38 | 4. Once you have completed the application, click on "create new pull request". 39 | 5. Rename the file with "psp-number_of_your_pr.md". 40 | 6. Update the pull request. 41 | 42 | ## :bulb: Help 43 | 44 | * [GitHub Discussions](https://github.com/w3f/PSPs/discussions) 45 | * [PSP Channel on Element](https://app.element.io/#/room/#psp:web3.foundation) 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "CC0-1.0", 3 | "devDependencies": { 4 | "prettier": "2.2.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/logo-polkadot.svg: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /src/psp-7/dss_sequence_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3f/PSPs/b6d570173146e7a012cf43d270177e02ed886e2e/src/psp-7/dss_sequence_diagram.png -------------------------------------------------------------------------------- /src/psp-7/sequence_wallet_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3f/PSPs/b6d570173146e7a012cf43d270177e02ed886e2e/src/psp-7/sequence_wallet_diagram.png -------------------------------------------------------------------------------- /src/psp-sign-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3f/PSPs/b6d570173146e7a012cf43d270177e02ed886e2e/src/psp-sign-in.png --------------------------------------------------------------------------------