# Allo SimpleGrants ## EAS + Offchain Approach #### Create Grant This is how Optimism create projects. It contains of several steps: - Create a Project attestation - Upload Project Metadata - Upload Avatar - Upload Cover - Create Project Metadata attestation with ref to Project - Upload Application Metadata - Create Application attestation These attestations are they pieced together to create something like this: ```tsx ProjectOrg = { type: "Project" project: { name: "Project Name", category: "Category", // Project can have multiple metadata and each application references one of them metadatas: [{ name, description, projectAvatarUrl, ... applications: [{ round: 6, ... }] }, { name, description, projectAvatarUrl, ... applications: [{ round: 5, ... }] }], } } ``` <details> <summary>Code block for how this is created on EAS</summary> <div> ```tsx // Sign In With Farcaster const farcasterID = "..."; // OP RetroFunding Project or Organization const projectRefUID = await eas.attest({ schema: schemas.projectOrOrg, data: { farcasterID, type: "Project", }, }); // Create Project const projectAvatarUrl = await uploadMetadata(ProjectAvatar); const projectCoverImageUrl = await uploadMetadata(ProjectCover); const projectMetadata = { name: "Superfluid", description: "...", projectAvatarUrl: "https://", projectCoverImageUrl: "https://", category: "Social", osoSlug: "Superfluid", socialLinks: { website: ["http://"], farcaster: ["https://"], twitter: "https://", mirror: "https://", }, team: ["637061"], // Farcaster IDs github: [{ url: "https://", name: null, description: null }], packages: [], contracts: [], grantsAndFunding: { ventureFunding: [], grants: [], revenue: [], retroFunding: [], }, pricingModel: null, pricingModelDetails: null, links: [], }; const metadataUrl = await uploadMetadata(projectMetadata); // OP RetroFunding Project Metadata const metadataSnapshotRefUID = await eas.attest({ schema: schemas.projectMetadata, data: { farcasterID, projectRefUID, name: projectMetadata.name, category: projectMetadata.category, parentProjectRefUID: null, metadataType: 2, metadataUrl, }, }); // Create Application const applicationMetadata = { round: 6, category: "Governance Analytics", subcategory: [ "Governance Infrastructure::Technical infrastructure which powers the voting process within Optimism Governance", "Governance Tooling::Tools which are used by Delegates or Citizens participate in Optimism Governance tools which are used by Delegates and/or Citizens asuch as voting clients. Tools which support delegates and citizens insights into governance performance and history", "Grants Tooling::Tools which support the Token House mission process, including the operation of the grants council. Tools which power or support the Retro Funding process.", ], impactStatement: [{ question: "?", answer: "!" }], }; const applicationMetadataUrl = await uploadMetadata(applicationMetadata); // OP RetroFunding Application const applicationRefUID = await eas.attest({ schema: schemas.application, data: { farcasterID, round: applicationMetadata.round, metadataSnapshotRefUID, metadataType: 2, metadataUrl: applicationMetadataUrl, }, }); ``` </div> </details> #### Browse Grants In Optimisms case, Agora has created an Indexer on top of EAS that pieces together all the data and stores in a Web2 database (Postgres). This enables them to more easily search and query the data but it comes with the complexity of the indexer they built. ### Allocation When a voter / funder has selected Grants to support, they navigate to a Checkout page. It is possible to send directly to grantees with `ERC20.transfer(grantee, amount)` but the funder would need to sign each transaction individually. There is a multicall3 contract but mainly it explicitly warns about approving it to spend tokens: > Never approve Multicall3 to spend your tokens. If you do, anyone can steal your tokens. There are likely bots that monitor for this as well. - It is not recommended to inherit from this contract if your contract will hold funds. But if you must, be sure you know what you're doing and protect all state changing methods with an onlyOwner modifier or similar so funds cannot be stolen. https://github.com/mds1/multicall?tab=readme-ov-file#security In addition, integrating protocols like Drips and Superfluid or implementing token or attestation gating would be very difficult (if possible at all). The other option is to transfer tokens to a Round/Strategy contract with: `round.allocateMany(recipients, amounts)` This round contract allocate function is flexible in how the Strategy builder choose to implement it. Here are some examples of what different implementations could look like depending on the RFP details: ```solidity // NFT gating function allocate(address recipient, uint256 amount) onlyNFT(recipient) {} // Only with a balance of > 10 Allo function allocate(address recipient, uint256 amount) onlyERC20(alloTokenAddress, 10**18) {} // Attestation with a specific schema and attester (defined in round.init) function allocate(address recipient, uint256 amount) onlyEAS(schema, attester) {} // Superfluid - start stream function allocate(address recipient, uint256 amount) { (, , uint256 currentUnitsHeld) = superToken.getSubscription(address(this), SUPERTOKEN_INDEX_ID, recipient); superToken.updateSubscriptionUnits(SUPERTOKEN_INDEX_ID, recipient, currentUnitsHeld + amount); } // Drips function allocate(address recipient, uint256 amount) { uint256 accountId = dripsDriver.calcAccountId(recipient); dripsDriver.give(accountId, token, amount); } ``` ### Distribution #### Off-chain Calculations can be done off-chain in a node script / backend API. This is useful when calculations are complex or when external data is desired. For example: - Quadratic calculation on 1000 projects ##### How does it work? 1. Call trigger.dev workflow (UI/Frontend is notified when done) 2. Call `round.addMerkle(root)` 3. Grantees can now generate a proof and call `round.claim(granteeAddress, amount, proof)`