Skip to content
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

feat: adding soulbound token #393

Merged
merged 93 commits into from
Sep 12, 2023
Merged
Changes from 9 commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
365e8c2
feat: adding soulbound token
robert-zaremba Sep 14, 2022
7aa2f8b
set NEP number
robert-zaremba Nov 28, 2022
1783a3e
Renamed queries *by_owner to *for_owner
robert-zaremba Nov 28, 2022
515f35d
use proper doc comment syntax
robert-zaremba Nov 28, 2022
fb488a8
add copyright
robert-zaremba Nov 28, 2022
56397d0
adding optional sbt_token_for_owner
robert-zaremba Nov 28, 2022
41d6020
typos
robert-zaremba Nov 28, 2022
6c261e1
added consequences
robert-zaremba Nov 29, 2022
cb96e8d
fix: nep 181 -> 171
robert-zaremba Nov 29, 2022
c5c64c5
small note about events
robert-zaremba Dec 14, 2022
f29bfa2
review: update summary
robert-zaremba Dec 20, 2022
764c68e
review, cleanup and more details
robert-zaremba Mar 23, 2023
0f26b31
Merge branch 'master' into robert/nep-sbt
robert-zaremba Mar 25, 2023
89aa83a
adding token kind
robert-zaremba Mar 25, 2023
13b152c
update documentation about soul_transfer, recover and revoke
robert-zaremba Mar 25, 2023
afab9e8
sbt registry
robert-zaremba Mar 25, 2023
e0d92fb
adding a mint flow
robert-zaremba Mar 25, 2023
664573f
adding mint event (non nep-171) + few cosmetic updates
robert-zaremba Mar 26, 2023
e1340c8
Recovery within a SBT Registry
robert-zaremba Mar 26, 2023
e8ade97
update nep-245 note
robert-zaremba Mar 27, 2023
a4a6e5c
fix flow
robert-zaremba Mar 27, 2023
99e9f99
registry mint authorization and update query functions
robert-zaremba Mar 27, 2023
1e9a6b3
change sbt_mint to support minting many tokens at once
robert-zaremba Mar 27, 2023
31d3ea8
fix tags
robert-zaremba Mar 27, 2023
08b0e2a
use token instead of token_id, update diagram
robert-zaremba Mar 27, 2023
9f1266f
review
robert-zaremba Mar 28, 2023
a8f4f49
add more info about Kill event and updated Considerations
robert-zaremba Mar 28, 2023
8acb192
add Burn event
robert-zaremba Mar 28, 2023
893f7a6
typos
robert-zaremba Mar 30, 2023
2d65c82
typos
robert-zaremba Mar 31, 2023
389a4bc
rename event: Kill -> Ban
robert-zaremba Mar 31, 2023
ad558dc
make kind optional in sbt_supply_by_owner query
robert-zaremba Mar 31, 2023
dbc20d8
review
robert-zaremba Apr 2, 2023
9a28f96
rename KindId to ClassId
robert-zaremba Apr 2, 2023
00649b0
formatting
robert-zaremba Apr 2, 2023
2213814
add missing memo type and update events
robert-zaremba Apr 2, 2023
2ea6fef
remove memo from registry functions and events
robert-zaremba Apr 2, 2023
3d809fa
adding is_banned to registry trait
robert-zaremba Apr 3, 2023
6c513a6
update registry motivation
robert-zaremba Apr 3, 2023
4e300e6
move to nep-0393.md
robert-zaremba Apr 3, 2023
e8ea5ec
fix snippet
robert-zaremba Apr 3, 2023
643cb89
fix typescript event type definitions
robert-zaremba Apr 3, 2023
21491ae
Merge branch 'master' into robert/nep-sbt
robert-zaremba Apr 3, 2023
08bd3ac
update comments
robert-zaremba Apr 5, 2023
c18f2b4
considerations cleanup
robert-zaremba Apr 6, 2023
99943a3
review
robert-zaremba Apr 6, 2023
549546a
update return types
robert-zaremba Apr 12, 2023
4c4231d
update diagram
robert-zaremba Apr 12, 2023
d2cbbdf
review, adding notes about possible burning mechanism
robert-zaremba Apr 13, 2023
1762ba2
adding note about PII
robert-zaremba Apr 13, 2023
3a8ceff
remove sbt_soul_transfer from required interface, and document requir…
robert-zaremba Apr 13, 2023
786d4a1
remove table
robert-zaremba Apr 24, 2023
dba3b4e
interface documentation update and review updates
robert-zaremba Apr 24, 2023
ee2317b
updates
robert-zaremba May 9, 2023
54e9a9d
remove return value from sbt_revoke
robert-zaremba May 9, 2023
07021ea
privacy note, and few typos/wording bugs
robert-zaremba May 9, 2023
34fc893
review updates
robert-zaremba Jun 8, 2023
876d9d3
udpated motiviation about registry
robert-zaremba Jun 8, 2023
808e047
add sbt_revoke_by_owner method
robert-zaremba Jun 8, 2023
f76f85d
update doc string
robert-zaremba Jun 12, 2023
45b73c9
One more comment to state that class_id > 0
robert-zaremba Jun 19, 2023
670cf5d
adding more diagrams
robert-zaremba Jun 22, 2023
dd7ebc2
fix
robert-zaremba Jun 22, 2023
8a7c584
add headers
robert-zaremba Jun 22, 2023
066d53b
todos
robert-zaremba Jun 22, 2023
ba4a5db
review
robert-zaremba Jun 22, 2023
8b91fb7
typo
robert-zaremba Jun 22, 2023
098d89c
update diagram
robert-zaremba Jun 22, 2023
9104c0e
Update neps/nep-0393.md
robert-zaremba Jun 29, 2023
b7c4d44
remove 'poorly'
robert-zaremba Jun 29, 2023
7850125
improving the flows and spec
robert-zaremba Jun 30, 2023
64afc80
Merge remote-tracking branch 'near/master' into robert/nep-sbt
robert-zaremba Jun 30, 2023
0ec0dca
more notes about token ID and the available space
robert-zaremba Jun 30, 2023
c57a4d2
language
robert-zaremba Jun 30, 2023
eece830
gramma
robert-zaremba Jun 30, 2023
b816793
gramma
robert-zaremba Jun 30, 2023
57b4ac6
small updates
robert-zaremba Jul 11, 2023
3a410f9
review
robert-zaremba Jul 11, 2023
1f5001e
language
robert-zaremba Jul 11, 2023
673beb6
sbt_mint example update
robert-zaremba Jul 11, 2023
3fc3447
function comments
robert-zaremba Jul 11, 2023
1149e98
bold must
robert-zaremba Jul 12, 2023
66c794f
add sbts method
robert-zaremba Jul 24, 2023
fe352b8
query: sbt_classes and docs
robert-zaremba Jul 26, 2023
7e784a2
more docs about sbt_renew
robert-zaremba Sep 6, 2023
06279c5
Merge branch 'master' into robert/nep-sbt
robert-zaremba Sep 6, 2023
51b14d9
update metadata struct comments
robert-zaremba Sep 6, 2023
ad56e5f
add sbt_update_token_references method and event to the standard
robert-zaremba Sep 6, 2023
3ea249b
fix: events: should use issuer rather than ctr as per reference imple…
robert-zaremba Sep 6, 2023
9f4c86e
language
robert-zaremba Sep 7, 2023
7b753f1
add soul_transfer implementation notes
robert-zaremba Sep 8, 2023
912803f
add benefits
robert-zaremba Sep 11, 2023
ceabc86
Added the decision statement and link to the video recording to the N…
frol Sep 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 232 additions & 0 deletions specs/Standards/Sould Bound Token/README.md
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.
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved

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>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may consider nft prefix where relevant to make the functions compatible with existing tools. eg : nft_tokens_for_owner instead of sbt_tokens_for_owner.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Collision with the existing tooling will create more harm than help here as we already discussed in #359. The only way I can see that being resolved is by the NEP which will help to identify the supported extensions, read more in #129, #154, and #275.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I was trying to refer to #351, which could be then used as a requirement for SBT NEP to unlock nft_ prefixes by saying that SBT = NEP-171 + NEP-330 + NEP-XXX (that just defines that nft_transfer is not available) - this way the tooling can reasonably handle the deviation from NEP-171 through introspection.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was bringing up interface registration already in 2020, then created #154 ;)

Copy link
Contributor Author

@robert-zaremba robert-zaremba Dec 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we reuse nft_ prefix then SBT is a subset of NFT, not an extension. Adding #330 makes sense. We need to discuss more if we want to be compatible with NFT standard, and carry over some of the shortcoming (like representing the token id as a U64 rather than u64 -- more about this in Considerations section)

robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved

/// 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.
Show resolved Hide resolved

/// Query sbt tokens by owner
robert-zaremba marked this conversation as resolved.
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.
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.
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you know that all implementations of this will require 5 milliNEAR to cover storage?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, I will rephrase it.

/// 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).
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are sure that seconds is a good idea here? The NFT metadata standard mandates milliseconds and you can this here in lines 51-54. I propose to keep it consistent. I also suggest we use either milliseconds or nanoseconds, as these are provided as functions from near-sdk-rs. With NFTs, most of the old contracts just use nanoseconds instead of the standardized milliseconds, same will probably happen here. Esp. in case of nanoseconds U64 or U128 data type might be more appropriate.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Milliseconds most probably were inherited from the JS environment, and nanoseconds are used in NEAR Protocol for timestamps as seconds precision was not enough and then given that u32 was not enough to hold even seconds precision for long enough, u64 was enough even to have nanoseconds precision.

Unfortunatelly, NFT NEP and near-contract-standards implementation are currently in disagreement:

expires_at: Option<String>, // ISO 8601 datetime when token expires

https://github.com/near/near-sdk-rs/blob/89f8c2d776c8d00feb45239fdb15725527f1894b/near-contract-standards/src/non_fungible_token/metadata.rs#L36

expires_at: number|null, // When token expires, Unix epoch in milliseconds

https://github.com/near/NEPs/blob/550e428cfffafaa28bd84f2e882504a8679d01b6/neps/nep-0177.md

There is clearly a need for a decision regarding time references.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer seconds, as the most natural, and good enough metric (unix timestamp measure, SI unit).
Personally, I think use of nano-seconds in NEAR is over-engineering.
However, if the ecosystem settled on milliseconds, then we can use milliseconds.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just 2 cents:

  • seconds precision is not enough given that NEAR can produce more than 1 block a second (see testnet)
  • to store milliseconds precision, we already need u64, and given that we now have enough bits, we may as well go with nanoseconds precision - this is why NEAR went with nanoseconds

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NEAR VM provides block_timestamp in nanoseconds, so it is usually a no-brainer to use that value to compare it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nanoseconds are not user friendly. I don't believe anyone things in terms of nanoseconds when defining expires_at. Most of the people will think in terms of days or hours for things related to human. Seconds are a good Unix standard. Milliseconds are default in JS.
Even if there are 100 blocks per second - the condition will be well defined. it's event the last transaction from the given block if using <= or the last transaction from the previous block if using < .
I don't believe anyone will like to go to nanosecond precision for things like SBT.expires_at. I other words, why anyone would put expires at: April 7 2023 11:23:56.751209857?
Also, worth to note that the block unit is a sequence number (1,2,3 ...).

With that, I think second (as more human friendly) or millisecond (as JS default) are definitely more appropriate. My preference is seconds. However, if there is broader adoption for milliseconds then let's use milliseconds.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion on seconds/milliseconds/nanosecond for the expiration timestamp. There are pros and cons to each of them. Just make sure you align the whole document on using one selected option as currently TokenMetadata states that it is milliseconds precision.

#[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.
Show resolved Hide resolved
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reasoning to call this revoke instead of burn?

Also, the SbtRevokeLog does not show up in SbtEventLogData and there is no sbt_revoke event name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed SbtRevoke and updated to use NEP-171 NftBurn instead and cleaned the interface. So we will have few "native" events, and 2 inherited from NEP-171 NftBurn. Note that we still need to discuss & agree the level of compatibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the new SBT Registry update, now we don't use NEP-171 events any more

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.
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.
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.
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.
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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As others have pointed out, mixing these standards and making interfaces compatible is well-intentioned but doomed to cause problems. There are so many examples where NFT standards are poorly or improperly implemented, adding another standard with differing functionality but equal naming in there will cause lots of misclassifications between NFTs/SBTs, and then recover methods called on NFT contracts, SBTs attempted to be listed on NFT marketplaces and so on. Probably nothing will happen because of smart contract panics, but devs will have to put defensive measures into place that will impair interoperability efforts. Users will be annoyed and blame devs all over the place. Everyone will be unhappy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good comment. On one hand side NEP-171 is desirable (support from existing tooling), on the other, it has some short commings. I've updated teh sentence and added more about it to the Considerations section.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@robert-zaremba Existing tooling will inevitably need to have dedicated support for SBTs, and the role of the standards is to make the support hassle-free. Thus, as we already covered before, it is better to avoid any overlap with the NFT ecosystem to make everybody's lives easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I still think that some developers may find it desirable to implement NFT query API on the issuer level. It is possible, so I would keep it mentioned. I will reword the sentence to make it more clear.


### 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).
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reasoning as above. New events. Updating indexers is not as bad as tokens being misclassified.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. But it's negative from the indexer perspective: new support will have to be added.


## Copyright

[Creative Commons Attribution 4.0 International Public License (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/)