From 430a49852b30aa0b488abd7c50f4230fbe2d69eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 16 Aug 2024 15:23:59 +0200 Subject: [PATCH 01/11] feat: update docs (wip) --- README.md | 99 +++++---- docs/README.md | 60 +++++ docs/badges.md | 206 +++++++++++++++--- ...ion-faq.md => canvas-interaction-guide.md} | 12 +- .../ethereum-year-badge.md | 0 .../scroll-origins-badge.md | 0 6 files changed, 303 insertions(+), 74 deletions(-) create mode 100644 docs/README.md rename docs/{integration-faq.md => canvas-interaction-guide.md} (95%) rename docs/{ => official-badges}/ethereum-year-badge.md (100%) rename docs/{ => official-badges}/scroll-origins-badge.md (100%) diff --git a/README.md b/README.md index 5e5190e..f4d2181 100644 --- a/README.md +++ b/README.md @@ -2,63 +2,78 @@ [![test](https://github.com/scroll-tech/canvas-contracts/actions/workflows/contracts.yml/badge.svg)](https://github.com/scroll-tech/canvas-contracts/actions/workflows/contracts.yml) -![Components overview](images/overview.png "Overview") +## Welcome to Scroll Canvas -([Editable link](https://viewer.diagrams.net/?tags=%7B%7D&highlight=0000ff&edit=_blank&layers=1&nav=1&title=skelly-v4.drawio#R7VpLc6M4EP41rpo5xIWEMeYYx8nsIdma2Rx2clRABs0K5BVybO%2BvXwkknk7ijCEwNalUyqjVenV%2F%2FVDDxL6K91842kR3LMB0Aq1gP7FXEwgBsIH8UZRDTllY85wQchJoppJwT%2F7Dmmhp6pYEOK0xCsaoIJs60WdJgn1RoyHO2a7Otma0vuoGhbhFuPcRbVP%2FJoGI9Ckcq6T%2FgUkYmZWBpXtiZJg1IY1QwHYVkn09sa84YyJ%2FivdXmCrhGbnk426e6S02xnEiThkQf%2Ft2uwvggibu7Q9%2Fk1hfCbzQykjFwRwYB%2FL8usm4iFjIEkSvS%2BqSs20SYDWrJVslzy1jG0kEkvgDC3HQykRbwSQpEjHVvXLD%2FPBdj88aD6oxdUxzta92rg66lQrO%2FinUIAW4XBNKrxhlPNu7vV742PcLzkrP48KZOWqOttS0IFO25T5%2BQVQGfYiHWLzAZ%2Bd8So6VBbROvmAWY3kmycAxRYI81XGGNFzDgq%2FUqHzQSn2DgsEsn%2FgJ0a1eagLnVB5gGZCnmurn%2F24VFpd%2BLrZLtcfw8ZPjTaBc2yp%2FP2dylLaUiIs1igk95Nx3OKEsZ7qTgPD1s5wZxRIZSz3%2FlZQ0wVz2%2FIl3zc58SMwSlm6Q0kexUprBSa0DrM0%2B76AkwReRtr%2Bsy9Fd82K%2Beah%2BkRA4FZ8%2Bm8NLYWbnz3uPWsEtepTOrIZcREmYyGdfokeewF4%2BYS6I9BaXuiMmQZAbCZb7RY%2FZfAp3G0YSkSnXWU6clcKuPJQxNKsNy5fMVa2K95Mj%2Fk%2BvWHMxNdTpURfW1Fo4bj72UJvpZFzqyb%2Bqk5WzXHj1EWy9TqW9NHFcbOkMaLeQfX1539LkLiIC3%2BdYWu1kkGpoNN3kYWNN9sqn1dRyxMeCZzTwrKTthVWTsu3q9q4STjQpqkQSQ%2BvcI8CW2O79CMfIWMYjN1bxFw5JapY8R6Ytd7zM%2FrS0j9F7EDx0Bha8O2isLRqnxVq8J%2BJ7ySlbD5WecpBq1OPzm9T5atS1T4y6cFRR12uperCkqR5mOrXFzpQ3G5XyzNVjDNobNuU9VX%2FOqPRnHwlwnFG6RGp%2FR6JcyuiTykdHljk4zsgyh%2FZVoibYDsXXsaM6XeZuU%2BZwYJk77ybzLiDbFN9saPEB%2BJv48ld9NOjaRzeufxoB0G0gADRUmwcdPaqHS2Hb%2B3%2FUO37Zeocx304KHrAGTN06E%2B5FsmiqH4v6DD1WPxYfQFe%2F1pRnZQrMxwT202Ppm5Hs2I26Rn%2BVonbForfgCU8tSIBqOaIoTrxfQQLOhwq4512KwFEz6L36FFyql3CymbAE55QborauFYS4MBw%2BRWlKfEPWbKBnPfadEIFZw1y9hrnmQGolRFIq6FBh0z7mzevc%2FBy%2FfMh30GnQMkKvBK3LLFFRUaN5Mf%2FK2f782vMorpWwKeyha9HwI3vIMEbSdIuzu%2FSYsodXUuXOUom5Z00tS6JzMfechV2%2FuM1U38yZyX%2FHdW3YV2CyPpBYXtiWh7YfXGGKQxmCWDImjPYIS5DBEngAQMe1JDgXdec596ae58zmnu1CD3p9AXPgb2PKetJDte%2B59PiVTCtAaZTtC5yVNRk38Ktlv27LyUh7Q340Dst5EX%2Fn25M1BZb51uNns9n%2BCxoGWhUdyQxwTej4ytzNTwtm3sDpnH3sK68hndfkTZXxM%2FzRqa8oDbpedUhlvdC27XpKZGp9I7ah9usin2Mk2iY0Ij9nsNtFjRdA68zCrinkNt9b9Ki09iuLwvH19klW1y4RgndzibJZfjWdK6H89ty%2B%2Fh8%3D)) +We are thrilled to have you join us in building unique discoveries with Scroll Canvas, a new product designed for ecosystem projects to interact with users in a more tailored way. -## ScrollBadge Schema and Resolver +## Overview -We define a *Scroll badge* [EAS schema](https://docs.attest.sh/docs/core--concepts/schemas): +**Scroll Canvas** allows users to showcase on-chain credentials, status, and achievements called **Badges** issued and collected across the Scroll ecosystem. +Users can mint a non-transferable and unique personal persona to collect and display their **Badges**. +### Key Features + +- **Canvas**: Each Canvas is a smart contract minted through the `ProfileRegistry` contract by the user on Scroll’s website. +- **Badges**: Attestations of achievements and traits verified through the Ethereum Attestation Service ([EAS service](https://docs.attest.sh/docs/welcome)), issued by different projects and the Scroll Foundation. + Badges are wallet-bound and non-transferable. + +| Attestation | NFT | +| --- | --- | +| Witness Proofs | Tokenized Assets | +| Non-transferable | Transferable | +| Recorded on disk (blockchain history) | Recorded in memory (blockchain states) | +| Prove ownership at a point in time | Exercise custodianship of an asset | + +## Developer Quickstart + +Visit the [Developer Documentation](./docs) in this repo to learn more about Canvas. + +See [Deployments](./docs/deployments.md) for the official Canvas contract addresses. + +## Running the Code + +### Node.js + +First install [`Node.js`](https://nodejs.org/en) and [`npm`](https://www.npmjs.com/). +Run the following command to install [`yarn`](https://classic.yarnpkg.com/en/): + +```bash +npm install --global yarn ``` -address badge -bytes payload -``` -This schema is tied to `ScrollBadgeResolver`. -Every time a Scroll badge attestation is created or revoked, `ScrollBadgeResolver` executes some checks. -After that, it forwards the call to the actual badge implementation. +### Foundry + +Install `foundryup`, the Foundry toolchain installer: + +```bash +curl -L https://foundry.paradigm.xyz | bash +``` -## Profiles +If you do not want to use the redirect, feel free to manually download the `foundryup` installation script from [here](https://raw.githubusercontent.com/foundry-rs/foundry/master/foundryup/foundryup). Then, run `foundryup` in a new terminal session or after reloading `PATH`. -Each user can create a `Profile` contract, minted through the `ProfileRegistry` contract. -Each wallet can mint only one profile. -All profiles share the same implementation, upgradable by Scroll to enable new features. +Other ways to install Foundry can be found [here](https://github.com/foundry-rs/foundry#installation). -The main use of profiles is personalization. -Users can configure a username and an avatar. -Users can also decide which badges they atach to their profile, and in which order. +### Install Dependencies -## Badges +Run the following command to install all dependencies locally. -Each badge is an EAS attestation that goes through the `ScrollBadgeResolver` contract and a badge contract. +``` +yarn +``` -Each badge type is a standalone contract, inheriting from `ScrollBadge`. -This badge contract can implement arbitrary logic attached to the attestation. -Badges implement a `badgeTokenURI` interface, similar to `ERC721.tokenURI`. +### Run Contract Tests -Badges are minted to the user's wallet address. -The user can express their personalization preferences (attach and order badges, choose a profile photo) through their `Profile`. +Run the following command to run the contract tests. -See [badges](./docs/badges.md) for details. +``` +yarn test +``` -### Extensions +## Contributing -This repo contains some useful [extensions](src/badge/extensions): -- `ScrollBadgeAccessControl` restricts who can create and revoke this badge. -- `ScrollBadgeCustomPayload` adds custom payload support to the badge. -- `ScrollBadgeDefaultURI` sets a default badge token URI. -- `ScrollBadgeEligibilityCheck` adds a standard on-chain eligibility check interface. -- `ScrollBadgeNoExpiry` disables expiration for the badge. -- `ScrollBadgeNonRevocable` disables revocation for the badge. -- `ScrollBadgeSBT` attaches an SBT token to each badge attestation. -- `ScrollBadgeSelfAttest` ensures that only the recipient of the badge can create the badge. -- `ScrollBadgeSingleton` ensures that each user can only have at most one of the badge. +We welcome community contributions to this repository. +For larger changes, please [open an issue](https://github.com/scroll-tech/canvas-contracts/issues/new/choose) and discuss with the team before submitting code changes. -### Examples +## License -This repo also contains some [examples](src/badge/examples): -- `ScrollBadgeSimple` is a simple badge with fixed metadata. -- `ScrollBadgePermissionless` is a permissionless badge that anyone can mint to themselves. -- `ScrollBadgeLevels` is an SBT badge that stores a level in its payload and renders different images based on this level. -- `ScrollBadgeTokenOwner` is a badge that is tied to the ownership of a Scroll Origins NFT. +Scroll Monorepo is licensed under the [MIT](./LICENSE) license. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..8586f26 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,60 @@ +# Scroll Canvas Developer Documentation + +![Components overview](images/overview.png "Overview") + +([Editable link](https://viewer.diagrams.net/?tags=%7B%7D&highlight=0000ff&edit=_blank&layers=1&nav=1&title=skelly-v4.drawio#R7VpLc6M4EP41rpo5xIWEMeYYx8nsIdma2Rx2clRABs0K5BVybO%2BvXwkknk7ijCEwNalUyqjVenV%2F%2FVDDxL6K91842kR3LMB0Aq1gP7FXEwgBsIH8UZRDTllY85wQchJoppJwT%2F7Dmmhp6pYEOK0xCsaoIJs60WdJgn1RoyHO2a7Otma0vuoGhbhFuPcRbVP%2FJoGI9Ckcq6T%2FgUkYmZWBpXtiZJg1IY1QwHYVkn09sa84YyJ%2FivdXmCrhGbnk426e6S02xnEiThkQf%2Ft2uwvggibu7Q9%2Fk1hfCbzQykjFwRwYB%2FL8usm4iFjIEkSvS%2BqSs20SYDWrJVslzy1jG0kEkvgDC3HQykRbwSQpEjHVvXLD%2FPBdj88aD6oxdUxzta92rg66lQrO%2FinUIAW4XBNKrxhlPNu7vV742PcLzkrP48KZOWqOttS0IFO25T5%2BQVQGfYiHWLzAZ%2Bd8So6VBbROvmAWY3kmycAxRYI81XGGNFzDgq%2FUqHzQSn2DgsEsn%2FgJ0a1eagLnVB5gGZCnmurn%2F24VFpd%2BLrZLtcfw8ZPjTaBc2yp%2FP2dylLaUiIs1igk95Nx3OKEsZ7qTgPD1s5wZxRIZSz3%2FlZQ0wVz2%2FIl3zc58SMwSlm6Q0kexUprBSa0DrM0%2B76AkwReRtr%2Bsy9Fd82K%2Beah%2BkRA4FZ8%2Bm8NLYWbnz3uPWsEtepTOrIZcREmYyGdfokeewF4%2BYS6I9BaXuiMmQZAbCZb7RY%2FZfAp3G0YSkSnXWU6clcKuPJQxNKsNy5fMVa2K95Mj%2Fk%2BvWHMxNdTpURfW1Fo4bj72UJvpZFzqyb%2Bqk5WzXHj1EWy9TqW9NHFcbOkMaLeQfX1539LkLiIC3%2BdYWu1kkGpoNN3kYWNN9sqn1dRyxMeCZzTwrKTthVWTsu3q9q4STjQpqkQSQ%2BvcI8CW2O79CMfIWMYjN1bxFw5JapY8R6Ytd7zM%2FrS0j9F7EDx0Bha8O2isLRqnxVq8J%2BJ7ySlbD5WecpBq1OPzm9T5atS1T4y6cFRR12uperCkqR5mOrXFzpQ3G5XyzNVjDNobNuU9VX%2FOqPRnHwlwnFG6RGp%2FR6JcyuiTykdHljk4zsgyh%2FZVoibYDsXXsaM6XeZuU%2BZwYJk77ybzLiDbFN9saPEB%2BJv48ld9NOjaRzeufxoB0G0gADRUmwcdPaqHS2Hb%2B3%2FUO37Zeocx304KHrAGTN06E%2B5FsmiqH4v6DD1WPxYfQFe%2F1pRnZQrMxwT202Ppm5Hs2I26Rn%2BVonbForfgCU8tSIBqOaIoTrxfQQLOhwq4512KwFEz6L36FFyql3CymbAE55QborauFYS4MBw%2BRWlKfEPWbKBnPfadEIFZw1y9hrnmQGolRFIq6FBh0z7mzevc%2FBy%2FfMh30GnQMkKvBK3LLFFRUaN5Mf%2FK2f782vMorpWwKeyha9HwI3vIMEbSdIuzu%2FSYsodXUuXOUom5Z00tS6JzMfechV2%2FuM1U38yZyX%2FHdW3YV2CyPpBYXtiWh7YfXGGKQxmCWDImjPYIS5DBEngAQMe1JDgXdec596ae58zmnu1CD3p9AXPgb2PKetJDte%2B59PiVTCtAaZTtC5yVNRk38Ktlv27LyUh7Q340Dst5EX%2Fn25M1BZb51uNns9n%2BCxoGWhUdyQxwTej4ytzNTwtm3sDpnH3sK68hndfkTZXxM%2FzRqa8oDbpedUhlvdC27XpKZGp9I7ah9usin2Mk2iY0Ij9nsNtFjRdA68zCrinkNt9b9Ki09iuLwvH19klW1y4RgndzibJZfjWdK6H89ty%2B%2Fh8%3D)) + + +# Overview + +Scroll Canvas consists of the following components: +- [**ProfileRegistry**](../src/profile/ProfileRegistry.sol): A contract for users to mint and query their Canvases. +- [**Profile**](../src/profile/Profile.sol): Each Canvas is an instance of the profile smart contract. +- [**EAS**](https://docs.attest.org/docs/welcome): A technology for issuing on-chain attestations. +- [**ScrollBadgeResolver**](../src/resolver/ScrollBadgeResolver.sol): Each attestation passes through this resolver before the badge is minted. It enforces Canvas badge rules. +- [**ScrollBadge**](../src/badge/ScrollBadge.sol): Each badge is a contract the conforms to a certain [interface](../src/interfaces/IScrollBadge.sol). + + +## Profiles + +Each user can mint a [`Profile`](../src/profile/Profile.sol) instance through [`ProfileRegistry`](../src/profile/ProfileRegistry.sol). +This contract is the user's Canvas, and minting it is a prerequisite to collecting badges. +Each wallet can only mint one profile. +All profiles share the same implementation, upgradable by Scroll to enable new features. + +The main use of profiles is personalization. +Users can configure a username and an avatar. +Users can also decide which badges they attach to their profile, and in which order they want to display them. + +See the [Canvas Interaction Guide](./canvas-interaction-guide.md) section for more details. + + +## ScrollBadge Schema and Resolver + +We define a *Scroll badge* [EAS schema](https://docs.attest.org/docs/core--concepts/schemas): + +``` +address badge +bytes payload +``` + +This schema is tied to `ScrollBadgeResolver`. +Every time a Scroll badge attestation is created or revoked through EAS, `ScrollBadgeResolver` executes some checks and actions. +After this, it forwards the call to the actual badge implementation. + +You can find the schema UID in the [Deployments](./deployments.md) section. +Browse the Scroll mainnet badge attestations on the [EAS Explorer](https://scroll.easscan.org/schema/view/0xd57de4f41c3d3cc855eadef68f98c0d4edd22d57161d96b7c06d2f4336cc3b49). + + +## Badges + +Each badge is an [EAS attestation](https://docs.attest.org/docs/core--concepts/attestations) that goes through the [`ScrollBadgeResolver`](../src/resolver/ScrollBadgeResolver.sol) contract and a badge contract. + +Each badge type is a standalone contract that inherits from [`ScrollBadge`](../src/badge/ScrollBadge.sol). +This badge contract can implement arbitrary logic attached to the attestation. +Badges implement a `badgeTokenURI` interface, similar to `ERC721.tokenURI`. + +Badges are minted to the user's wallet address. +The user can express their personalization preferences (attach and reorder badges, choose a profile photo) through their Canvas [`Profile`](../src/profile/Profile.sol). + +See the [Badges](./badges.md) section for more details. diff --git a/docs/badges.md b/docs/badges.md index a735057..9d6ec21 100644 --- a/docs/badges.md +++ b/docs/badges.md @@ -1,19 +1,17 @@ -# Canvas Badge FAQ +# Badges ### What is a badge? Each Canvas badge is an [EAS attestation](https://docs.attest.sh/docs/core--concepts/attestations), with some additional logic attached to it. -The badge attestation uses the official Scroll Canvas schema (see `SCROLL_SEPOLIA_BADGE_SCHEMA` in [deployments.md](./deployments.md)). -This means that the badge data includes two fields: `address badge, bytes payload`, and badges will go through the official Canvas badge resolver contract. +The badge attestation uses the official Scroll Canvas schema (see `BADGE_SCHEMA` in [Deployments](./deployments.md)). +This means that the badge data contains two fields: `address badge, bytes payload`, and badges are issued through the official Canvas badge [resolver contract](../src/resolver/ScrollBadgeResolver.sol). ### How to implement a new badge? -As a badge developer, you need to deploy a badge contract that inherits from [`ScrollBadge`](../src/badge/ScrollBadge.sol). -Additionally, you can use one of more [extensions](../src/badge/extensions). - -The badge must implement 3 APIs (see [`IScrollBadge`](../src/interfaces/IScrollBadge.sol)): +Each badge must implement a certain interface to ensure it is compatible with Canvas. +In particular, each badge must implement 3 APIs (see [`IScrollBadge`](../src/interfaces/IScrollBadge.sol)): - `issueBadge`: Implement arbitrary logic that is triggered when a new badge is created. - `revokeBadge`: Implement arbitrary logic that is triggered when a badge is revoked. - `badgeTokenURI`: Return the badge token URI. @@ -21,39 +19,197 @@ The badge must implement 3 APIs (see [`IScrollBadge`](../src/interfaces/IScrollB In most cases, the badge contract would use a static image, shared by all instances of this badge. However, on-chain-generated SVG data URLs are also possible. +As a badge developer, it is strongly recommended that your contract inherits from [`ScrollBadge`](../src/badge/ScrollBadge.sol). +Additionally, you can use one or more [extensions](../src/badge/extensions). Refer to the examples in [examples](../src/badge/examples). -> While this is not compulsory, we recommend creating badges that do no expire, are non-revocable, and are singletons (at most 1 badge per user). +> While this is not mandatory, we recommend creating badges that do no expire, are non-revocable, and are singletons (at most 1 badge per user). -### How to mint a badge? +### Badge Types -Badges are created by attesting to the recipient using the `SCROLL_SEPOLIA_BADGE_SCHEMA`. +Badges are created by attesting to the recipient using the [`BADGE_SCHEMA`](./deployments.md). EAS provides multiple interfaces to attest: `attest`, `attestByDelegation`, `multiAttest`, `multiAttestByDelegation`. See [`IEAS.sol`](https://github.com/ethereum-attestation-service/eas-contracts/blob/master/contracts/IEAS.sol). -Another useful example is [`AttesterProxy.sol`](../src/AttesterProxy.sol), which allows creating unordered delegated attestations. -There are 3 main badge minting flows: -1. **Fully permissionless**. - The user attests to themselves using `EAS.attest`. - The badge contract ensures that the issuer is authorized. +There are three main badge types of badges: + +1. **Permissionless**. + Permissionless badges allow users to attest to themselves using `EAS.attest`. + The badge contract ensures that the user is authorized to mint. See [`ScrollBadgePermissionless.sol`](../src/badge/examples/ScrollBadgePermissionless.sol) and [`ScrollBadgeTokenOwner.sol`](../src/badge/examples/ScrollBadgeTokenOwner.sol). 2. **Backend-authorized**. - A centralized backend implements some off-chain eligibility check. + For backend-authorized badges, the issuer maintains a centralized backend service. + This backend implements some off-chain eligibility check and exposes an eligibility check and claim API. If the user is authorized to mint, the backend issues a signed permit. - The user then mints by calling `AttesterProxy.attestByDelegation`. - Note: In this case, the badge issuer will be the address of `AttesterProxy`. + The user then mints using this permit. + + For backend-authorized badges, you need to deploy two contracts: the badge contract, and an [`AttesterProxy`](../src/AttesterProxy.sol). + `AttesterProxy` allows executing delegated attestations in arbitrary order. + The user can mint the badge by calling `AttesterProxy.attestByDelegation` and providing the signed permit. + +3. **Gifted**. + Badges can also be issued with no user interaction. + To do this, the issuer uses `EAS.attest` or `EAS.multiAttest` to airdrop these badges to a list of users. + + +### Overview of Requirements + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDescriptionBasic RequirementsAdditional RequirementsExamples
+ + `Permissionless` + + + + Badge checks eligibility based on smart contract. + + **Example: Badges attesting to completing an onchain transaction or holding an NFT are eligible to mint the badge.** + + +
    +
  • + + The badge is deployed on the **Scroll Mainnet** and verified on [ScrollScan](https://scrollscan.com). + +
  • +
  • + + The badge contract implements [defaultTokenURI](https://github.com/scroll-tech/canvas-contracts/blob/master/src/badge/extensions/ScrollBadgeDefaultURI.sol). + +
  • +
  • + + Your project is listed on [Scroll Ecosystem - Browse all protocols](https://scroll.io/ecosystem#protocols). + +
  • +
  • + + All URLs mentioned above are configured for cross-origin access on https://scroll.io. + +
  • +
+
+ N/A +
+ + `Backend-authorized` + + + + Badge checks eligibility based on the issuer’s API. + + **Example: Badges attesting to completing offchain actions or a certain allow list.** + + + +
    +
  • + + The **check API** and **claim API** have been deployed to **production**. + +
  • +
  • + + The attester proxy contract is deployed on the **Scroll Mainnet** and verified on [ScrollScan](https://scrollscan.com). + +
  • +
+
+ + `Gifted` + + + + Badge checks eligibility based on the issuer’s API and automatically sends to users' canvas. There is no minting required for users to display the badge. + + **Example: Badges attesting to ownership or paid membership on other platforms / chains.** + + + +
    +
  • + + The **check API** has been deployed to **production**. + +
  • +
+
+ + +### Extensions + +This repo contains some useful [extensions](src/badge/extensions): +- `ScrollBadgeAccessControl` restricts who can create and revoke this badge. +- `ScrollBadgeCustomPayload` adds custom payload support to the badge. +- `ScrollBadgeDefaultURI` sets a default badge token URI. +- `ScrollBadgeEligibilityCheck` adds a standard on-chain eligibility check interface. +- `ScrollBadgeNoExpiry` disables expiration for the badge. +- `ScrollBadgeNonRevocable` disables revocation for the badge. +- `ScrollBadgeSBT` attaches an SBT token to each badge attestation. +- `ScrollBadgeSelfAttest` ensures that only the recipient of the badge can create the badge. +- `ScrollBadgeSingleton` ensures that each user can only have at most one of the badge. + + +### Examples + +This repo also contains some [examples](src/badge/examples): +- `ScrollBadgeSimple` is a simple badge with fixed metadata. +- `ScrollBadgePermissionless` is a permissionless badge that anyone can mint to themselves. +- `ScrollBadgeLevels` is an SBT badge that stores a level in its payload and renders different images based on this level. +- `ScrollBadgeTokenOwner` is a badge that is tied to the ownership of a Scroll Origins NFT. + + +### Troubleshooting -3. **Airdropped**. - Badges can also be issues with no user interaction. - To do this, the issuer uses `EAS.attest` or `EAS.multiAttest`. +We recommend going through this checklist before your badge is published: +- [ ] The badge contract is deployed on Scroll mainnet and verified on Scrollscan. +- [ ] The badge contract configured the correct resolver address, see [Deployments](./deployments.md). -### How to ensure that the badge can be shown on scroll.io? +Backend-authorized badges: -Simply provide the deployed badge contract address to the Scroll team. +- [ ] The attester proxy contract is deployed on Scroll mainnet and verified on Scrollscan. +- [ ] The badge enabled the attester proxy: `badge.toggleAttester(attesterProxy, true)`. +- [ ] The attester proxy enabled your backend signer account: `attesterProxy.toggleAttester(signer, true)`. +If your badge minting transaction reverts, we recommend debugging using `cast`: -### How to ensure that the badge can be minted on scroll.io? +```sh +cast run --rpc-url https://rpc.scroll.io [txhash] +``` -TBA +This call will simulate the transaction in a local environment, and show you the call stack and revert reason. diff --git a/docs/integration-faq.md b/docs/canvas-interaction-guide.md similarity index 95% rename from docs/integration-faq.md rename to docs/canvas-interaction-guide.md index ef0f8ae..57d8720 100644 --- a/docs/integration-faq.md +++ b/docs/canvas-interaction-guide.md @@ -1,13 +1,11 @@ -# Canvas Integration FAQ +# Canvas Interaction Guide -In the examples on this page, we use the configurations from [deployments.md](./deployments.md), as well as the following values: +This document will show you the basic steps how one would interact with a Canvas profile. + +In the examples on this page, we use the configurations from [Deployments](./deployments.md), as well as the following values: ```bash # Canvas badges -- each badge type is a new contract, here we only have three simple test contracts -SCROLL_MAINNET_SIMPLE_BADGE_A_ADDRESS="0xB1Dbd079c62d181926E5A54932Bb1b15F760e8A0" -SCROLL_MAINNET_SIMPLE_BADGE_B_ADDRESS="0xe626E631BdDcd985D02D2eEe4fbdF901b52AE33C" -SCROLL_MAINNET_SIMPLE_BADGE_C_ADDRESS="0xe485f8fcBf3b678e83d208fa3f1933a315d58356" - SCROLL_SEPOLIA_SIMPLE_BADGE_A_ADDRESS="0x30C98067517f8ee38e748A3aF63429974103Ea6B" SCROLL_SEPOLIA_SIMPLE_BADGE_B_ADDRESS="0xeBFc9B95328B2Cdb3c4CA8913e329c101d2Abbc2" SCROLL_SEPOLIA_SIMPLE_BADGE_C_ADDRESS="0x64492EF5a60245fbaF65F69782FCf158F3a8e3Aa" @@ -59,7 +57,7 @@ To mint a profile with a referral, produce a signed referral, then submit it alo ### How to list all badges that a user has? -We can use the EAS GraphQL API to query a user's Canvas badges. +We can use the [EAS GraphQL API](https://docs.attest.org/docs/developer-tools/api) to query a user's Canvas badges. > Warning: Badges are minted to the user's wallet address, not to their profile address! diff --git a/docs/ethereum-year-badge.md b/docs/official-badges/ethereum-year-badge.md similarity index 100% rename from docs/ethereum-year-badge.md rename to docs/official-badges/ethereum-year-badge.md diff --git a/docs/scroll-origins-badge.md b/docs/official-badges/scroll-origins-badge.md similarity index 100% rename from docs/scroll-origins-badge.md rename to docs/official-badges/scroll-origins-badge.md From 84db4a555bc90962d1c9e8b04ed0efd0fab8d6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 16 Aug 2024 15:25:13 +0200 Subject: [PATCH 02/11] fix image link --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 8586f26..de8e380 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # Scroll Canvas Developer Documentation -![Components overview](images/overview.png "Overview") +![Components overview](../images/overview.png "Overview") ([Editable link](https://viewer.diagrams.net/?tags=%7B%7D&highlight=0000ff&edit=_blank&layers=1&nav=1&title=skelly-v4.drawio#R7VpLc6M4EP41rpo5xIWEMeYYx8nsIdma2Rx2clRABs0K5BVybO%2BvXwkknk7ijCEwNalUyqjVenV%2F%2FVDDxL6K91842kR3LMB0Aq1gP7FXEwgBsIH8UZRDTllY85wQchJoppJwT%2F7Dmmhp6pYEOK0xCsaoIJs60WdJgn1RoyHO2a7Otma0vuoGhbhFuPcRbVP%2FJoGI9Ckcq6T%2FgUkYmZWBpXtiZJg1IY1QwHYVkn09sa84YyJ%2FivdXmCrhGbnk426e6S02xnEiThkQf%2Ft2uwvggibu7Q9%2Fk1hfCbzQykjFwRwYB%2FL8usm4iFjIEkSvS%2BqSs20SYDWrJVslzy1jG0kEkvgDC3HQykRbwSQpEjHVvXLD%2FPBdj88aD6oxdUxzta92rg66lQrO%2FinUIAW4XBNKrxhlPNu7vV742PcLzkrP48KZOWqOttS0IFO25T5%2BQVQGfYiHWLzAZ%2Bd8So6VBbROvmAWY3kmycAxRYI81XGGNFzDgq%2FUqHzQSn2DgsEsn%2FgJ0a1eagLnVB5gGZCnmurn%2F24VFpd%2BLrZLtcfw8ZPjTaBc2yp%2FP2dylLaUiIs1igk95Nx3OKEsZ7qTgPD1s5wZxRIZSz3%2FlZQ0wVz2%2FIl3zc58SMwSlm6Q0kexUprBSa0DrM0%2B76AkwReRtr%2Bsy9Fd82K%2Beah%2BkRA4FZ8%2Bm8NLYWbnz3uPWsEtepTOrIZcREmYyGdfokeewF4%2BYS6I9BaXuiMmQZAbCZb7RY%2FZfAp3G0YSkSnXWU6clcKuPJQxNKsNy5fMVa2K95Mj%2Fk%2BvWHMxNdTpURfW1Fo4bj72UJvpZFzqyb%2Bqk5WzXHj1EWy9TqW9NHFcbOkMaLeQfX1539LkLiIC3%2BdYWu1kkGpoNN3kYWNN9sqn1dRyxMeCZzTwrKTthVWTsu3q9q4STjQpqkQSQ%2BvcI8CW2O79CMfIWMYjN1bxFw5JapY8R6Ytd7zM%2FrS0j9F7EDx0Bha8O2isLRqnxVq8J%2BJ7ySlbD5WecpBq1OPzm9T5atS1T4y6cFRR12uperCkqR5mOrXFzpQ3G5XyzNVjDNobNuU9VX%2FOqPRnHwlwnFG6RGp%2FR6JcyuiTykdHljk4zsgyh%2FZVoibYDsXXsaM6XeZuU%2BZwYJk77ybzLiDbFN9saPEB%2BJv48ld9NOjaRzeufxoB0G0gADRUmwcdPaqHS2Hb%2B3%2FUO37Zeocx304KHrAGTN06E%2B5FsmiqH4v6DD1WPxYfQFe%2F1pRnZQrMxwT202Ppm5Hs2I26Rn%2BVonbForfgCU8tSIBqOaIoTrxfQQLOhwq4512KwFEz6L36FFyql3CymbAE55QborauFYS4MBw%2BRWlKfEPWbKBnPfadEIFZw1y9hrnmQGolRFIq6FBh0z7mzevc%2FBy%2FfMh30GnQMkKvBK3LLFFRUaN5Mf%2FK2f782vMorpWwKeyha9HwI3vIMEbSdIuzu%2FSYsodXUuXOUom5Z00tS6JzMfechV2%2FuM1U38yZyX%2FHdW3YV2CyPpBYXtiWh7YfXGGKQxmCWDImjPYIS5DBEngAQMe1JDgXdec596ae58zmnu1CD3p9AXPgb2PKetJDte%2B59PiVTCtAaZTtC5yVNRk38Ktlv27LyUh7Q340Dst5EX%2Fn25M1BZb51uNns9n%2BCxoGWhUdyQxwTej4ytzNTwtm3sDpnH3sK68hndfkTZXxM%2FzRqa8oDbpedUhlvdC27XpKZGp9I7ah9usin2Mk2iY0Ij9nsNtFjRdA68zCrinkNt9b9Ki09iuLwvH19klW1y4RgndzibJZfjWdK6H89ty%2B%2Fh8%3D)) From b117f26391df6ec67fa81b5e54af51658fa17bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 16 Aug 2024 15:29:31 +0200 Subject: [PATCH 03/11] add explore section --- docs/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/README.md b/docs/README.md index de8e380..25cc3d4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -58,3 +58,12 @@ Badges are minted to the user's wallet address. The user can express their personalization preferences (attach and reorder badges, choose a profile photo) through their Canvas [`Profile`](../src/profile/Profile.sol). See the [Badges](./badges.md) section for more details. + + +## Explore the Documentation + +Explore the following pages to learn more about different aspects of Canvas: +- [Deployments](./deployments.md) lists the official Canvas contract addresses on Scroll mainnet and on the Scroll Sepolia testnet. +- [Badges](./badges.md) introduces the basic requirements for badge contracts and lists resources for getting started as a badge developer. +- [Canvas Interaction Guide](./canvas-interaction-guide.md) lists common questions and examples for interacting with Canvas profiles and badges. +- [Official Badges](./official-badges) contains addresses and documentation for some badges issued by Scroll. From 9fe4c4f65bf2f3846ff8a25796b5d243cfd1a7e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 16 Aug 2024 15:45:52 +0200 Subject: [PATCH 04/11] update badges.md --- docs/badges.md | 56 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/docs/badges.md b/docs/badges.md index 9d6ec21..674edcd 100644 --- a/docs/badges.md +++ b/docs/badges.md @@ -26,6 +26,32 @@ Refer to the examples in [examples](../src/badge/examples). > While this is not mandatory, we recommend creating badges that do no expire, are non-revocable, and are singletons (at most 1 badge per user). +### Badge Token URI + +Each badge must define a badge token URI. + +The badge token URI is very similar to the tokenURI in ERC-721. +It must point to a metadata JSON object that contains `name`, `description`, `image`, and `issuerName`. +You can use a normal URL, an IPFS link, or a [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs) as your token URI. +The metadata is used by the Canvas frontend to render the badge. + + +For example, the badge token URI https://nft.scroll.io/canvas/year/2024.json points to the following metadata: + +```json +{ + "name": "Ethereum Year", + "description": "Check out the Ethereum Year Badge! It's like a digital trophy that shows off the year your wallet made its debut on Ethereum. It's a little present from Scroll to celebrate all the cool stuff you've done in the Ethereum ecosystem.", + "image": "https://nft.scroll.io/canvas/year/2024.webp", + "issuerName": "Scroll" +} +``` + +Your badge contract can provide a single URI for all badges, in which case all instances of your badge will look the same. +Alternatively, you can also render a different image for different instances of your badge, see [`EthereumYearBadge`](../src/badge/examples/EthereumYearBadge.sol). +You should also configure a default badge token URI, see [`ScrollBadgeDefaultURI`](../src/badge/extensions/ScrollBadgeDefaultURI.sol). + + ### Badge Types Badges are created by attesting to the recipient using the [`BADGE_SCHEMA`](./deployments.md). @@ -59,8 +85,7 @@ There are three main badge types of badges: Type Description -Basic Requirements -Additional Requirements +Requirements Examples @@ -79,6 +104,9 @@ There are three main badge types of badges: + + **Basic Requirements**: +
  • @@ -104,7 +132,9 @@ There are three main badge types of badges: - N/A + + [`ScrollBadgePermissionless`](../src/badge/examples/ScrollBadgePermissionless.sol), [`ScrollBadgeTokenOwner`](../src/badge/examples/ScrollBadgeTokenOwner.sol), [`ScrollBadgeWhale`](../src/badge/examples/ScrollBadgeWhale.sol). + @@ -123,8 +153,9 @@ There are three main badge types of badges: - - + + All **Basic Requirements**, plus +
    • @@ -138,6 +169,12 @@ There are three main badge types of badges:
    + + + + [`EthereumYearBadge`](../src/badge/examples/EthereumYearBadge.sol), [`ScrollBadgeSimple`](../src/badge/examples/ScrollBadgeSimple.sol). + + @@ -155,8 +192,9 @@ There are three main badge types of badges: - - + + All **Basic Requirements**, plus +
    • @@ -165,6 +203,10 @@ There are three main badge types of badges:
    + + + N/A + From d9b2e7c2d1dea3728aad3442b646541064a1e9c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 16 Aug 2024 15:54:13 +0200 Subject: [PATCH 05/11] update --- docs/badges.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/badges.md b/docs/badges.md index 674edcd..fea719d 100644 --- a/docs/badges.md +++ b/docs/badges.md @@ -51,6 +51,11 @@ Your badge contract can provide a single URI for all badges, in which case all i Alternatively, you can also render a different image for different instances of your badge, see [`EthereumYearBadge`](../src/badge/examples/EthereumYearBadge.sol). You should also configure a default badge token URI, see [`ScrollBadgeDefaultURI`](../src/badge/extensions/ScrollBadgeDefaultURI.sol). +Design guidelines for badge images: +- Maximum resolution: 480px x 480px +- Optimal resolution: 600px x 600px +- File size: Under 300KB + ### Badge Types @@ -69,6 +74,7 @@ There are three main badge types of badges: This backend implements some off-chain eligibility check and exposes an eligibility check and claim API. If the user is authorized to mint, the backend issues a signed permit. The user then mints using this permit. + See [this document](https://scrollzkp.notion.site/Badge-APIs-95890d7ca14944e2a6d34835ceb6b914) for the API requirements. For backend-authorized badges, you need to deploy two contracts: the badge contract, and an [`AttesterProxy`](../src/AttesterProxy.sol). `AttesterProxy` allows executing delegated attestations in arbitrary order. @@ -120,12 +126,12 @@ There are three main badge types of badges:
  • - Your project is listed on [Scroll Ecosystem - Browse all protocols](https://scroll.io/ecosystem#protocols). + Your project is listed on [Scroll Ecosystem - Browse all protocols](https://scroll.io/ecosystem#protocols). (If not listed, apply [here](https://tally.so/r/waxLBW).)
  • - All URLs mentioned above are configured for cross-origin access on https://scroll.io. + All URLs mentioned above are configured for cross-origin access (CORS) on https://scroll.io.
@@ -159,7 +165,7 @@ There are three main badge types of badges:
  • - The **check API** and **claim API** have been deployed to **production**. + The [**check API**](https://scrollzkp.notion.site/Badge-APIs-95890d7ca14944e2a6d34835ceb6b914) and [**claim API**](https://scrollzkp.notion.site/Badge-APIs-95890d7ca14944e2a6d34835ceb6b914) have been deployed to **production**.
  • @@ -198,7 +204,7 @@ There are three main badge types of badges:
    • - The **check API** has been deployed to **production**. + The [**check API**](https://scrollzkp.notion.site/Badge-APIs-95890d7ca14944e2a6d34835ceb6b914) has been deployed to **production**.
    From 6026b5cfa975853449f61de3673d4e6be8191a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 16 Aug 2024 15:55:00 +0200 Subject: [PATCH 06/11] update README --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f4d2181..17f50b1 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ ## Welcome to Scroll Canvas -We are thrilled to have you join us in building unique discoveries with Scroll Canvas, a new product designed for ecosystem projects to interact with users in a more tailored way. +We are thrilled to have you join us in building unique discoveries with [Scroll Canvas](https://scroll.io/canvas), a new product designed for ecosystem projects to interact with users in a more tailored way. + +Try Canvas at https://scroll.io/canvas ## Overview @@ -30,6 +32,12 @@ Visit the [Developer Documentation](./docs) in this repo to learn more about Can See [Deployments](./docs/deployments.md) for the official Canvas contract addresses. +See the [Integration Guide](https://scrollzkp.notion.site/Introducing-Scroll-Canvas-Badge-Integration-Guide-8656463ab63b42e8baf924763ed8c9d5) for more information. + +## Support + +For questions regarding Canvas and custom badge development, please join [Scroll dev support channel](https://discord.com/channels/853955156100907018/1028102371894624337) on Discord. + ## Running the Code ### Node.js From 64ad15800faf4853d2e6ca525bf83d946b228716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 16 Aug 2024 15:59:36 +0200 Subject: [PATCH 07/11] nit --- README.md | 4 +++- docs/badges.md | 13 +++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 17f50b1..f12533d 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,11 @@ Users can mint a non-transferable and unique personal persona to collect and dis ### Key Features - **Canvas**: Each Canvas is a smart contract minted through the `ProfileRegistry` contract by the user on Scroll’s website. -- **Badges**: Attestations of achievements and traits verified through the Ethereum Attestation Service ([EAS service](https://docs.attest.sh/docs/welcome)), issued by different projects and the Scroll Foundation. +- **Badges**: Attestations of achievements and traits verified through the [Ethereum Attestation Service](https://docs.attest.sh/docs/welcome) (EAS), issued by different projects and the Scroll Foundation. Badges are wallet-bound and non-transferable. +Differences between attestations and NFTs: + | Attestation | NFT | | --- | --- | | Witness Proofs | Tokenized Assets | diff --git a/docs/badges.md b/docs/badges.md index fea719d..1c22017 100644 --- a/docs/badges.md +++ b/docs/badges.md @@ -74,6 +74,7 @@ There are three main badge types of badges: This backend implements some off-chain eligibility check and exposes an eligibility check and claim API. If the user is authorized to mint, the backend issues a signed permit. The user then mints using this permit. + See [this document](https://scrollzkp.notion.site/Badge-APIs-95890d7ca14944e2a6d34835ceb6b914) for the API requirements. For backend-authorized badges, you need to deploy two contracts: the badge contract, and an [`AttesterProxy`](../src/AttesterProxy.sol). @@ -116,7 +117,7 @@ There are three main badge types of badges:
    • - The badge is deployed on the **Scroll Mainnet** and verified on [ScrollScan](https://scrollscan.com). + The badge is deployed on the **Scroll Mainnet** and verified on [Scrollscan](https://scrollscan.com).
    • @@ -170,7 +171,7 @@ There are three main badge types of badges:
    • - The attester proxy contract is deployed on the **Scroll Mainnet** and verified on [ScrollScan](https://scrollscan.com). + The attester proxy contract is deployed on the **Scroll Mainnet** and verified on [Scrollscan](https://scrollscan.com).
    @@ -212,7 +213,7 @@ There are three main badge types of badges: N/A - + @@ -245,12 +246,12 @@ This repo also contains some [examples](src/badge/examples): We recommend going through this checklist before your badge is published: -- [ ] The badge contract is deployed on Scroll mainnet and verified on Scrollscan. -- [ ] The badge contract configured the correct resolver address, see [Deployments](./deployments.md). +- [ ] The badge contract is deployed on Scroll mainnet and verified on [Scrollscan](https://scrollscan.com). +- [ ] The badge contract was deployed with the correct resolver address, see [Deployments](./deployments.md). Backend-authorized badges: -- [ ] The attester proxy contract is deployed on Scroll mainnet and verified on Scrollscan. +- [ ] The attester proxy contract is deployed on Scroll mainnet and verified on [Scrollscan](https://scrollscan.com). - [ ] The badge enabled the attester proxy: `badge.toggleAttester(attesterProxy, true)`. - [ ] The attester proxy enabled your backend signer account: `attesterProxy.toggleAttester(signer, true)`. From 306b55454ac3d3708d3c3a73a2b3975c1c99d68e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 21 Aug 2024 14:40:56 +0200 Subject: [PATCH 08/11] add section about upgradability --- docs/badges.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/badges.md b/docs/badges.md index 1c22017..4c4bf2a 100644 --- a/docs/badges.md +++ b/docs/badges.md @@ -57,7 +57,7 @@ Design guidelines for badge images: - File size: Under 300KB -### Badge Types +### Ways to Issue Badges Badges are created by attesting to the recipient using the [`BADGE_SCHEMA`](./deployments.md). EAS provides multiple interfaces to attest: `attest`, `attestByDelegation`, `multiAttest`, `multiAttestByDelegation`. See [`IEAS.sol`](https://github.com/ethereum-attestation-service/eas-contracts/blob/master/contracts/IEAS.sol). @@ -219,6 +219,21 @@ There are three main badge types of badges: +### Upgradable Badges + +> This section is not about contract upgradability. +> If you want to make your badge contract upgradable, use any standard [upgradability pattern](https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies). + +Upgradable badges are badges that can evolve over time. +This pattern is most suitable for badges that represent that the user has reached a certain "level". +A user can first mint a badge at a certain level. +Then, once the user is eligible, they can upgrade their badge to a higher level. + +Upgradable badges must implement the [`IScrollBadgeUpgradeable`](../src/badge/extensions/IScrollBadgeUpgradeable.sol) interface. +Currently this interface only supports on-chain upgrade conditions. + + + ### Extensions This repo contains some useful [extensions](src/badge/extensions): From 32a6657cfddf902fe04b3a0ec5d7f9cebca0dc57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 29 Aug 2024 13:45:40 +0200 Subject: [PATCH 09/11] improve docs and add examples --- docs/README.md | 3 +- docs/badge-examples.md | 309 +++++++++++++++++++++++++++++++++++++++++ docs/badges.md | 63 ++++++--- 3 files changed, 357 insertions(+), 18 deletions(-) create mode 100644 docs/badge-examples.md diff --git a/docs/README.md b/docs/README.md index 25cc3d4..ba58f7e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -57,7 +57,7 @@ Badges implement a `badgeTokenURI` interface, similar to `ERC721.tokenURI`. Badges are minted to the user's wallet address. The user can express their personalization preferences (attach and reorder badges, choose a profile photo) through their Canvas [`Profile`](../src/profile/Profile.sol). -See the [Badges](./badges.md) section for more details. +See the [Badges](./badges.md) section for more details, and [Badge Examples](./badge-examples.md) for Solidity code examples. ## Explore the Documentation @@ -65,5 +65,6 @@ See the [Badges](./badges.md) section for more details. Explore the following pages to learn more about different aspects of Canvas: - [Deployments](./deployments.md) lists the official Canvas contract addresses on Scroll mainnet and on the Scroll Sepolia testnet. - [Badges](./badges.md) introduces the basic requirements for badge contracts and lists resources for getting started as a badge developer. +- [Badge Examples](./badge-examples.md) shows the process of developing custom badges by going through some common examples and use cases. - [Canvas Interaction Guide](./canvas-interaction-guide.md) lists common questions and examples for interacting with Canvas profiles and badges. - [Official Badges](./official-badges) contains addresses and documentation for some badges issued by Scroll. diff --git a/docs/badge-examples.md b/docs/badge-examples.md new file mode 100644 index 0000000..884e5d5 --- /dev/null +++ b/docs/badge-examples.md @@ -0,0 +1,309 @@ +# Badge Examples + +- [Permissionless Singleton Badge](#permissionless-singleton-badge) + - [Writing the badge from scratch](#writing-the-badge-from-scratch) + - [Reusing extensions](#reusing-extensions) + - [Minting the badge](#minting-the-badge) +- [Custom Payload and Complex On-Chain Eligibility Checks](#custom-payload-and-complex-on-chain-eligibility-checks) +- [Backend-Authorized Badges](#backend-authorized-badges) + +## Permissionless Singleton Badge + +### Writing the badge from scratch + +First, we will walk through an example of implementing a simple badge from scratch. +The example here, `MyScrollBadge`, is a permissionless badge, i.e. anyone can mint it independently. +The only restriction is that we will require that each user mint for themselves, i.e. you cannot gift a badge to someone else. +We will also ensure that it is a singleton badge, meaning that each user can mint at most one badge. + +We start by importing [`Attestation`](https://github.com/ethereum-attestation-service/eas-contracts/blob/b84f18326432e5f23ec0dfa5dab06ea154c2a502/contracts/Common.sol#L25) from EAS, and [`ScrollBadge`](../src/badge/ScrollBadge.sol) from Canvas. +Just like our example `MyScrollBadge` here, each valid badge is a direct or indirect subclass of `ScrollBadge`. +This ensures that each badge implements the correct interface that [`ScrollBadgeResolver`](../src/resolver/ScrollBadgeResolver.sol) knows how to interact with. + +```solidity +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import {Attestation} from "@eas/contracts/IEAS.sol"; +import {ScrollBadge} from "../ScrollBadge.sol"; + +contract MyScrollBadge is ScrollBadge { + // ... +} +``` + +For correct display, each badge must have a [badge token URI](./badges.md#badge-token-uri). +In this example, we will use a static token URI that is shared for all badges minted with this contract. +This can be a link to a JSON stored on a centralized backend, or stored on decentralized storage like IPFS. + +It is important to note that each badge must configure the correct resolved address during deployment. +See the address in [Deployments](./deployments.md). + +```solidity +string public staticTokenURI; + +constructor(address resolver_, string memory tokenURI_) ScrollBadge(resolver_) { + staticTokenURI = tokenURI_; +} + +function badgeTokenURI(bytes32 /*uid*/ ) public pure override returns (string memory) { + return staticTokenURI; +} +``` + +Next, we implement the `onIssueBadge` hook that is called when your badge is minted. +You can execute checks and revert or return false to prevent an invalid badge from being minted. +Here, we implement two checks: +First, we make sure that the user does not already have a badge. +Second, we check whether the user is minting for themselves or not. + +```solidity +function onIssueBadge(Attestation calldata attestation) internal virtual override returns (bool) { + if (!super.onIssueBadge(attestation)) { + return false; + } + + // singleton + if (hasBadge(attestation.recipient)) { + revert SingletonBadge(); + } + + // self-attest + if (attestation.recipient != attestation.attester) { + revert Unauthorized(); + } + + return true; +} +``` + +Similarly, we also need to implement the `onRevokeBadge` hook, but in most cases, this will be empty. + +```solidity +/// @inheritdoc ScrollBadge +function onRevokeBadge(Attestation calldata attestation) internal virtual override returns (bool) { + return super.onRevokeBadge(attestation); +} +``` + +Finally, we add the on-chain eligibility check function `isEligible` so that the frontend can check if the user is eligible or not. + +```solidity +function isEligible(address recipient) external virtual returns (bool) { + return !hasBadge(recipient); +} +``` + +And now we are ready! +You have implemented your first badge. + +### Reusing extensions + +This type of badge is quite common, so we offer some useful extensions that you can reuse. +You can write the same contract using the [`ScrollBadgeSelfAttest`](../src/badge/extensions/ScrollBadgeSelfAttest.sol), [`ScrollBadgeEligibilityCheck`](../src/badge/extensions/ScrollBadgeEligibilityCheck.sol), and [`ScrollBadgeSingleton`](../src/badge/extensions/ScrollBadgeSingleton.sol) extensions from this repo. + +```solidity +pragma solidity 0.8.19; + +import {Attestation} from "@eas/contracts/IEAS.sol"; + +import {ScrollBadge} from "../ScrollBadge.sol"; +import {ScrollBadgeSelfAttest} from "../extensions/ScrollBadgeSelfAttest.sol"; +import {ScrollBadgeEligibilityCheck} from "../extensions/ScrollBadgeEligibilityCheck.sol"; +import {ScrollBadgeSingleton} from "../extensions/ScrollBadgeSingleton.sol"; + +/// @title ScrollBadgePermissionless +/// @notice A simple badge that anyone can mint in a permissionless manner. +contract ScrollBadgePermissionless is ScrollBadgeSelfAttest, ScrollBadgeEligibilityCheck, ScrollBadgeSingleton { + string public staticTokenURI; + + constructor(address resolver_, string memory tokenURI_) ScrollBadge(resolver_) { + staticTokenURI = tokenURI_; + } + + /// @inheritdoc ScrollBadge + function onIssueBadge(Attestation calldata attestation) + internal + virtual + override (ScrollBadge, ScrollBadgeSelfAttest, ScrollBadgeSingleton) + returns (bool) + { + return super.onIssueBadge(attestation); + } + + /// @inheritdoc ScrollBadge + function onRevokeBadge(Attestation calldata attestation) + internal + virtual + override (ScrollBadge, ScrollBadgeSelfAttest, ScrollBadgeSingleton) + returns (bool) + { + return super.onRevokeBadge(attestation); + } + + /// @inheritdoc ScrollBadge + function badgeTokenURI(bytes32 /*uid*/ ) public pure override returns (string memory) { + return staticTokenURI; + } +} +``` + +### Minting the badge + +Permissionless badges can be minted directly through EAS. +The user can simply call [`EAS.attest`](https://github.com/ethereum-attestation-service/eas-contracts/blob/b84f18326432e5f23ec0dfa5dab06ea154c2a502/contracts/IEAS.sol#L117) and provide the Scroll Canvas [schema UID](./deployments.md) and the attestation. +The attestation payload must include the badge contract address. + + +## Custom Payload and Complex On-Chain Eligibility Checks + +You can attach a custom payload to your badge attestations, that can then be processed in your badge contract. +Let us consider an example of a simple badge that attests that you have reached a certain level. + +Start by deciding your badge payload format. +In this case, we only need a single `uint8` field, signifying the user's level. +Note: The badge payload is encoded using Solidity's [ABI encoding](https://docs.soliditylang.org/en/develop/abi-spec.html). + +```solidity +string constant BADGE_LEVELS_SCHEMA = "uint8 scrollLevel"; + +function decodePayloadData(bytes memory data) pure returns (uint8) { + return abi.decode(data, (uint8)); +} +``` + +If your contract inherits from the [`ScrollBadgeCustomPayload`](../src/badge/extensions/ScrollBadgeCustomPayload.sol) extension, then you can conveniently use the `getPayload` function. + +```solidity +contract ScrollBadgeLevels is ScrollBadgeCustomPayload { + // ... + + /// @inheritdoc ScrollBadgeCustomPayload + function getSchema() public pure override returns (string memory) { + return BADGE_LEVELS_SCHEMA; + } + + function getCurrentLevel(bytes32 uid) public view returns (uint8) { + Attestation memory badge = getAndValidateBadge(uid); + bytes memory payload = getPayload(badge); + (uint8 level) = decodePayloadData(payload); + return level; + } +} +``` + +You can access and interpret the payload during badge minting (in `onIssueBadge`) and badge revocation (in `onRevokeBadge`): + +```solidity +function onIssueBadge(Attestation calldata attestation) internal override returns (bool) { + if (!super.onIssueBadge(attestation)) return false; + + bytes memory payload = getPayload(attestation); + (uint8 level) = decodePayloadData(payload); + + if (level > 10) { + revert InvalidLevel(); + } + + return true; +} +``` + +You can also use the custom payload when constructing the token URI (in `badgeTokenURI`). +This is particularly useful for badges that generate different token URIs based on each badge using [Data URLs](https://developer.mozilla.org/en-US/docs/Web/URI/Schemes/data): + +```solidity + +/// @inheritdoc ScrollBadge + function badgeTokenURI(bytes32 uid) public pure override returns (string memory) { + uint8 level = getCurrentLevel(uid); + string memory name = string(abi.encode("Level #", Strings.toString(level))); + string memory description = "Level Badge"; + string memory image = ""; // IPFS, HTTP, or data URL + string memory issuerName = "Scroll"; + "issuerName": "Scroll" + string memory tokenUriJson = Base64.encode( + abi.encodePacked('{"name":"', name, '", "description":"', description, ', "image": "', image, ', "issuerName": "', issuerName, '"}') + ); + return string(abi.encodePacked("data:application/json;base64,", tokenUriJson)); + } +``` + +You can see the full example in [`ScrollBadgeLevels`](../src/badge/examples/ScrollBadgeLevels.sol). + + +## Backend-Authorized Badges + +Backend authorized badges are badges that require a signed permit to be minted. +This is generally used for badges when there is a centralized issuer who wishes to control who can and cannot mint. +Another use case is off-chain eligibility check, when the signer of the permit vouches that the user is eligible. + +The simplest backend-authorized badge is implemented like this (see [`ScrollBadgeSimple`](../src/badge/examples/ScrollBadgeSimple.sol)): + +```solidity +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import {Attestation} from "@eas/contracts/IEAS.sol"; + +import {ScrollBadgeAccessControl} from "../extensions/ScrollBadgeAccessControl.sol"; +import {ScrollBadgeSingleton} from "../extensions/ScrollBadgeSingleton.sol"; +import {ScrollBadge} from "../ScrollBadge.sol"; + +/// @title ScrollBadgeSimple +/// @notice A simple badge that has the same static metadata for each token. +contract ScrollBadgeSimple is ScrollBadgeAccessControl, ScrollBadgeSingleton { + string public sharedTokenURI; + + constructor(address resolver_, string memory tokenUri_) ScrollBadge(resolver_) { + sharedTokenURI = tokenUri_; + } + + /// @inheritdoc ScrollBadge + function onIssueBadge(Attestation calldata attestation) + internal + override (ScrollBadgeAccessControl, ScrollBadgeSingleton) + returns (bool) + { + return super.onIssueBadge(attestation); + } + + /// @inheritdoc ScrollBadge + function onRevokeBadge(Attestation calldata attestation) + internal + override (ScrollBadgeAccessControl, ScrollBadgeSingleton) + returns (bool) + { + return super.onRevokeBadge(attestation); + } + + /// @inheritdoc ScrollBadge + function badgeTokenURI(bytes32 /*uid*/ ) public view override returns (string memory) { + return sharedTokenURI; + } +} +``` + +Importantly, this badge inherits from `ScrollBadgeAccessControl`. +This allows the deployed to control who is authorized to mint. + +To implement the backend-authorized minting flow, you need to deploy two contracts: the badge contract itself (`ScrollBadgeSimple` in this example) and the attester proxy contract ([`AttesterProxy`](../src/AttesterProxy.sol)). +The attester proxy is a simple contract that verifies permits and mints badges. + +For such badges, all attestations are minted through the attester proxy. +For this reason, you need to authorize the proxy to mint your badge by calling `badge.toggleAttester(attesterProxy, true)`. + +The attester proxy in turn needs to know who is authorized to sign permits, which is typically a private key in your backend. +You also need to authorize this account by calling `attesterProxy.toggleAttester(signer, true)`. + +Finally, you need to configure a backend that implements two public APIs: eligibility check and claim. + +Minting through the Scroll Canvas website then works as follows: +1. The frontend calls your eligibility API to see if the user is eligible. +2. If yes, a mint button is shown to the user. When the user clicks it, the frontend calls your claim API to get the signer permit. +3. The signed permit is submitted from the user's wallet to your attester proxt contract. +4. The attester proxy contract verifies the signature and then creates an attestation through EAS. +5. EAS creates an attestation, then calls `ScrollBadgeResolver`, which in turn calls your badge contract. +6. Your badge contract executes any additional actions and checks. diff --git a/docs/badges.md b/docs/badges.md index 4c4bf2a..9da7420 100644 --- a/docs/badges.md +++ b/docs/badges.md @@ -1,5 +1,18 @@ # Badges +This section introduces the basic concepts of Canvas badges. +For jumping into code examples, see [Badge Examples](./badge-examples.md). + +- [What is a badge?](#what-is-a-badge) +- [How to implement a new badge?](#how-to-implement-a-new-badge) +- [Badge Token URI](#badge-token-uri) +- [Ways to Issue Badges](#ways-to-issue-badges) +- [Overview of Requirements](#overview-of-requirements) +- [Upgradable Badges](#upgradable-badges) +- [Extensions](#extensions) +- [Examples](#examples) +- [Troubleshooting](#troubleshooting) + ### What is a badge? Each Canvas badge is an [EAS attestation](https://docs.attest.sh/docs/core--concepts/attestations), with some additional logic attached to it. @@ -26,7 +39,7 @@ Refer to the examples in [examples](../src/badge/examples). > While this is not mandatory, we recommend creating badges that do no expire, are non-revocable, and are singletons (at most 1 badge per user). -### Badge Token URI +### Badge Token URI Each badge must define a badge token URI. @@ -117,22 +130,32 @@ There are three main badge types of badges:
    • - The badge is deployed on the **Scroll Mainnet** and verified on [Scrollscan](https://scrollscan.com). + The badge contract is deployed on the **Scroll Mainnet** and verified on [Scrollscan](https://scrollscan.com).
    • - The badge contract implements [defaultTokenURI](https://github.com/scroll-tech/canvas-contracts/blob/master/src/badge/extensions/ScrollBadgeDefaultURI.sol). + The badge contract is configured to use the correct **badge resolver address**, see [Deployments](./deployments.md).
    • - Your project is listed on [Scroll Ecosystem - Browse all protocols](https://scroll.io/ecosystem#protocols). (If not listed, apply [here](https://tally.so/r/waxLBW).) + The badge contract has a **default token URI**, see [ScrollBadgeDefaultURI](../src/badge/extensions/ScrollBadgeDefaultURI.sol).
    • +
    + + **Additional Requirements**: + +
    • - All URLs mentioned above are configured for cross-origin access (CORS) on https://scroll.io. + The badge contract implements on-chain eligibility check, see [ScrollBadgeEligibilityCheck](../src/badge/extensions/ScrollBadgeEligibilityCheck.sol). + +
    • +
    • + + Your project is listed on [Scroll Ecosystem - Browse all protocols](https://scroll.io/ecosystem#protocols). (If not listed, apply [here](https://tally.so/r/waxLBW).)
    @@ -166,13 +189,28 @@ There are three main badge types of badges:
    • - The [**check API**](https://scrollzkp.notion.site/Badge-APIs-95890d7ca14944e2a6d34835ceb6b914) and [**claim API**](https://scrollzkp.notion.site/Badge-APIs-95890d7ca14944e2a6d34835ceb6b914) have been deployed to **production**. + The [**check API**](https://scrollzkp.notion.site/Badge-APIs-95890d7ca14944e2a6d34835ceb6b914) (off-chain eligibility check) and [**claim API**](https://scrollzkp.notion.site/Badge-APIs-95890d7ca14944e2a6d34835ceb6b914) have been deployed to **production**. + +
    • +
    • + + All URLs mentioned above are configured for cross-origin access (CORS) on https://scroll.io.
    • The attester proxy contract is deployed on the **Scroll Mainnet** and verified on [Scrollscan](https://scrollscan.com). +
    • +
    • + + The attester proxy contract is authorized to mint your badge (through `badge.toggleAttester`). + +
    • +
    • + + The backend signer is authorized to sign permits (through `attesterProxy.toggleAttester`). +
    @@ -259,16 +297,7 @@ This repo also contains some [examples](src/badge/examples): ### Troubleshooting -We recommend going through this checklist before your badge is published: - -- [ ] The badge contract is deployed on Scroll mainnet and verified on [Scrollscan](https://scrollscan.com). -- [ ] The badge contract was deployed with the correct resolver address, see [Deployments](./deployments.md). - -Backend-authorized badges: - -- [ ] The attester proxy contract is deployed on Scroll mainnet and verified on [Scrollscan](https://scrollscan.com). -- [ ] The badge enabled the attester proxy: `badge.toggleAttester(attesterProxy, true)`. -- [ ] The attester proxy enabled your backend signer account: `attesterProxy.toggleAttester(signer, true)`. +We recommend going through the [requirements](#overview-of-requirements) before your badge is published. If your badge minting transaction reverts, we recommend debugging using `cast`: @@ -276,4 +305,4 @@ If your badge minting transaction reverts, we recommend debugging using `cast`: cast run --rpc-url https://rpc.scroll.io [txhash] ``` -This call will simulate the transaction in a local environment, and show you the call stack and revert reason. +This call will simulate the transaction in a local environment and show you the call stack and revert reason. From 447f50059f5edf40d32d5d54ef6538a013889c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 29 Aug 2024 13:46:06 +0200 Subject: [PATCH 10/11] add license --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3dcad3f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Scroll + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 3e49f8fd64d715fac696e985bcfd3f9c55043477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Thu, 29 Aug 2024 13:50:27 +0200 Subject: [PATCH 11/11] fix code formatting --- README.md | 2 +- docs/badge-examples.md | 75 +++++++++++++++++++++--------------------- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index f12533d..e7c7be3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ We are thrilled to have you join us in building unique discoveries with [Scroll Canvas](https://scroll.io/canvas), a new product designed for ecosystem projects to interact with users in a more tailored way. -Try Canvas at https://scroll.io/canvas +Try Canvas at [scroll.io/canvas](https://scroll.io/canvas) ## Overview diff --git a/docs/badge-examples.md b/docs/badge-examples.md index 884e5d5..cf6f976 100644 --- a/docs/badge-examples.md +++ b/docs/badge-examples.md @@ -44,7 +44,7 @@ See the address in [Deployments](./deployments.md). string public staticTokenURI; constructor(address resolver_, string memory tokenURI_) ScrollBadge(resolver_) { - staticTokenURI = tokenURI_; + staticTokenURI = tokenURI_; } function badgeTokenURI(bytes32 /*uid*/ ) public pure override returns (string memory) { @@ -62,17 +62,17 @@ Second, we check whether the user is minting for themselves or not. function onIssueBadge(Attestation calldata attestation) internal virtual override returns (bool) { if (!super.onIssueBadge(attestation)) { return false; - } + } // singleton if (hasBadge(attestation.recipient)) { revert SingletonBadge(); - } + } // self-attest if (attestation.recipient != attestation.attester) { revert Unauthorized(); - } + } return true; } @@ -119,8 +119,8 @@ contract ScrollBadgePermissionless is ScrollBadgeSelfAttest, ScrollBadgeEligibil string public staticTokenURI; constructor(address resolver_, string memory tokenURI_) ScrollBadge(resolver_) { - staticTokenURI = tokenURI_; - } + staticTokenURI = tokenURI_; + } /// @inheritdoc ScrollBadge function onIssueBadge(Attestation calldata attestation) @@ -128,9 +128,9 @@ contract ScrollBadgePermissionless is ScrollBadgeSelfAttest, ScrollBadgeEligibil virtual override (ScrollBadge, ScrollBadgeSelfAttest, ScrollBadgeSingleton) returns (bool) - { + { return super.onIssueBadge(attestation); - } + } /// @inheritdoc ScrollBadge function onRevokeBadge(Attestation calldata attestation) @@ -138,14 +138,14 @@ contract ScrollBadgePermissionless is ScrollBadgeSelfAttest, ScrollBadgeEligibil virtual override (ScrollBadge, ScrollBadgeSelfAttest, ScrollBadgeSingleton) returns (bool) - { + { return super.onRevokeBadge(attestation); - } + } /// @inheritdoc ScrollBadge function badgeTokenURI(bytes32 /*uid*/ ) public pure override returns (string memory) { return staticTokenURI; - } + } } ``` @@ -182,14 +182,14 @@ contract ScrollBadgeLevels is ScrollBadgeCustomPayload { /// @inheritdoc ScrollBadgeCustomPayload function getSchema() public pure override returns (string memory) { return BADGE_LEVELS_SCHEMA; - } + } function getCurrentLevel(bytes32 uid) public view returns (uint8) { - Attestation memory badge = getAndValidateBadge(uid); + Attestation memory badge = getAndValidateBadge(uid); bytes memory payload = getPayload(badge); - (uint8 level) = decodePayloadData(payload); + (uint8 level) = decodePayloadData(payload); return level; - } + } } ``` @@ -200,11 +200,11 @@ function onIssueBadge(Attestation calldata attestation) internal override return if (!super.onIssueBadge(attestation)) return false; bytes memory payload = getPayload(attestation); - (uint8 level) = decodePayloadData(payload); + (uint8 level) = decodePayloadData(payload); if (level > 10) { revert InvalidLevel(); - } + } return true; } @@ -214,20 +214,21 @@ You can also use the custom payload when constructing the token URI (in `badgeTo This is particularly useful for badges that generate different token URIs based on each badge using [Data URLs](https://developer.mozilla.org/en-US/docs/Web/URI/Schemes/data): ```solidity - /// @inheritdoc ScrollBadge - function badgeTokenURI(bytes32 uid) public pure override returns (string memory) { - uint8 level = getCurrentLevel(uid); - string memory name = string(abi.encode("Level #", Strings.toString(level))); - string memory description = "Level Badge"; - string memory image = ""; // IPFS, HTTP, or data URL - string memory issuerName = "Scroll"; - "issuerName": "Scroll" - string memory tokenUriJson = Base64.encode( - abi.encodePacked('{"name":"', name, '", "description":"', description, ', "image": "', image, ', "issuerName": "', issuerName, '"}') - ); - return string(abi.encodePacked("data:application/json;base64,", tokenUriJson)); - } +function badgeTokenURI(bytes32 uid) public pure override returns (string memory) { + uint8 level = getCurrentLevel(uid); + + string memory name = string(abi.encode("Level #", Strings.toString(level))); + string memory description = "Level Badge"; + string memory image = ""; // IPFS, HTTP, or data URL + string memory issuerName = "Scroll"; + + string memory tokenUriJson = Base64.encode( + abi.encodePacked('{"name":"', name, '", "description":"', description, ', "image": "', image, ', "issuerName": "', issuerName, '"}') + ); + + return string(abi.encodePacked("data:application/json;base64,", tokenUriJson)); +} ``` You can see the full example in [`ScrollBadgeLevels`](../src/badge/examples/ScrollBadgeLevels.sol). @@ -258,31 +259,31 @@ contract ScrollBadgeSimple is ScrollBadgeAccessControl, ScrollBadgeSingleton { string public sharedTokenURI; constructor(address resolver_, string memory tokenUri_) ScrollBadge(resolver_) { - sharedTokenURI = tokenUri_; - } + sharedTokenURI = tokenUri_; + } /// @inheritdoc ScrollBadge function onIssueBadge(Attestation calldata attestation) internal override (ScrollBadgeAccessControl, ScrollBadgeSingleton) returns (bool) - { + { return super.onIssueBadge(attestation); - } + } /// @inheritdoc ScrollBadge function onRevokeBadge(Attestation calldata attestation) internal override (ScrollBadgeAccessControl, ScrollBadgeSingleton) returns (bool) - { + { return super.onRevokeBadge(attestation); - } + } /// @inheritdoc ScrollBadge function badgeTokenURI(bytes32 /*uid*/ ) public view override returns (string memory) { return sharedTokenURI; - } + } } ```