diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 0ccae3965..5c706b4fc 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -110,9 +110,14 @@ "scrollSepoliaBlockExplorer": "Scroll Sepolia Explorer", "sepoliaRollupExplorer": "Rollup Explorer", "sepoliaBlockExplorer": "Scrollscan Explorer", - "canvasBadge": "Canvas & Badge Integration", "transactionJourney": "Checking Transaction Journey" }, + "whatToBuild": { + "whatToBuild": "What to Build", + "stablecoinPaymentsTutorial": "Stablecoin Payments Tutorial", + "solidityCookbook": "Solidity Cookbook", + "privacyDappsWithZk": "Privacy dApps with ZK" + }, "technology": { "introduction": "Introduction", "principles": "Principles", diff --git a/public/locales/es/translation.json b/public/locales/es/translation.json index 242184b7c..cccae1ca4 100644 --- a/public/locales/es/translation.json +++ b/public/locales/es/translation.json @@ -109,6 +109,12 @@ "sepoliaRollupExplorer": "Explorador de Rollup", "sepoliaBlockExplorer": "Explorador de bloques Scrollscan" }, + "whatToBuild": { + "whatToBuild": "Qué construir", + "stablecoinPaymentsTutorial": "Pagos con Stablecoins", + "solidityCookbook": "Recetario de Solidity", + "privacyDappsWithZk": "Dapps Privadas con ZK" + }, "technology": { "introduction": "Introducción", "principles": "Principios", diff --git a/src/assets/images/developers/privacy-dapps-with-zk/proof-steps.png b/src/assets/images/developers/privacy-dapps-with-zk/proof-steps.png new file mode 100644 index 000000000..e2fc7fd08 Binary files /dev/null and b/src/assets/images/developers/privacy-dapps-with-zk/proof-steps.png differ diff --git a/src/assets/images/developers/privacy-dapps-with-zk/zk-dapp.png b/src/assets/images/developers/privacy-dapps-with-zk/zk-dapp.png new file mode 100644 index 000000000..7b4e56f64 Binary files /dev/null and b/src/assets/images/developers/privacy-dapps-with-zk/zk-dapp.png differ diff --git a/src/assets/images/developers/stablecoin-payments-tutorial/nft-recipt.png b/src/assets/images/developers/stablecoin-payments-tutorial/nft-recipt.png new file mode 100644 index 000000000..4de3684d8 Binary files /dev/null and b/src/assets/images/developers/stablecoin-payments-tutorial/nft-recipt.png differ diff --git a/src/assets/images/developers/stablecoin-payments-tutorial/payment-dapp.png b/src/assets/images/developers/stablecoin-payments-tutorial/payment-dapp.png new file mode 100644 index 000000000..da61fd00d Binary files /dev/null and b/src/assets/images/developers/stablecoin-payments-tutorial/payment-dapp.png differ diff --git a/src/config/sidebar.ts b/src/config/sidebar.ts index 66bfb5b74..495200b9a 100644 --- a/src/config/sidebar.ts +++ b/src/config/sidebar.ts @@ -162,10 +162,6 @@ export const getSidebar = () => { title: t("sidebar.developers.runningNode"), url: formatUrl("developers/guides/running-a-scroll-node"), }, - { - title: t("sidebar.developers.canvasBadge"), - url: formatUrl("developers/guides/canvas-badge-integration"), - }, { title: t("sidebar.developers.transactionJourney"), url: formatUrl("developers/guides/checking-transaction-journey"), @@ -184,6 +180,23 @@ export const getSidebar = () => { // }, ], }, + { + section: t("sidebar.whatToBuild.whatToBuild"), + contents: [ + { + title: t("sidebar.whatToBuild.stablecoinPaymentsTutorial"), + url: formatUrl("developers/what-to-build/stablecoin-payments-tutorial"), + }, + { + title: t("sidebar.whatToBuild.solidityCookbook"), + url: formatUrl("developers/what-to-build/solidity-cookbook"), + }, + { + title: t("sidebar.whatToBuild.privacyDappsWithZk"), + url: formatUrl("developers/what-to-build/privacy-dapps-with-zk"), + }, + ], + }, { section: t("sidebar.developers.mainnetResources"), contents: [ diff --git a/src/content/docs/en/developers/_images/badgeDetails.jpg b/src/content/docs/en/developers/_images/badgeDetails.jpg deleted file mode 100644 index b5541d28b..000000000 Binary files a/src/content/docs/en/developers/_images/badgeDetails.jpg and /dev/null differ diff --git a/src/content/docs/en/developers/_images/badgeLevels.png b/src/content/docs/en/developers/_images/badgeLevels.png deleted file mode 100644 index d01105cc7..000000000 Binary files a/src/content/docs/en/developers/_images/badgeLevels.png and /dev/null differ diff --git a/src/content/docs/en/developers/guides/canvas-badge-integration.mdx b/src/content/docs/en/developers/guides/canvas-badge-integration.mdx deleted file mode 100644 index 338142f29..000000000 --- a/src/content/docs/en/developers/guides/canvas-badge-integration.mdx +++ /dev/null @@ -1,230 +0,0 @@ ---- -section: developers -date: Last Modified -title: "Canvas & Badge Integration Guide" -lang: "en" -permalink: "developers/guides/canvas-badge-integration" -excerpt: "This guide contains instructions on how to issue your own badge on Scroll Canvas." ---- - -import Aside from "../../../../../components/Aside.astro" -import BadgeDetails from "../_images/badgeDetails.jpg" -import BadgeLevels from "../_images/badgeLevels.png" -import ClickToZoom from "../../../../../components/ClickToZoom.astro" -import Columns from "../../../../../components/Columns.astro" -import ToggleElement from "../../../../../components/ToggleElement.astro" - -We are thrilled to have you join us in building unique badges on Scroll Canvas, a product designed for ecosystem projects to interact with users in a more personal way on-chain. - -## Background - -### Canvas - -Each Canvas is a `Profile` smart contract, minted by the users through the `ProfileRegistry` contract on Scroll. Canvas is not transferrable and unique to each wallet. (You can check out Canvas contracts [here](https://github.com/scroll-tech/canvas-contracts)) - -Canvas is an open onchain profile database of user identities and achievements ([check out dashboard built by community on Dune](https://dune.com/soodoo/scroll-marks)). - -### Badge - -**Badges** are attestations of identities, achievements and traits issued through the [Ethereum Attestation Service](https://scroll.easscan.org/learn/scroll) **permissionlessly by different ecosystem projects**. Badges are wallet-bound and non-transferable. **Badges facilitate interactions between ecosystem projects and users**. - -Developers can issue badges in three methods: - -| Issuance Method | **Description** | **Example** | -| --- | --- | --- | -| `Permissionless` | Badge checks eligibility based on smart contract. | Badges attest to the completion of an on-chain transaction or holding of an NFT. | -| `Backend-authorized` | Badge checks eligibility based on the issuer’s API. | Badges attest to the completion of off-chain actions or a certain allow list. | -| `Gifted` | Badge checks eligibility based on the issuer’s API and **automatically** sends the badges to users’ canvas. There is no minting required for users to display the badge. | Badges attest to the ownership or paid membership on other platforms / chains. | - -Developers can design badges in two ways: - -| Badge Structure | Description | -| --- | --- | -| **`Singleton badges`** | static attestations based on a certain user action/attribute. | -| **`Leveled badges`** | dynamic attestations based on the progression of the users’ onchain activities | - -## Before you Start - -Badge issuance and deployment of the contract is fully permissionless. Please follow the steps below to issue a Badge for your project. - - - -## Step 1 `Design`: Badge Design Guidelines - - - - - - -### Visual Format Request: - -- Minimum resolution: 480px x 480px -- Optimal resolution: 600px x 600px -- File size: Under 300KB - -### Decide on Badge Details - -| Badge Details | Additional Info | -| --- | --- | -| Name | Name your badge to be *descriptive and creative* | -| Description | Brief description of the *purpose* of this badge and *who is eligible* | -| Structure | *One single attribute*
*Multiple Badges with levels* trigged by different actions and traits | -| Category | *Achievement*: tasks you have done. e.g. Scroll Origin NFTs, tasks completion badge
*Identities*: who you are. e.g. Ethereum year, Gitcoin passport, NFT community badge | -| Issuance method | *Fully permissionless*: meaning your badge can be automatically issued by checking smart contract conditions
*Gifted*: Badges can also be issues with no user interaction, requires APIs
*Backend-authorized*: requires APIs to enable eligibility criteria | -| Support link | *Support channel link* to your Discord/Telegram group | - - - - -## Step 2 `Engineering`: Deploying Badges & Go Live - - - -1. **Understand Canvas and Badges**: Read through this guide and [](https://github.com/scroll-tech/canvas-contracts)understand how Canvas and Badges work together. -2. **Develop and Deploy Badge Contract (**[link to deployment contracts](https://github.com/scroll-tech/canvas-contracts/blob/master/docs/deployments.md)): Ensure it is verified on Scrollscan. For backend-authorized badges, you also need to deploy an `AttesterProxy` -3. **For backend-authorized badges, set up Check and Claim APIs. For gifted badges, set up Check API**: Check eligibility and claim eligibility using provided schemas, confirm correct cross-origin configuration. - -
Reference: Check and Claim API
- - ### Check Eligibility For Minting Badge - - 1. **method**: GET - 2. **url**: `{{pathname}}/check?badge={{badgeContractAddress}}&recipient={{walletAddress}}` - 3. **contentType**: `data/json` - 4. **responseSchema** - - ```jsx - // eligible - { - code: 1, - message: "success", - eligibility: true - } - - // not eligible - { - code: 0, - message: "why the recipient is not eligible", - eligibility: false - } - - // error - // http code is not 200 - ``` - - ### Claim Badge - - 1. **method**: GET - 2. **url**: `{{pathname}}/claim?badge={{badgeContractAddress}}&recipient={{walletAddress}}` - 3. **contentType**: `data/json` - 4. **responseSchema** - - ```jsx - // success - { - code: 1, - message: "success", - tx: ... - } - - // failure - { - code: 0, - message: "some msg", - } - - // error - // http code is not 200 - ``` -
- -4. **Access Readiness**: Ensure your badge contract implements `ScrollBadgeDefaultURI`, allowing retrieval of default display data (`name, image, description, issuerName `) via `bytes32(0)` with informative name and description. -5. **Set up support**: Prepare a channel for your badge holders can find you for support, get the url to join that channel ready. - -## Step 3 `Test`: Sanity Check - -1. Once your badge has been deployed on Scroll, you can auto-check some (but not all) of these requirements by running `yarn check-badge` ([link to check badge script](https://github.com/scroll-tech/canvas-contracts/blob/master/script/CheckBadge.s.sol)). - - - If your badge minting transaction reverts, we recommend debugging using `cast`: - - ```bash - 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. - - For `badgeTokenURI` link returned, ensure correct cross-origin configuration - -
Reference: CORS
- - ### CORS - - > Please ensure the provided API supports the cross-origin requirements. - > - - ### Sepolia - - - **localhost**: This allows for development and testing directly from your local machine environment. - - **Domains containing the substring 'scroll'**: Any domain that includes the substring 'scroll' in its name is allowed to make requests. - - ### Mainnet - - - **`scroll.io` domain**: requests originating from **`scroll.io`** are permitted. -
-2. **Review the information on the badge detail page**: - - Confirm that all basic badge information and redirect links are correct and match the information you provided. - - Verify that the check API is operational. Test it with 2-4 wallet addresses eligible/ineligible to mint this badge and confirm that the claim API is functioning properly. - - If you have created a badge that can be upgraded, please use wallets that meet different level requirements to attempt minting it. Ideally, find at least one wallet per level requirement to ensure the badge mints correctly. -3. To maintain the availability of the check API and claim API, Scroll recommends maintaining a TPS of approximately 300. Adjust this rate accordingly if your API handles multiple badges simultaneously. -4. If your badge image is stored on IPFS, due to the distributed nature of IPFS, the request time for users in different regions to access content on IPFS can be unpredictable. If they want a better user experience, they can self-host the content. - - - - -## Step 4 `Announce`: Reveal Your Badge - -1. To get the word out faster, you are encouraged to embed badge mint and discovery in your product! Scroll provides an example code for anthropomorphic "assistants" to pop up when the user meets the badge minting requirements. - - [**Scroll Canvas - A**nthropomorphic "assistants" **Integration Guide for Developers**](https://github.com/scroll-tech/frontends/blob/mainnet/docs/canvas.md) - -2. Share on social media about your badge launch and the eligibility criteria. Use `#BadgeonScroll` and tag `@Scroll_ZKP` to boost more visibility. -3. Monitor your community / support channel for user feedback. If there is any Canvas backend related issues, please reach out to Scroll Discord channel. - ---- - - - -
Get listed on [Canvas & Badges](https://scroll.io/canvas-and-badges)
- - - - - 1. Ensure that your project is listed on the [Scroll Ecosystem Page](https://scroll.io/ecosystem). If you cannot find it yet, please [fill out this form](https://tally.so/r/waxLBW) to discuss onboarding your Badges with Scroll Partnerships team. - 2. **Application Portal**: [https://scroll.io/canvas/listing](https://scroll.io/canvas/listing) - - General Badge Review Criteria : - - Project is indexed on Scroll’s ecosystem page. If you cannot find it yet, please [fill out this form](https://tally.so/r/waxLBW). - - Complete and clear badge description including Badge name, Issuer name, Issuer URL (website/official link), Support URL (support channel link). - - Clear and reasonable eligibility criteria. Scroll holds discretion on not featuring badges with criteria that can be exploitive to users. - - Badge design is high-resolution, representative of the issuer’s branding. - - **Two badges** will be on display from the same project on Canvas & Badges page. If there are new and innovative new use cases for badges from the same project, Scroll team will consider making exceptions to allow up to two more badges. - - Once your project is visible on the ecosystem page, after they submit the listing request form, Scroll team will review submission and approve on a weekly basis. - - Badge Swaps and Withdrawal - - After reaching the 2 badge quota, if a project team wants to add new badges, they can raise the request in Discord with link to the new badge information. - -
\ No newline at end of file diff --git a/src/content/docs/en/developers/guides/running-a-scroll-node.mdx b/src/content/docs/en/developers/guides/running-a-scroll-node.mdx index 73f506df0..a9bf7c8fb 100644 --- a/src/content/docs/en/developers/guides/running-a-scroll-node.mdx +++ b/src/content/docs/en/developers/guides/running-a-scroll-node.mdx @@ -5,7 +5,7 @@ title: "Running a Scroll L2geth Node" lang: "en" permalink: "developers/guides/running-a-scroll-node" excerpt: "This guide contains instructions on how to run your own node on the Scroll network." -whatsnext: { "Canvas & Bridge Integration Guide": "/developers/guides/canvas-badge-integration" } +whatsnext: { "Checking Transaction Journey Guide": "/developers/guides/checking-transaction-journey" } --- import Aside from "../../../../../components/Aside.astro" diff --git a/src/content/docs/en/developers/what-to-build/privacy-dapps-with-zk.mdx b/src/content/docs/en/developers/what-to-build/privacy-dapps-with-zk.mdx new file mode 100644 index 000000000..a87654589 --- /dev/null +++ b/src/content/docs/en/developers/what-to-build/privacy-dapps-with-zk.mdx @@ -0,0 +1,437 @@ +--- +section: developers +date: Last Modified +title: "Privacy dApps with ZK" +lang: "en" +permalink: "what-to-build/privacy-dapps-with-zk" +excerpt: "Build dApps with privacy by default on Scroll" +whatsnext: { "Verify Your Smart Contracts": "/en/developers/verifying-smart-contracts" } +--- + +import Aside from "../../../../../components/Aside.astro" +import ClickToZoom from "../../../../../components/ClickToZoom.astro" +import ToggleElement from "../../../../../components/ToggleElement.astro" +import proofSteps from "../../../../../assets/images/developers/privacy-dapps-with-zk/proof-steps.png" +import zkDapp from "../../../../../assets/images/developers/privacy-dapps-with-zk/zk-dapp.png" + + +Scroll has been a pioneer in zk dapp support from the start, enabling tools like Circom, Noir, and ZoKrates. Today, we're building a privacy-focused app that leverages Scroll’s low fees, fast blocks, and zk-native guarantees to unlock new use cases. + +Users need privacy in finance, identity, and social interactions but web3 is public by design. The solution is browser-based proving: generating zk proofs locally, before any data touches the internet. This keeps user data secure and private by default. + + +_In order to keep the paramaters private, they should never get out of the browser_ + +Let's get to know, with a practical and simple example, how to create interfaces that make use of zk-wasm, the technology that makes this possible. + +## Dependencies + +For this example, we will use Circom. If you don't have it installed, you can do so with the following commands. + +```bash +curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh +git clone https://github.com/iden3/circom.git +cd circom +cargo build --release +cargo install --path circom +npm install -g snarkjs +``` + +## 1. Create a circuit + +We'll create a very simple example: generating a computation proof for a multiplication a*b=c while keeping a and b private. This will make a solid starting point before building real use cases. + +Circom allows us to create circuits that generate execution proofs while obfuscating the parameters. + +Start by creating the following circuit: + +`myCircuit.circom` + +```js +pragma circom 2.0.0; + +template Multiplier() { + signal input a; + signal input b; + signal output c; + c <== a*b; + } + + component main = Multiplier(); +``` + +Now compile it and generate the artifacts that we will use later. + +```bash +circom myCircuit.circom --r1cs --wasm --sym +snarkjs powersoftau new bn128 12 pot12_0000.ptau -v +snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau --name="First contribution" -v +snarkjs powersoftau prepare phase2 pot12_0001.ptau pot12_final.ptau -v +snarkjs groth16 setup myCircuit.r1cs pot12_final.ptau myCircuit_0000.zkey +snarkjs zkey contribute myCircuit_0000.zkey myCircuit_0001.zkey --name="1st Contributor Name" -v +snarkjs zkey export verificationkey myCircuit_0001.zkey verification_key.json +``` + +## 2. Deploy the contracts + +The following command will generate a verifier contract in the `verifier.sol` file. Deploy it on Scroll testnet by using the framework of your choice (we recommend either [Remix](https://remix.ethereum.org/) for a borwser experience or Foundry as detailed in our [getting started guide](https://docs.scroll.io/en/developers/developer-quickstart/#deploy-your-smart-contract)). This contract contains the `verifyProof()` function, which takes a computation proof made with our circuit as a parameter and returns true if the proof is correct. + +```bash +snarkjs zkey export solidityverifier myCircuit_0001.zkey verifier.sol +``` + +Now deploy the following custom logic contract, passing the address of the verifier contract we deployed earlier as a constructor parameter. In this contract, you can add any desired logic in Solidity, such as vote counting in a voting system or the reception or sending of ERC20 tokens in an anonymous DeFi system. In this example, we will only store the result of the multiplication we did in our circuit. + +```javascript +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.0 <0.9.0; + +// Interface that allows verifing proofs by sending them as a call to the verifier contract we just deployed +interface ICircomVerifier { + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[1] calldata _pubSignals) external view returns (bool); +} + +// Contract that demonstrates the typical structure of a ZK verifer with custom logic in Solidity +contract CircomCustomLogic { + ICircomVerifier circomVerifier; + uint public publicInput; + + // Recieves a valid circom verifer contract address that was autogenerated by snarkjs + constructor(address circomVeriferAddress) { + circomVerifier = ICircomVerifier(circomVeriferAddress); + } + + // Typical proof verifying function, execute custom logic if the proof passed as parameter is correct + function sendProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[1] calldata _pubSignals) public { + // ZK verification + circomVerifier.verifyProof(_pA, _pB, _pC, _pubSignals); + + // Your custom logic, in this case just storing the multiplication result + publicInput = _pubSignals[0]; + } +} +``` + +## 3. Build a frontend + +Let's start by creating a new wagmi frontend in react with the snarkjs dependency that will help us building ZK proofs. + +```bash +cd .. +pnpm create wagmi zk-tutorial -t vite-react +cd frontend +pnpm install snarkjs +``` + +Now create this file structure where you populate the `zk_artifacts` with the build result in your circuit drectory and add the typescript files as detailed below: + +``` +zk-tutorial/ +├── public/ +│ └── zk_artifacts/ +│ ├── myCircuit.wasm +│ ├── myCircuit_final.zkey +│ └── verification_key.json +├── src/ +│ ├── components/ +│ │ └── ZKForm.tsx +│ └── App.tsx +├── index.html +├── tsconfig.json +├── env.local +└── package.json +``` + + + +Add the address of the `CircomCustomLogic` you just deployed to the environment files. + +`zk-tutorial/env.local` +```bash +VITE_COUNTER_CONTRACT_ADDRESS=0xYourNewContractAddress +``` + +Now implement the ZK and web3 logic in a new component. + +`zk-tutorial/src/components/ZKForm.tsx` +```ts +// src/components/ZKForm.tsx +import { useAccount, useWriteContract } from 'wagmi' +import { createPublicClient, http } from 'viem' +import { scrollSepolia } from 'viem/chains' +import { useState, useEffect } from 'react' +import { groth16 } from "snarkjs"; + +// Custom logic contract we just deployed +const CONTRACT_ADDRESS = import.meta.env.VITE_CIRCOM_CUSTOM_LOGIC_CONTRACT_ADDRESS as `0x${string}` + +// In this tutorial we'll use Scroll Sepolia, but this code can also be used on Scroll Mainnet or any EVM chain that supports all precompiles needed for ZK proof +const publicClient = createPublicClient({ + chain: scrollSepolia, + transport: http() +}) + +// Custom logic contract ABI to be able to send proofs on-chain +const verifierAbi = [ + { + "inputs": [ + { + "internalType": "uint256[2]", + "name": "_pA", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2][2]", + "name": "_pB", + "type": "uint256[2][2]" + }, + { + "internalType": "uint256[2]", + "name": "_pC", + "type": "uint256[2]" + }, + { + "internalType": "uint256[1]", + "name": "_pubSignals", + "type": "uint256[1]" + } + ], + "name": "sendProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "publicInput", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] as const + +// Function responsible to generate private computation on the browser and then sending the computation proof on-chain +function useZKForm() { + const [a, setA] = useState("") + const [b, setB] = useState("") + const [message, setMessage] = useState("Connect your wallet") + const [publicInput, setPublicInput] = useState(null) + const { writeContract, isPending: isSendingProof } = useWriteContract() + + // Reads the contract state, in this case the multiplication result that was previously submmited + const fetchPublicInput = async () => { + try { + const result = await publicClient.readContract({ + address: CONTRACT_ADDRESS, + abi: verifierAbi, + functionName: "publicInput", + }) + setPublicInput(Number(result)) + } catch (err) { + console.error("Error fetching public input:", err) + } + } + + // Generates the ZK proof based on private parameters, and then sends the proofs to the smart contract on Scroll + const sendProof = async () => { + setMessage("Generating proof...") + + // Let's start by generating a proof by passing private inputs and the ZK artifacts generated by the circom compiler + // Notice the ZK artifacts should be shared publicly in your website + const { proof, publicSignals } = await groth16.fullProve( + { a: Number(a), b: Number(b) }, + "./zk_artifacts/myCircuit.wasm", + "./zk_artifacts/myCircuit_final.zkey" + ) + + // Now let's verify the proof locally, this is optional + setMessage("Verifying off‑chain...") + const vkey = await fetch("/verification_key.json").then((r) => + r.json() + ) + const valid = await groth16.verify(vkey, publicSignals, proof) + if (!valid) { + setMessage("Proof verification failed") + return + } + + // We now translate the proof in a format compatible with what our smart contract expects + const pA = proof.pi_a.slice(0, 2) // Take first two elements + const pB = proof.pi_b.slice(0, 2).map((row: string[]) => row.slice(0, 2)) + const pC = proof.pi_c.slice(0, 2) + + // Once the proof is ready we send it on-chain + setMessage("Proof generated please confirm transaction.") + writeContract({ + address: CONTRACT_ADDRESS, + abi: verifierAbi, + functionName: "sendProof", + args: [pA, pB, pC, publicSignals], + }, { + onSuccess: (txHash) => { + setMessage("Executing...") + publicClient.waitForTransactionReceipt({ hash: txHash }) + .then(() => { + setMessage("Success!") + fetchPublicInput() + }) + .catch((err) => { + console.error("ERROR! Transaction reverted:", err) + setMessage("Transaction failed") + }) + }, + onError: (err) => { + console.error("ERROR! Transaction reverted:", err) + setMessage("Transaction failed") + } + }) + } + + return { + a, + setA, + b, + setB, + message, + publicInput, + isSendingProof, + sendProof, + fetchPublicInput + } +} + +// React UI that handles contract reads, and private inputs for the ZK proof generation +export default function ZKForm() { + const account = useAccount() + const { + a, setA, + b, setB, + message, + publicInput, + isSendingProof, + sendProof, + fetchPublicInput + } = useZKForm() + + useEffect(() => { + if (account.status === 'connected') { + fetchPublicInput() + } + }, [account.status, fetchPublicInput]) + + return ( +
+

ZK Multiplication

+

{message}

+ setA(e.target.value)} + /> + setB(e.target.value)} + /> + {account.status === 'connected' && ( + + )} + {publicInput !== null && ( +

+ Last stored result: {publicInput} +

+ )} +
+ ) +} +``` + +Add the new component to your app. + +`zk-tutorial/src/App.tsx` +```ts +import { useAccount, useConnect, useDisconnect } from 'wagmi' +// Import your ZKForm +import ZKForm from "./components/ZKForm"; + +function App() { + const account = useAccount() + const { connectors, connect, status, error } = useConnect() + const { disconnect } = useDisconnect() + + return ( + <> +
+

Account

+ +
+ status: {account.status} +
+ addresses: {JSON.stringify(account.addresses)} +
+ chainId: {account.chainId} +
+ + {account.status === 'connected' && ( + + )} +
+ +
+

Connect

+ {connectors.map((connector) => ( + + ))} +
{status}
+
{error?.message}
+
+ +
+

ZK Tutorial

+ +
+ + ) +} + +export default App +``` + +Finally, start the frontend. + +``` +pnpm run dev +``` + + +_Once everything is ready this is how your app should look like_ + + +Now that you know the core parts of a zkDapp you can start experimenting with real use cases. ZK has demonstrated to serve DeFi, DiD, gamming and more. We're excited to see what you will build on Scroll! \ No newline at end of file diff --git a/src/content/docs/en/developers/what-to-build/solidity-cookbook.mdx b/src/content/docs/en/developers/what-to-build/solidity-cookbook.mdx new file mode 100644 index 000000000..6e4f153f5 --- /dev/null +++ b/src/content/docs/en/developers/what-to-build/solidity-cookbook.mdx @@ -0,0 +1,186 @@ +--- +section: developers +date: Last Modified +title: "Solidity Cookbook" +lang: "en" +permalink: "what-to-build/solidity-cookbook" +excerpt: "Solidity snipets to plug into your contracts. DeFi integrations and more." +whatsnext: { "Verify Your Smart Contracts": "/en/developers/verifying-smart-contracts" } +--- + +Smart contracts are open and interoperable by default. Scroll’s ecosystem includes both established blue-chip protocols and native projects. Below is a collection of Solidity code snippets you can use to easily integrate DeFi and more into your contracts. + +Did we miss an important protocol, or do you want to add your own? Send us a PR [here](#TODO). + +## Lend on Aave + +Supplying assets to Aave serves two functions: earning yield and providing collateral for loans. By depositing any idle tokens from your protocol into Aave, you create a sustainable revenue stream either for your platform or your users. Additionally, you can integrate Aave’s lending features directly into your app or abstract the borrowing experience behind your own interface. + +In the following example we will supply USDC to Aave to generate yield. You will be able to see your supplied assets and also other Markets available at [app.aave.com](https://app.aave.com/). Currently ETH, USDC, weETH, wstETH and SCR are supported. + +Learn more at Aave's [official docs](https://aave.com/docs/developers/smart-contracts/pool#write-methods-supply). + + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +// Import Openzeppelin's IERC20 to interact with XXX and Aave's IPool to interact with the supply API +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IPool} from "https://github.com/aave/aave-v3-core/blob/master/contracts/interfaces/IPool.sol"; + +// Demonstrates how to supply tokens into the Scroll Aave pools +contract AaveScrollDemo { + address public immutable AAVE_POOL_ADDRESS = 0x11fCfe756c05AD438e312a7fd934381537D3cFfe; // Aave pool on Scroll Mainnet + address public immutable USDC_ADDRESS = 0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4; // USDC token on Scroll Mainnet + + // Supply tokens to the Aave pool on behalf of the sender + // Important: You need to approve this contract before calling this, also remember USDC uses 6 decimals. Approve here: https://scrollscan.com/token/0x06efdbff2a14a7c8e15944d1f4a48f9f95f663a4#writeProxyContract#F1 + function stake(uint amount) public { + // First we transfer the USDC from the sender into this contract and approve the Aave Pool + IERC20(USDC_ADDRESS).transferFrom(msg.sender, address(this), amount); + IERC20(USDC_ADDRESS).approve(AAVE_POOL_ADDRESS, amount); + // Next we call the supply function + IPool(AAVE_POOL_ADDRESS).supply( + USDC_ADDRESS, + amount, + msg.sender, + 0); + // After the transaction succeeds you should be able to see your supply at app.aave.com + } +} +``` + +## Query Chainlink Price Feeds + +Chainlink has been an historical backbone in DeFi. In this demo we query the bitcoin price on Scroll Mainnet. + +See the complete list of price feeds in the [official documentation](https://docs.chain.link/data-feeds/price-feeds/addresses?page=1&testnetPage=1&network=scroll#scroll-mainnet). + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +// Import Chainlink's aggregator interface that exposes many data feeds +import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; + +// Demonstrates how to query Bitcoin price in Scroll Mainnet +contract ChainlinkScrollDemo { + // Connect your contract with the Bitcoin price provider + AggregatorV3Interface internal dataFeed = AggregatorV3Interface( + 0xCaca6BFdeDA537236Ee406437D2F8a400026C589 + ); + + // View function that returns the current Bitcoin price + function getChainlinkDataFeedLatestAnswer() public view returns (int) { + // prettier-ignore + ( + /* uint80 roundID */, + int answer, + /*uint startedAt*/, + /*uint timeStamp*/, + /*uint80 answeredInRound*/ + ) = dataFeed.latestRoundData(); + return answer; + } +} +``` + +## Attest to Anything on Ethereum Attestation Service + +Define custom schemas and issue attestations for anything, from digital identities to DeFi events, by using the EAS. You can interact with EAS directly on Scroll’s [EAScan](https://scroll.easscan.org/) or integrate it into your own smart contracts. + +Next, we’ll demonstrate how to issue a “friendship attestation” from a smart contract, certifying that the controller of a given address is your friend. + +For full details on EAS and its APIs, refer to the [official documentation](https://docs.attest.org/docs/welcome). + +```solidity +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +import { IEAS, AttestationRequest, AttestationRequestData, RevocationRequest, RevocationRequestData } from "@ethereum-attestation-service/eas-contracts/contracts/IEAS.sol"; +import { NO_EXPIRATION_TIME, EMPTY_UID } from "@ethereum-attestation-service/eas-contracts/contracts/Common.sol"; + +contract EASScrollDemo +{ + address easAddress = 0xaEF4103A04090071165F78D45D83A0C0782c2B2a; + bytes32 schema = 0x27d06e3659317e9a4f8154d1e849eb53d43d91fb4f219884d1684f86d797804a; + + // check at https://scroll-sepolia.easscan.org/schema/view/0x27d06e3659317e9a4f8154d1e849eb53d43d91fb4f219884d1684f86d797804a + + function sendIsFriend(address to, bool isFriend) public returns(bytes32) + { + return IEAS(easAddress).attest( + AttestationRequest({ + schema: schema, + data: AttestationRequestData({ + recipient: to, + expirationTime: NO_EXPIRATION_TIME, + revocable: false, + refUID: EMPTY_UID, + data: abi.encode(isFriend), + value: 0 // No value/ETH + }) + }) + ); + } +} +``` + +## Swap Tokens on Uniswap V3 + +Uniswap V3 is a leading DEX protocol that enables token swaps and concentrated liquidity. You can integrate Uniswap V3 swaps directly into your contracts using the ISwapRouter interface. Below is an example of how to swap USDC for WETH on Scroll Mainnet. Important note: keep in mind that currently, in Scroll, most liquidity is onther DEXes other than Uniswap V3. + +Learn more at Uniswap [V3 Core Docs](https://docs.uniswap.org/contracts/v3/reference/periphery/interfaces/ISwapRouter). + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +// Import the OpenZeppelin's IERC20 so we can interacts with ERC20 tokens +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +// Uniswap v3 Router (Rollups version) deployed on Scroll +interface ISwapRouter { + struct ExactInputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; + uint160 sqrtPriceLimitX96; + } + function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); +} + +// Demonstrates how to make a swap from within a smart contract on uniswap v3 on Scroll Mainnet +contract UniV3ScrollDemo { + ISwapRouter public immutable swapRouter = ISwapRouter(0xfc30937f5cDe93Df8d48aCAF7e6f5D8D8A31F636); + address public constant USDC = 0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4; // USDC on Scroll Mainnet + address public constant WETH9 = 0x5300000000000000000000000000000000000004; // WETH on Scroll Mainnet + uint24 public constant poolFee = 500; // 0.05% + + // Swaps a specified amount of USDC for WETH + function swapUSDCForWETH(uint256 amountIn) external returns (uint256 amountOut) { + // Transfer DAI from sender to this contract and approve the Uniswap Router + IERC20(USDC).transferFrom(msg.sender, address(this), amountIn); + IERC20(USDC).approve(address(swapRouter), amountIn); + + // Set up the swap parameters + ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({ + tokenIn: USDC, + tokenOut: WETH9, + fee: poolFee, + recipient: msg.sender, + amountIn: amountIn, + amountOutMinimum: 0, // WARNING: Set to 0 for simplicity; consider slippage in production + sqrtPriceLimitX96: 0 + }); + + // Execute the swap + amountOut = swapRouter.exactInputSingle(params); + } +} +``` \ No newline at end of file diff --git a/src/content/docs/en/developers/what-to-build/stablecoin-payments-tutorial.mdx b/src/content/docs/en/developers/what-to-build/stablecoin-payments-tutorial.mdx new file mode 100644 index 000000000..a646620f3 --- /dev/null +++ b/src/content/docs/en/developers/what-to-build/stablecoin-payments-tutorial.mdx @@ -0,0 +1,419 @@ +--- +section: developers +date: Last Modified +title: "Stablecoin Payments Tutorial" +lang: "en" +permalink: "developers/stablecoin-payments-tutorial" +excerpt: "Learn how to build a dApp that processes USDT payments and mints NFTs as receipts on Scroll" +whatsnext: { "Verify Your Smart Contracts": "/en/developers/verifying-smart-contracts" } +--- + +import Aside from "../../../../../components/Aside.astro" +import ClickToZoom from "../../../../../components/ClickToZoom.astro" +import ToggleElement from "../../../../../components/ToggleElement.astro" +import paymentDApp from "../../../../../assets/images/developers/stablecoin-payments-tutorial/payment-dapp.png" +import nftRecipt from "../../../../../assets/images/developers/stablecoin-payments-tutorial/nft-recipt.png" + +Welcome to the Scroll Stablecoin Payments Tutorial. This guide walks you through building a dApp that processes USDT payments and mints NFTs as receipts, from smart contract development to frontend integration. + + + +## What You'll Build + +By the time you're done, you'll have: +- Deployed a PaymentProcessor smart contract that handles USDT payments +- Created a React frontend that enables users to approve and make payments +- Implemented NFT minting as payment receipts +- Connected everything to Scroll's network + + +_What you'll build: a payments app that processes USDT payments and mints NFTs as recipts._ + + + +If you run into any issues, please reach out in [our Discord](https://discord.gg/scroll). + +## Install and Configure Foundry + +We'll use Foundry to compile and deploy our `PaymentProcessor` contract. + +Create a folder for the contract: + +```bash +# create a new contracts directory +mkdir contracts +cd contracts +# install and update foundry if you haven't +curl -L https://foundry.paradigm.xyz | bash +foundryup +# initialize a fresh Foundry project +forge init --no-git +# install openzeppelin contracts +npm install @openzeppelin/contracts +``` + +## Configure Contract Dependencies + +Create a `remappings.txt` file to help Foundry locate the OpenZeppelin contracts: + +`remappings.txt` +``` +openzeppelin-contracts/=node_modules/@openzeppelin/contracts/ +``` + + +`src/PaymentProcessor.sol` +```solidity +// SPDX-License-Identifier: MIT +// Compatible with OpenZeppelin Contracts ^5.0.0 +pragma solidity ^0.8.27; + +import {ERC721} from "openzeppelin-contracts/token/ERC721/ERC721.sol"; +import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol"; + +// The following contract will process payments from users by debiting the item price and minting an NFT as recipt +contract PaymentProcessor is ERC721 { + // Internal NFT counter for ID generation + uint _nextTokenId; + // NFT metadata url for our demo + string _tokenURI = "https://raw.githubusercontent.com/Turupawn/erc721-nft-metadata-demo/refs/heads/main/metadata.json"; + // Set to the contract deployer, will receive each USDT payment + address public STORE_OWNER; + // USDT token address in mainnet + address public PAYMENT_TOKEN = 0xf55BEC9cafDbE8730f096Aa55dad6D22d44099Df; + // USDT uses 6 decimals, adapt accordingly if you use other token + uint8 PAYMENT_TOKEN_DECIMALS = 6; + // Item price will cost 0.05 USDT using the formula based on decimal amount + uint public ITEM_PRICE = 5 * 10**PAYMENT_TOKEN_DECIMALS / 100; + + constructor() ERC721("MyToken", "MTK") + { + STORE_OWNER = msg.sender; + } + + // During puchase, the item price will be debited from the user and transfered to the shop owner + function processPurchase() + public + { + uint tokenId = _nextTokenId++; + _safeMint(msg.sender, tokenId); + IERC20(PAYMENT_TOKEN).transferFrom(msg.sender, address(this), ITEM_PRICE); + IERC20(PAYMENT_TOKEN).transfer(STORE_OWNER, ITEM_PRICE); + } + + // Even though in our demo each item has the same metadata we still follow the ERC721 standard + function tokenURI(uint tokenId) + public + view + override(ERC721) + returns (string memory) + { + tokenId; + return _tokenURI; + } +} +``` + +## Deploy the Smart Contract + + + +Set your environment variables: + +`.env` +```bash +SCROLL_RPC_URL="https://rpc.scroll.io/" +PRIVATE_KEY= +``` + +Deploy with Foundry: + +```bash +source .env +forge create src/PaymentProcessor.sol:PaymentProcessor \ + --rpc-url $SCROLL_RPC_URL \ + --broadcast \ + --private-key $PRIVATE_KEY +``` + +You should see output confirming the deployer address, contract address, and transaction hash. Save the contract address under `Deployed to:`, you'll need it when configuring the frontend. + + +```bash +[⠊] Compiling... +No files changed, compilation skipped +Deployer: 0xbef34f2FCAe62dC3404c3d01AF65a7784c9c4A19 +Deployed to: 0xf0e9ceCAE516B2F5Ac297B3857453b07455f817F +Transaction hash: 0x2bca5934ad82ce26332847fdd6a9241b0da0e38e6928f06499ee58ebc967bbde +``` + +## Create and Configure the React Frontend + +Scaffold a React app with Vite, wagmi, and Viem: + +```bash +cd .. +pnpm create wagmi frontend -t vite-react +cd frontend +pnpm install +``` + +Add your deployed contract address to `.env.local`: + +`env.local` +```bash +VITE_PAYMENT_PROCESSOR_ADDRESS=0xYourNewContractAddress +``` + +## Implement the Frontend Logic + +Create a new payments component on `src/components/Payments.tsx`: + +`src/components/Payments.tsx` +```ts +import { useAccount, useWriteContract } from 'wagmi' +import { createPublicClient, http } from 'viem' +import { scroll } from 'viem/chains' +import { useState, useEffect } from 'react' + +const PAYMENT_PROCESSOR_ADDRESS = import.meta.env.VITE_PAYMENT_PROCESSOR_ADDRESS as `0x${string}` +const USDT_ADDRESS = '0xf55BEC9cafDbE8730f096Aa55dad6D22d44099Df' + +// Connect your webapp to Scroll Mainnet +const publicClient = createPublicClient({ + chain: scroll, + transport: http() +}) + +// ABI that allows connecting to our payments smart contract +const paymentProcessorABI = [ + { + inputs: [], + name: "processPurchase", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "ITEM_PRICE", + outputs: [{ type: "uint256" }], + stateMutability: "view", + type: "function", + }, +] as const + +// ERC20 ABI to be able to send USDT to our smart contract +const usdtABI = [ + { + inputs: [ + { name: "spender", type: "address" }, + { name: "amount", type: "uint256" } + ], + name: "approve", + outputs: [{ type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { name: "owner", type: "address" }, + { name: "spender", type: "address" } + ], + name: "allowance", + outputs: [{ type: "uint256" }], + stateMutability: "view", + type: "function", + }, +] as const + +// Custom hook for payment functionality +function usePayment() { + const { address } = useAccount() + const [itemPrice, setItemPrice] = useState(null) + const [allowance, setAllowance] = useState(null) + const { writeContract: writePaymentProcessor, isPending: isProcessing } = useWriteContract() + const { writeContract: writeUSDT, isPending: isApproving } = useWriteContract() + + // Fetches the price of the item set on the smart contract: 0.05 USDT + const fetchItemPrice = async () => { + try { + const result = await publicClient.readContract({ + address: PAYMENT_PROCESSOR_ADDRESS, + abi: paymentProcessorABI, + functionName: 'ITEM_PRICE', + }) + setItemPrice(result) + } catch (error) { + console.error('Error reading item price:', error) + } + } + + // Check if the user already approved the smart contract + const fetchAllowance = async () => { + if (!address) return + try { + const result = await publicClient.readContract({ + address: USDT_ADDRESS, + abi: usdtABI, + functionName: 'allowance', + args: [address, PAYMENT_PROCESSOR_ADDRESS], + }) + setAllowance(result) + } catch (error) { + console.error('Error reading allowance:', error) + } + } + + useEffect(() => { + fetchItemPrice() + fetchAllowance() + }, [address]) + + // Approves USDT to be sent to our smart contract + const approveUSDT = () => { + if (!itemPrice) return + writeUSDT({ + address: USDT_ADDRESS, + abi: usdtABI, + functionName: 'approve', + args: [PAYMENT_PROCESSOR_ADDRESS, itemPrice], + }, { + onSuccess: (txHash) => { + publicClient.waitForTransactionReceipt({ hash: txHash }) + .then(() => fetchAllowance()) + .catch(console.error) + }, + }) + } + + // Process the payment + const processPurchase = () => { + writePaymentProcessor({ + address: PAYMENT_PROCESSOR_ADDRESS, + abi: paymentProcessorABI, + functionName: 'processPurchase', + }, { + onSuccess: (txHash) => { + publicClient.waitForTransactionReceipt({ hash: txHash }) + .then(() => fetchAllowance()) + .catch(console.error) + }, + }) + } + + return { + itemPrice, + allowance, + isProcessing, + isApproving, + approveUSDT, + processPurchase, + refreshAllowance: fetchAllowance + } +} + +// Payment Component +export function Payments() { + const account = useAccount() + const { + itemPrice, + allowance, + isProcessing, + isApproving, + approveUSDT, + processPurchase + } = usePayment() + + const needsApproval = allowance !== null && itemPrice !== null && allowance < itemPrice + + // Displays either the approval or purchase button depending on the state + return ( +
+

Purchase Item

+

Price: {itemPrice ? Number(itemPrice) / 1e6 : '...'} USDT

+ {account.status === 'connected' && ( + <> + {needsApproval ? ( + + ) : ( + + )} + + )} +
+ ) +} +``` + +Add that component to your app: + +`src/App.tsx` +```ts +import { useAccount, useConnect, useDisconnect } from 'wagmi' +// Add your new Payments component +import { Payments } from './components/Payments' + +function App() { + const account = useAccount() + const { connectors, connect, error } = useConnect() + const { disconnect } = useDisconnect() + + return ( +
+
+

Wallet

+ {account.status === 'connected' ? ( + <> +

Connected: {account.addresses?.[0]}

+ + + ) : ( + <> + {connectors.map((connector) => ( + + ))} + {error &&

{error.message}

} + + )} +
+ // Render your new Payments component + +
+ ) +} + +export default App +``` + +## Start the Frontend + +```bash +pnpm run dev +``` + +Open your browser at `http://localhost:5173`. Connect your wallet, approve USDT, and make a purchase to see your NFT receipt on any marketplace that supports Scroll, such as [Element](https://element.market/). + + \ No newline at end of file