From 23e4ac699d9e77a668754cde06fbf330891dae83 Mon Sep 17 00:00:00 2001 From: Chris Maddern Date: Mon, 16 Dec 2024 10:46:27 -0500 Subject: [PATCH] Remove Prohibition Daily --- README.md | 101 ++++++++---------- src/ingestors/index.ts | 4 +- src/ingestors/prohibition-daily/abi.ts | 91 ---------------- src/ingestors/prohibition-daily/index.ts | 92 ---------------- .../prohibition-daily/offchain-metadata.ts | 26 ----- .../prohibition-daily/onchain-metadata.ts | 42 -------- test/ingestors/prohibition-daily.test.ts | 83 -------------- 7 files changed, 48 insertions(+), 391 deletions(-) delete mode 100644 src/ingestors/prohibition-daily/abi.ts delete mode 100644 src/ingestors/prohibition-daily/index.ts delete mode 100644 src/ingestors/prohibition-daily/offchain-metadata.ts delete mode 100644 src/ingestors/prohibition-daily/onchain-metadata.ts delete mode 100644 test/ingestors/prohibition-daily.test.ts diff --git a/README.md b/README.md index 718f005..6417058 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ ## What is Mobile Minting? + Mobile Minting unlocks mobile payments for onchain purchases and powers minting in the Rally app (formerly known as Floor!). **📱 Users pay in-app with In-App Purchases** @@ -14,7 +15,8 @@ _Example Mobile Minting user experience._
## What is this repository? -This repository is a **public, contributable collection of Ingestors** that teach Mobile Minting how to support mints from new platforms. + +This repository is a **public, contributable collection of Ingestors** that teach Mobile Minting how to support mints from new platforms. Historically, only Rally could choose what mints users could mint through Rally based on our roadmap, or partnerships, now anyone can build support for any minting platform / product and (provided it meets some safety & reliability checks) it can be included in Mobile Minting. @@ -24,7 +26,8 @@ This library is included in Rally's platform and executed in a sandboxed environ ## Adding a new platform to Mobile Minting -Today, Mobile Minting supports the following creator platforms: +Today, Mobile Minting supports the following creator platforms: + - - - - - - -
@@ -162,23 +165,6 @@ Today, Mobile Minting supports the following creator platforms: ❌
- Prohibition Daily - - Base - - ✅ - - ✅ - - ❌ -
Transient Labs @@ -202,16 +188,17 @@ Today, Mobile Minting supports the following creator platforms: Adding a new platform to Mobile Minting is easy - you just have to write a `MintIngestor`! ### MintIngestor functionality + A MintIngestor has a simple job: > Transform a URL or contract address into a valid MintTemplate, iff it represents a supported mint by the ingestor ![template](https://github.com/floornfts/mobile-minting/assets/1068437/7693bfe2-8754-48ba-ac5d-7b222b1435de) -
### What is a `MintTemplate`? + A MintTemplate is a standard format for expressing the details of a mint. It's used to populate the marketing page, pricing, and ultimately fulfill the item onchain. mint-template @@ -220,19 +207,19 @@ _Mapping of MintTemplate fields to an in-app mint._ Once you generate a MintTemplate, Rally will do all the additional work to get the mint live: -* Map priceWei to an in-app purchase -* Simulate the transaction in multiple scenarios to ensure success -* Re-host all images & cache IPFS resources +- Map priceWei to an in-app purchase +- Simulate the transaction in multiple scenarios to ensure success +- Re-host all images & cache IPFS resources The full process looks something like this... ![mint-template-flow](https://github.com/floornfts/mobile-minting/assets/1068437/fbe12e74-da4a-40ab-822b-aea197b35d3f) -
### Writing your first `MintIngestor` -We recommend consulting example complete ingestor PRs e.g. [#1: Prohibition Daily](https://github.com/floornfts/mobile-minting/pull/1/files). + +We recommend consulting example complete ingestor PRs e.g. [#65: VV Mint Ingestor](https://github.com/floornfts/mobile-minting/pull/65). You will create a new folder in `src/ingestors` for your new Ingestor. @@ -260,25 +247,24 @@ For building the MintTemplate, we recommend using the `MintTemplateBuilder` whic In this example we make a template, relying on `getMintMetadataFromSomewhere()` and `getMintContractDetailsFromSomewhere()` to fetch the marketing & onchain data respectively. We'll touch on those later. - ```ts - const mintBuilder = new MintTemplateBuilder() - .setOriginalUrl(url) - .setMintInstructionType(MintInstructionType.EVM_MINT) - .setPartnerName('MyMints'); - - const { name, description, image } = await getMintMetadataFromSomewhere(); - mintBuilder.setName(name).setDescription(description).setFeaturedImageUrl(image); - - const { contractAddress, priceWei } = await getMintContractDetailsFromSomewhere(); - mintBuilder.setMintInstructions({ - chainId: '8453', - contractAddress, - contractMethod: 'mint', - contractParams: '[address, 1]', - abi: YOUR_ABI_FILE_IMPORT, - priceWei: totalPriceWei, - }); +const mintBuilder = new MintTemplateBuilder() + .setOriginalUrl(url) + .setMintInstructionType(MintInstructionType.EVM_MINT) + .setPartnerName('MyMints'); + +const { name, description, image } = await getMintMetadataFromSomewhere(); +mintBuilder.setName(name).setDescription(description).setFeaturedImageUrl(image); + +const { contractAddress, priceWei } = await getMintContractDetailsFromSomewhere(); +mintBuilder.setMintInstructions({ + chainId: '8453', + contractAddress, + contractMethod: 'mint', + contractParams: '[address, 1]', + abi: YOUR_ABI_FILE_IMPORT, + priceWei: totalPriceWei, +}); ``` _Note: You will typically want to implement either `createMintForContract` or `createMintTemplateForUrl`, and then call that one from the other._ @@ -286,7 +272,7 @@ _Note: You will typically want to implement either `createMintForContract` or `c You can then build the MintTemplate from the builder. ```ts - return mintBuilder.build(); +return mintBuilder.build(); ``` Note that `mintBuilder.build()` will throw if the MintTemplate does not meet validation requirements. @@ -294,6 +280,7 @@ Note that `mintBuilder.build()` will throw if the MintTemplate does not meet val
### Getting outside resources + Your MintIngestor is passed a `resources` object in it's sandboxed environment. ```ts @@ -301,8 +288,9 @@ async createMintTemplateForUrl(url: string, resources: MintIngestorResources): P ``` This resources object contains properties: -* `alchemy`: An initalized Alchemy instance -* `fetch`: An HTTP client (Axios) + +- `alchemy`: An initalized Alchemy instance +- `fetch`: An HTTP client (Axios) You can use these to fetch resources you need. @@ -311,10 +299,11 @@ You can use these to fetch resources you need.
### Local Setup + In order to use the Alchemy instance on resources, you will need to set a local Alchemy key. -* Copy `.env.sample` -> `.env` -* Insert your own Alchemy API key as `ALCHEMY_API_KEY` +- Copy `.env.sample` -> `.env` +- Insert your own Alchemy API key as `ALCHEMY_API_KEY` This repo uses [Yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable) to manage dependencies & execution. @@ -328,11 +317,12 @@ yarn test
### Trying out your Mint Ingestor + You can try your mint ingestory using `yarn dry-run` -- this will: -* Create an instance of your minter -* Pass the inputs you provide to it -* Print out the output MintTemplate +- Create an instance of your minter +- Pass the inputs you provide to it +- Print out the output MintTemplate ```bash yarn dry-run @@ -343,6 +333,7 @@ yarn dry-run ``: A full URL for `url`, or a colon delimited fullly qualified address for `contract Examples: + ```bash yarn dry-run prohibition-daily url https://daily.prohibition.art/mint/8453/0x896037d93a231273070dd5f5c9a72aba9a3fe920 yarn dry-run prohibition-daily contract 8453:0x896037d93a231273070dd5f5c9a72aba9a3fe920 @@ -350,9 +341,11 @@ yarn dry-run some-erc-1155-ingestor contract 8453:contractAddress:tokenId ``` # Submitting a Mobile Minting Ingestor + Once you've written a Mobile Minting Ingestor, it needs to be Pull Requested to this repository to be included in the production Rally Mobile Minting ingestion fleet. ### Before you submit + - [ ] Ensure your generated MintTemplate works 😄 - [ ] Ensure that your code is restricted to a single folder in `src/ingestors` - [ ] Ensure that all required assets are included (e.g. ABIs) @@ -363,11 +356,12 @@ Once you've written a Mobile Minting Ingestor, it needs to be Pull Requested to ### Submitting a Mint Ingestor -Open a Pull Request against this repo with your new Ingestor, as well as any comments / questions. +Open a Pull Request against this repo with your new Ingestor, as well as any comments / questions. We're excited to see new platforms supported, so will quickly jump to help! ### Hopes and dreams + Mobile Minting started out entirely internal & this is our first experiment in decentralizing it & making it more accessible. In time, we hope to continue down this path, but for now all ingestors will be reviewed by the Rally engineering team & accepted on the basis of safety, cost & other considerations by Rally. @@ -375,6 +369,5 @@ In time, we hope to continue down this path, but for now all ingestors will be r We hope to see people (other companies!?) emerge for whom Mobile Minting, and a unified standard for expressing onchain mints is useful, and look forward to working with them to continue this mission. ### Questions? -Open an issue, or email developers@rally.xyz - +Open an issue, or email developers@rally.xyz diff --git a/src/ingestors/index.ts b/src/ingestors/index.ts index fda2eac..ef47c70 100644 --- a/src/ingestors/index.ts +++ b/src/ingestors/index.ts @@ -1,5 +1,4 @@ import { MintIngestor } from '../lib/types/mint-ingestor'; -import { ProhibitionDailyIngestor } from './prohibition-daily'; import { ManifoldIngestor } from './manifold'; import { RaribleIngestor } from './rarible'; import { FxHashIngestor } from './fxhash'; @@ -17,14 +16,13 @@ export type MintIngestionMap = { export const ALL_MINT_INGESTORS: MintIngestionMap = { 'zora-internal': new ZoraInternalIngestor(), rodeo: new RodeoIngestor(), - 'prohibition-daily': new ProhibitionDailyIngestor(), fxhash: new FxHashIngestor(), manifold: new ManifoldIngestor(), rarible: new RaribleIngestor(), transient: new TransientIngestor(), highlight: new HighlightIngestor(), foundation: new FoundationIngestor(), - 'coinbase-wallet': new CoinbaseWalletIngestor() + 'coinbase-wallet': new CoinbaseWalletIngestor(), }; export * from './'; diff --git a/src/ingestors/prohibition-daily/abi.ts b/src/ingestors/prohibition-daily/abi.ts deleted file mode 100644 index 91e7819..0000000 --- a/src/ingestors/prohibition-daily/abi.ts +++ /dev/null @@ -1,91 +0,0 @@ -export const PROHIBITION_DAILY_ABI = [ - { - inputs: [], - name: 'contractURI', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'saleStart', - outputs: [ - { - internalType: 'uint32', - name: '', - type: 'uint32', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'saleEnd', - outputs: [ - { - internalType: 'uint32', - name: '', - type: 'uint32', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'quantity', - type: 'uint256', - }, - ], - name: 'mintFee', - outputs: [ - { - internalType: 'uint256', - name: 'fee', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'tokenPrice', - outputs: [ - { - internalType: 'uint96', - name: '', - type: 'uint96', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'numberOfTokens', - type: 'uint256', - }, - ], - name: 'mint', - outputs: [], - stateMutability: 'payable', - type: 'function', - } -]; diff --git a/src/ingestors/prohibition-daily/index.ts b/src/ingestors/prohibition-daily/index.ts deleted file mode 100644 index 032f529..0000000 --- a/src/ingestors/prohibition-daily/index.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { MintContractOptions, MintIngestor, MintIngestorResources } from '../../lib/types/mint-ingestor'; -import { MintIngestionErrorName, MintIngestorError } from '../../lib/types/mint-ingestor-error'; -import { MintInstructionType, MintTemplate } from '../../lib/types/mint-template'; -import { MintTemplateBuilder } from '../../lib/builder/mint-template-builder'; -import { PROHIBITION_DAILY_ABI } from './abi'; -import { getProhibitionContractMetadata, getProhibitionMintPriceInEth } from './onchain-metadata'; -import { prohibitionOnchainDataFromUrl, urlForValidProhibitionPage } from './offchain-metadata'; - -export class ProhibitionDailyIngestor implements MintIngestor { - - configuration = { - supportsContractIsExpensive: true, - }; - - async supportsUrl(resources: MintIngestorResources, url: string): Promise { - if (new URL(url).hostname !== 'daily.prohibition.art') { - return false; - } - const { chainId, contractAddress } = await prohibitionOnchainDataFromUrl(url); - return !!chainId && !!contractAddress; - } - - async supportsContract(resources: MintIngestorResources, contract: MintContractOptions): Promise { - const { chainId, contractAddress } = contract; - if (!chainId || !contractAddress) { - return false; - } - - const url = await urlForValidProhibitionPage(chainId, contractAddress, resources.fetcher); - return !!url; - } - - async createMintTemplateForUrl(resources: MintIngestorResources, url: string): Promise { - const isCompatible = await this.supportsUrl(resources, url); - if (!isCompatible) { - throw new MintIngestorError(MintIngestionErrorName.IncompatibleUrl, 'Incompatible URL'); - } - - const { chainId, contractAddress } = await prohibitionOnchainDataFromUrl(url); - - if (!chainId || !contractAddress) { - throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Missing required data'); - } - - return this.createMintForContract(resources, { chainId, contractAddress, url }); - } - - async createMintForContract(resources: MintIngestorResources, contract: MintContractOptions): Promise { - - const { chainId, contractAddress } = contract; - if (!chainId || !contractAddress) { - throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Missing required data'); - } - - const mintBuilder = new MintTemplateBuilder() - .setMintInstructionType(MintInstructionType.EVM_MINT) - .setPartnerName('Prohibition'); - - if (contract.url) { - mintBuilder.setMarketingUrl(contract.url); - } - - const { name, description, image, startDate, endDate } = await getProhibitionContractMetadata( - chainId, - contractAddress, - resources.alchemy, - ); - - mintBuilder.setName(name).setDescription(description).setFeaturedImageUrl(image); - - const totalPriceWei = await getProhibitionMintPriceInEth(chainId, contractAddress, resources.alchemy); - - mintBuilder.setMintInstructions({ - chainId, - contractAddress, - contractMethod: 'mint', - contractParams: '[address, 1]', - abi: PROHIBITION_DAILY_ABI, - priceWei: totalPriceWei, - - }); - - const liveDate = new Date() > startDate ? new Date() : startDate; - mintBuilder - .setAvailableForPurchaseEnd(endDate || new Date('2030-01-01')) - .setAvailableForPurchaseStart(startDate || new Date()) - .setLiveDate(liveDate); - - const output = mintBuilder.build(); - return output; - } -} diff --git a/src/ingestors/prohibition-daily/offchain-metadata.ts b/src/ingestors/prohibition-daily/offchain-metadata.ts deleted file mode 100644 index 24bf2ea..0000000 --- a/src/ingestors/prohibition-daily/offchain-metadata.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Axios } from "axios"; - -export const urlForValidProhibitionPage = async (chainId: number, contractAddress: string, fetcher: Axios): Promise => { - const url = `https://daily.prohibition.art/mint/${chainId}/${contractAddress}`; - try { - const response = await fetcher.get(url); - if (response.status !== 200) { - return undefined; - } - return url; - } catch (error) { - return undefined; - } -}; - -export const prohibitionOnchainDataFromUrl = async (url: string): Promise<{ chainId: number | undefined, contractAddress: string | undefined }> => { - // https://daily.prohibition.art/mint/8453/0x20479b19ca05e0b63875a65acf24d81cd0973331 - const urlParts = url.split('/'); - const contractAddress = urlParts.pop(); - const chainId = parseInt(urlParts.pop() || ''); - - if (!chainId || Number.isNaN(chainId) || !contractAddress || !contractAddress.startsWith('0x')){ - return { chainId: undefined, contractAddress: undefined }; - } - return { chainId, contractAddress }; -}; \ No newline at end of file diff --git a/src/ingestors/prohibition-daily/onchain-metadata.ts b/src/ingestors/prohibition-daily/onchain-metadata.ts deleted file mode 100644 index 55ed985..0000000 --- a/src/ingestors/prohibition-daily/onchain-metadata.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Alchemy, Contract } from 'alchemy-sdk'; -import { PROHIBITION_DAILY_ABI } from './abi'; - -const getContract = async (chainId: number, contractAddress: string, alchemy: Alchemy): Promise => { - const ethersProvider = await alchemy.config.getProvider(); - const contract = new Contract(contractAddress, PROHIBITION_DAILY_ABI, ethersProvider); - return contract; -}; - -export const getProhibitionContractMetadata = async ( - chainId: number, - contractAddress: string, - alchemy: Alchemy, -): Promise => { - const contract = await getContract(chainId, contractAddress, alchemy); - const metadata = await contract.functions.contractURI(); - - const startDateEpoch = await contract.functions.saleStart(); - const endDateEpoch = await contract.functions.saleEnd(); - - const decoded = atob(metadata[0].split(',')[1]); - const json = JSON.parse(decoded); - - return { - ...json, - startDate: new Date(startDateEpoch * 1000), - endDate: new Date(endDateEpoch * 1000), - }; -}; - -export const getProhibitionMintPriceInEth = async ( - chainId: number, - contractAddress: string, - alchemy: Alchemy, -): Promise => { - const contract = await getContract(chainId, contractAddress, alchemy); - const feePrice = await contract.functions.mintFee(1); - const tokenPrice = await contract.functions.tokenPrice(); - - const totalFee = parseInt(feePrice.toString()) + parseInt(tokenPrice.toString()); - return `${totalFee}`; -}; diff --git a/test/ingestors/prohibition-daily.test.ts b/test/ingestors/prohibition-daily.test.ts deleted file mode 100644 index 1ad3434..0000000 --- a/test/ingestors/prohibition-daily.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { expect } from 'chai'; -import { ProhibitionDailyIngestor } from '../../src/ingestors/prohibition-daily'; -import { mintIngestorResources } from '../../src/lib/resources'; -import { EVMMintInstructions } from '../../src/lib/types/mint-template'; -import { MintTemplateBuilder } from '../../src/lib/builder/mint-template-builder'; -import { basicIngestorTests } from '../shared/basic-ingestor-tests'; - -const resources = mintIngestorResources(); - -describe('prohibition-daily', function () { - basicIngestorTests(new ProhibitionDailyIngestor(), resources, { - successUrls: [ - 'https://daily.prohibition.art/mint/8453/0x4680c6a96941a977feaf71e503f3d0409157e02f' - ], - failureUrls: [ - 'https://daily.prohibition.art/', - 'https://daily.prohibition.art/mint/8453/0x4680c6a96941a977feaf71e503f3d0409157e02f/extra' - ], - successContracts: [ - { chainId: 8453, contractAddress: '0x4680c6a96941a977feaf71e503f3d0409157e02f' } - ], - failureContracts: [ - { chainId: 8453, contractAddress: '0x965ef172b303b0bcdc38669df1de3c26bad2db8a' }, - { chainId: 8453, contractAddress: 'derp' } - ] - }, { - 8453: '0xF469C6' - }); - it('createMintTemplateForUrl: Returns a mint template for a supported URL', async function () { - const ingestor = new ProhibitionDailyIngestor(); - const url = 'https://daily.prohibition.art/mint/8453/0x4680c6a96941a977feaf71e503f3d0409157e02f'; - - const template = await ingestor.createMintTemplateForUrl(resources, url); - - // Verify that the mint template passed validation - const builder = new MintTemplateBuilder(template); - builder.validateMintTemplate(); - - expect(template.name).to.equal('Equilibrium'); - expect(template.description).to.equal('An animated mixed media piece inspired by the beautiful harmony between humanity and nature.'); - const mintInstructions = template.mintInstructions as EVMMintInstructions; - - expect(mintInstructions.contractAddress).to.equal('0x4680c6a96941a977feaf71e503f3d0409157e02f'); - expect(mintInstructions.contractMethod).to.equal('mint'); - expect(mintInstructions.contractParams).to.equal('[address, 1]'); - expect(mintInstructions.priceWei).to.equal('2300000000000000'); - - expect(template.featuredImageUrl?.length).to.be.greaterThan(0); - - expect(template.marketingUrl).to.equal(url); - expect(template.availableForPurchaseStart?.getTime()).to.equal(1718812800000); - expect(template.availableForPurchaseEnd?.getTime()).to.equal(1718899140000); - }); - it('createMintTemplateForContract: Returns a mint template for a supported contract', async function () { - const ingestor = new ProhibitionDailyIngestor(); - const contract = { - chainId: 8453, - contractAddress: '0x4680c6a96941a977feaf71e503f3d0409157e02f', - url: 'https://daily.prohibition.art/mint/8453/0x4680c6a96941a977feaf71e503f3d0409157e02f' - }; - - const template = await ingestor.createMintForContract(resources, contract); - - // Verify that the mint template passed validation - const builder = new MintTemplateBuilder(template); - builder.validateMintTemplate(); - - expect(template.name).to.equal('Equilibrium'); - expect(template.description).to.equal('An animated mixed media piece inspired by the beautiful harmony between humanity and nature.'); - const mintInstructions = template.mintInstructions as EVMMintInstructions; - - expect(mintInstructions.contractAddress).to.equal('0x4680c6a96941a977feaf71e503f3d0409157e02f'); - expect(mintInstructions.contractMethod).to.equal('mint'); - expect(mintInstructions.contractParams).to.equal('[address, 1]'); - expect(mintInstructions.priceWei).to.equal('2300000000000000'); - - expect(template.featuredImageUrl?.length).to.be.greaterThan(0); - - expect(template.marketingUrl).to.equal(contract.url); - expect(template.availableForPurchaseStart?.getTime()).to.equal(1718812800000); - expect(template.availableForPurchaseEnd?.getTime()).to.equal(1718899140000); - }); -}); \ No newline at end of file