-
Notifications
You must be signed in to change notification settings - Fork 161
feat: adding soulbound token #393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
365e8c2
7aa2f8b
1783a3e
515f35d
fb488a8
56397d0
41d6020
6c261e1
cb96e8d
c5c64c5
f29bfa2
764c68e
0f26b31
89aa83a
13b152c
afab9e8
e0d92fb
664573f
e1340c8
e8ade97
a4a6e5c
99e9f99
1e9a6b3
31d3ea8
08b0e2a
9f1266f
a8f4f49
8acb192
893f7a6
2d65c82
389a4bc
ad558dc
dbc20d8
9a28f96
00649b0
2213814
2ea6fef
3d809fa
6c513a6
4e300e6
e8ea5ec
643cb89
21491ae
08bd3ac
c18f2b4
99943a3
549546a
4c4231d
d2cbbdf
1762ba2
3a8ceff
786d4a1
dba3b4e
ee2317b
54e9a9d
07021ea
34fc893
876d9d3
808e047
f76f85d
45b73c9
670cf5d
dd7ebc2
8a7c584
066d53b
ba4a5db
8b91fb7
098d89c
9104c0e
b7c4d44
7850125
64afc80
0ec0dca
c57a4d2
eece830
b816793
57b4ac6
3a410f9
1f5001e
673beb6
3fc3447
1149e98
66c794f
fe352b8
7e784a2
06279c5
51b14d9
ad56e5f
3ea249b
9f4c86e
7b753f1
912803f
ceabc86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,232 @@ | ||
| # NEP: Soulbound Token | ||
|
|
||
| --- | ||
|
|
||
| NEP: 393 | ||
| Title: Soulbound Token | ||
| Authors: Robert Zaremba <@robert-zaremba>, Noak Lindqvist <@KazanderDad> | ||
| DiscussionsTo: | ||
| Status: Draft | ||
| Type: Standards Track | ||
| Category: Contract | ||
| Created: 12-Sep-2022 | ||
| Requires: -- | ||
|
|
||
| --- | ||
|
|
||
| ## Summary | ||
|
|
||
| Soulbound Tokens (SBT) are non transferable NFTs. Even though tranferability is not available, we define a recoverability mechanism. | ||
|
|
||
| SBTs are well suited of carrying proof-of-attendance NFTs, proof-of-unique-human "stamps" and other similar credibility-carriers. | ||
|
|
||
| ## Motivation | ||
|
|
||
| Recent Decentralized Society trend opens a new area of Web3 research to model various aspects of what characterizes humans. Economic and governance value is generated by humans and their relationship. Creating a strong primitive is necessary to model new innovative systems and decentralized societies. Examples include one-person-one-vote, fair airdrops & ICOs, universal basic income, non KYC identity systems, Human DAOs as well as methods for Sybil attack resistance. | ||
|
|
||
| We propose an SBT standard to model protocols described above. | ||
|
|
||
| ## Specification | ||
|
|
||
| Soulbound tokens need to be recoverable in case a user's private key is compromised (due to extortion, loss, etc). This becomes especially important for proof-of-human stamps and other NFTs that can only be issued once per user. | ||
|
|
||
| Two safeguards against misuse of recovery are contemplated. 1) Users cannot recover an SBT by themselves. The issuer, a DAO or a smart contract (eg: multisig) dedicated to manage the recovery should be assigned. 2) Whenever a recovery is triggered then the wallet from which the NFT was recovered gets blacklisted (indicator that the account identity is burned). Recovering one SBT triggers a blacklist that should apply for SBTs that share the same address. | ||
|
|
||
| Soulbound tokens with expire date SHOULD have an option to be renewable. Examples include mandatory renewal with some frequency to check that the owner is still alive, or renew membership to a DAO that uses SBTs as membership gating. | ||
|
|
||
| ### Smart contract interface | ||
|
|
||
| The Soulbound Token interface is a subset of NFT, hence the interface follows the [NEP-171](https://github.com/near/NEPs/blob/master/neps/nep-0171.md). | ||
|
|
||
| ```rust | ||
| /// TokenMetadata defines attributes for each SBT token. | ||
| pub struct TokenMetadata { | ||
| pub title: Option<String>, // ex. "fist bump with Robert" | ||
| pub description: Option<String>, // free-form description | ||
| pub media: Option<String>, // URL to associated media, preferably to decentralized, content-addressed storage | ||
| pub media_hash: Option<Base64VecU8>, // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included. | ||
| pub copies: Option<u64>, // number of copies of this set of metadata in existence when token was minted. | ||
| pub issued_at: Option<u64>, // When token was issued or minted, Unix epoch in milliseconds | ||
| pub expires_at: Option<u64>, // When token expires, Unix epoch in milliseconds | ||
| pub starts_at: Option<u64>, // When token starts being valid, Unix epoch in milliseconds | ||
| pub updated_at: Option<u64>, // When token was last updated, Unix epoch in milliseconds | ||
| pub extra: Option<String>, // anything extra the SBT wants to store on-chain. Can be stringified JSON. | ||
| pub reference: Option<String>, // URL to an off-chain JSON file with more info. | ||
| pub reference_hash: Option<Base64VecU8>, // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. | ||
| } | ||
|
|
||
|
|
||
| trait SBT { | ||
| /********** | ||
| * QUERIES | ||
| **********/ | ||
|
|
||
| /// get the information about specific token ID | ||
| fn sbt(&self, token_id: TokenId) -> Option<Token>; | ||
|
||
|
|
||
| /// returns total amount of tokens minted by this contract | ||
| fn sbt_total_supply(&self) -> U64; | ||
|
|
||
| /// returns total supply of SBTs for a given owner | ||
| fn sbt_supply_for_owner(&self, account: AccountId); | ||
|
|
||
| /// Query for sbt tokens | ||
| fn sbt_tokens(&self, from_index: Option<U64>, limit: Option<u32>) -> Vec<Token>; | ||
robert-zaremba marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// Query sbt tokens by owner | ||
robert-zaremba marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| fn sbt_tokens_for_owner( | ||
| &self, | ||
| account: AccountId, | ||
| from_index: Option<U64>, | ||
| limit: Option<u32>, | ||
| ) -> Vec<Token>; | ||
robert-zaremba marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// Optional. If the SBT implementaiton assures that one account can have maximum one SBT | ||
| /// the the following function should be implemented. | ||
| /// Returns Some(Token) if an `account` owns an SBT, otherwise returns None. | ||
| fn sbt_token_for_owner(&self, account: AccountId) -> Option<Token> {} | ||
|
|
||
| /********** | ||
| * TRANSACTIONS | ||
| **********/ | ||
|
|
||
| /// creates a new, unique token and assigns it to the `receiver`. | ||
| #[payable] | ||
| fn sbt_mint(&mut self, metadata: TokenMetadata, receiver: AccountId); | ||
robert-zaremba marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// sbt_recover reassigns all tokens from the old owner to a new owner, | ||
| /// and registers `old_owner` to a burned addresses registry. | ||
| /// Must be called by operator. | ||
| /// Must provide 5 miliNEAR to cover registry storage cost. Operator should | ||
|
||
| /// put that cost to the requester (old_owner), eg by asking operation fee. | ||
| #[payable] | ||
| fn sbt_recover(&mut self, from: AccountId, to: AccountId); | ||
|
|
||
| /// sbt_renew will update the expire time of provided tokens. | ||
| /// `expires_at` is a unix timestamp (in seconds). | ||
|
||
| #[payable] | ||
| pub fn sbt_renew(&mut self, tokens: Vec<TokenId>, expires_at: u64, memo: Option<String>); | ||
| } | ||
| ``` | ||
|
|
||
| ### Logs | ||
|
|
||
| ```typescript | ||
| interface SbtEventLogData { | ||
| standard: "nepXXX"; | ||
| version: "1.0.0"; | ||
| event: "sbt_mint" | "sbt_recover" | "sbt_renew"; | ||
| data: SbtMintLog[] | SbtRecoverLog[] | SbtRenew[]; | ||
| } | ||
|
|
||
| // An event emitted when a new SBT is minted. | ||
| // Arguments | ||
| // * `owner`: "account.near" | ||
| // * `tokens`: ["1", "abc"] | ||
| // * `memo`: optional message | ||
| interface SbtMintLog { | ||
| owner: string; | ||
| tokens: string[]; | ||
| memo?: string; | ||
| } | ||
|
|
||
| // An event emitted when existing SBTs are revoked and burned. | ||
| // Arguments | ||
| // * `owner`: "account.near" | ||
| // * `tokens`: ["1", "abc"] | ||
| // * `memo`: optional message | ||
| interface SbtRevokeLog { | ||
robert-zaremba marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| owner: string; | ||
| tokens: string[]; | ||
| memo?: string; | ||
| } | ||
|
|
||
| // An event emitted when a recovery process succeeded to reassign SBT. | ||
| // Arguments | ||
| // * `old_owner`: "old_account.near" | ||
| // * `new_owner`: "new_account.near" | ||
| // * `token_ids`: ["1", "abc"] | ||
| // * `memo`: optional message | ||
| interface SbtRecoverLog { | ||
| old_owner: string; | ||
| new_owner: string; | ||
| tokens: string[]; | ||
| memo?: string; | ||
| } | ||
|
|
||
| // An event emitted when a existing tokens are renewed. | ||
| // Arguments | ||
| // * `tokens`: ["1", "abc"] | ||
| // * `memo`: optional message | ||
| interface SbtRenewLog { | ||
| tokens: uint64[]; | ||
| memo?: string; | ||
| } | ||
| ``` | ||
|
|
||
| Whenever a recovery is made in a way that an existing SBT is burned, the `SbtRevokeLog` event must be emitted. | ||
|
|
||
| ```typescript | ||
robert-zaremba marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| class SbtBurnedAccounts { | ||
| burn(account_id) { | ||
| this.burned_accounts[this.account_id][this.predecessor_id] = true; | ||
| } | ||
|
|
||
| // query | ||
| is_burned(account_id, sbt_token_contract): bool { | ||
| return this.burned_accounts[account_id][sbt_token_id]; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Recommended functions | ||
|
|
||
| Although the functions below are not part of the standard (depending on a use case, they may need different parameters), we recommend them as a part of every implementation and we also provide them in the reference implementation. | ||
|
|
||
| ```typescript= | ||
|
|
||
| interface SBT { | ||
robert-zaremba marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// Function for recovery committee, which can be either | ||
| /// the issuer, DAO, or an operator smart contract, authorized for the | ||
| /// recovery process. | ||
| /// Emits `SbtRecoverLog` when the recover process succeeds. | ||
| /// Returns an error if the recovery process failed. | ||
| recover(token_id: uint64, old_address: string, new_address: string): error | undefined; | ||
|
|
||
| /// Function to revoke and burn SBT. Must emit `SbtRevokeLog` event. | ||
| /// Return true if a token_id is an valid, active SBT. Otherwise returns false. | ||
| revoke(token_id: uint64): bool; | ||
| } | ||
| ``` | ||
|
|
||
| ## Reference Implementation | ||
|
|
||
| - https://github.com/alpha-fi/i-am-human/tree/master/contracts/soulbound | ||
|
|
||
| ## Example Flow | ||
robert-zaremba marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## Consequences | ||
|
|
||
| ### Positive | ||
|
|
||
| - Template and set of guidelines for creating SBT tokens. | ||
| - Ability to create SBT aggregators. | ||
| - Ability to use SBT as a primitive to model non KYC identity, badges, certificates etc... | ||
| - SBT can be further used for "lego" protocols, like: Proof of Humanity (dicussed for NDC Governance), undercollateralized lending, role based authentication sytems, innovative economic and social applications... | ||
| - Stanarized recoverability mechanism. | ||
robert-zaremba marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - SBT are considered as a basic primitive for Decentralized Societies. | ||
| - new way to implement Sybil attack resistance. | ||
|
|
||
| ### Netural | ||
|
|
||
| - API follows the NEP-171 (NFT) standard. | ||
| NOTE: we can decide to use `nft_` prefix whenever possible. | ||
|
||
|
|
||
| ### Negative | ||
|
|
||
| - new set of events to be handled by the indexer. However this is not an issue if we decide to use subset of events used by the NEP-171 (NFT). | ||
|
||
|
|
||
| ## Copyright | ||
|
|
||
| [Creative Commons Attribution 4.0 International Public License (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/) | ||
Uh oh!
There was an error while loading. Please reload this page.