Skip to content

Commit

Permalink
Merge branch 'main' into raribleIngestor
Browse files Browse the repository at this point in the history
  • Loading branch information
s29papi authored Jul 26, 2024
2 parents 322d567 + 37eb249 commit 30b35bf
Show file tree
Hide file tree
Showing 14 changed files with 1,159 additions and 10 deletions.
18 changes: 9 additions & 9 deletions src/dry-run/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { simulateEVMTransactionWithAlchemy } from "../lib/simulation/simulation";
import { ALL_MINT_INGESTORS } from "../ingestors";
import { mintIngestorResources } from "../lib/resources";
import { simulateEVMTransactionWithAlchemy } from '../lib/simulation/simulation';
import { ALL_MINT_INGESTORS } from '../ingestors';
import { mintIngestorResources } from '../lib/resources';
import dotenv from 'dotenv';
import { EVMMintInstructions } from "src/lib";
import { EVMMintInstructions } from 'src/lib';
dotenv.config();

const args = process.argv.slice(2);
Expand All @@ -13,8 +13,8 @@ if (!ingestorName || !inputType || !input) {
process.exit(1);
}

console.log(`Running dry-run\n
ingestory: ${ingestorName}\n
console.log(`Running dry-run
ingestory: ${ingestorName}
inputType: ${inputType}
input: ${input}`);

Expand All @@ -35,15 +35,15 @@ const resources = mintIngestorResources();
result = await ingestor.createMintTemplateForUrl(resources, input);
break;
case 'contract':
const [ chainId, contractAddress, tokenId ] = input.split(':');
const [chainId, contractAddress, tokenId] = input.split(':');
if (!chainId || !contractAddress) {
console.error('Invalid contract input');
process.exit(1);
}
result = await ingestor.createMintForContract(resources, {
chainId: parseInt(chainId),
contractAddress,
tokenId
tokenId,
});
break;
default:
Expand All @@ -65,4 +65,4 @@ const resources = mintIngestorResources();
console.error(error);
process.exit(1);
}
})();
})();
72 changes: 72 additions & 0 deletions src/ingestors/foundation/abi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
export const FOUNDATION_MINT_ABI = [
{
inputs: [{ internalType: 'address', name: 'nftContract', type: 'address' }],
name: 'getFixedPriceSaleV2',
outputs: [
{ internalType: 'address payable', name: 'seller', type: 'address' },
{ internalType: 'uint256', name: 'price', type: 'uint256' },
{ internalType: 'uint256', name: 'limitPerAccount', type: 'uint256' },
{ internalType: 'uint256', name: 'numberOfTokensAvailableToMint', type: 'uint256' },
{ internalType: 'bool', name: 'marketCanMint', type: 'bool' },
{ internalType: 'uint256', name: 'generalAvailabilityStartTime', type: 'uint256' },
{ internalType: 'uint256', name: 'earlyAccessStartTime', type: 'uint256' },
{ internalType: 'uint256', name: 'mintFeePerNftInWei', type: 'uint256' },
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ internalType: 'address', name: 'nftContract', type: 'address' }],
name: 'getDutchAuctionV2',
outputs: [
{ internalType: 'uint256', name: 'maxPrice', type: 'uint256' },
{ internalType: 'uint256', name: 'minPrice', type: 'uint256' },
{ internalType: 'uint256', name: 'limitPerAccount', type: 'uint256' },
{ internalType: 'uint256', name: 'startTime', type: 'uint256' },
{ internalType: 'uint256', name: 'endTime', type: 'uint256' },
{ internalType: 'uint256', name: 'totalAvailableSupply', type: 'uint256' },
{ internalType: 'uint256', name: 'totalMintedCount', type: 'uint256' },
{ internalType: 'uint256', name: 'lastSalePrice', type: 'uint256' },
{ internalType: 'uint256', name: 'currentPrice', type: 'uint256' },
{ internalType: 'uint256', name: 'mintFeePerNftInWei', type: 'uint256' },
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ internalType: 'address', name: 'nftContract', type: 'address' },
{ internalType: 'uint256', name: 'count', type: 'uint256' },
{ internalType: 'address', name: 'nftRecipient', type: 'address' },
],
name: 'mintFromDutchAuctionV2',
outputs: [{ internalType: 'uint256', name: 'firstTokenId', type: 'uint256' }],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{ internalType: 'address', name: 'nftContract', type: 'address' },
{ internalType: 'uint16', name: 'count', type: 'uint16' },
{ internalType: 'address', name: 'nftRecipient', type: 'address' },
{ internalType: 'address payable', name: 'buyReferrer', type: 'address' },
],
name: 'mintFromFixedPriceSaleV2',
outputs: [{ internalType: 'uint256', name: 'firstTokenId', type: 'uint256' }],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{ internalType: 'address', name: 'nftContract', type: 'address' },
{ internalType: 'uint256', name: 'count', type: 'uint256' },
{ internalType: 'address', name: 'nftRecipient', type: 'address' },
{ internalType: 'address payable', name: 'buyReferrer', type: 'address' },
{ internalType: 'bytes32[]', name: 'proof', type: 'bytes32[]' },
],
name: 'mintFromFixedPriceSaleWithEarlyAccessAllowlistV2',
outputs: [{ internalType: 'uint256', name: 'firstTokenId', type: 'uint256' }],
stateMutability: 'payable',
type: 'function',
},
];
157 changes: 157 additions & 0 deletions src/ingestors/foundation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
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 { getFoundationMintPriceInWei } from './onchain-metadata';
import { getFoundationMintByAddress } from './offchain-metadata';
import { FOUNDATION_MINT_ABI } from './abi';

const CONTRACT_ADDRESS = '0x62037b26ffF91929655AA3A060F327b47d1e2b3e';

const getChainId = (chain: string) => {
switch (chain) {
case 'base':
return 8453;
case 'eth':
default:
return 1;
}
};

export class FoundationIngestor implements MintIngestor {
async supportsUrl(_resources: MintIngestorResources, url: string): Promise<boolean> {
const urlSplit = url.split('/');
const slug = urlSplit.pop();
if (!slug || slug.length !== 42 || !slug.startsWith('0x')) {
return false;
}
const chain = urlSplit.pop();
if (chain !== 'base') {
return false;
}
return new URL(url).hostname === 'www.foundation.app' || new URL(url).hostname === 'foundation.app';
}

async supportsContract(resources: MintIngestorResources, contractOptions: MintContractOptions): Promise<boolean> {
if (contractOptions.chainId !== 8453) {
return false;
}
const contract = await getFoundationMintByAddress(resources, contractOptions);
if (!contract) {
return false;
}
if (contract.contractType !== 'FND_BATCH_MINT_REVEAL') {
return false;
}
return true;
}

async createMintForContract(
resources: MintIngestorResources,
contractOptions: MintContractOptions,
): Promise<MintTemplate> {

const isCompatible = await this.supportsContract(resources, contractOptions);
if (!isCompatible) {
throw new MintIngestorError(MintIngestionErrorName.IncompatibleUrl, 'Contract not supported');
}

const mintBuilder = new MintTemplateBuilder()
.setMintInstructionType(MintInstructionType.EVM_MINT)
.setPartnerName('Foundation');

if (contractOptions.url) {
mintBuilder.setMarketingUrl(contractOptions.url);
}

const collection = await getFoundationMintByAddress(resources, contractOptions);

if (!collection) {
throw new MintIngestorError(MintIngestionErrorName.CouldNotResolveMint, 'Collection not found');
}

const contractAddress = collection.contractAddress;

const description = collection?.description;
const image = collection?.media.url;

mintBuilder.setName(collection.name).setDescription(description).setFeaturedImageUrl(image);
mintBuilder.setMintOutputContract({ chainId: 8453, address: contractAddress });

if (collection.coverImageUrl) {
mintBuilder.addImage(collection.coverImageUrl, 'cover-image');
}

if (!collection.creator) {
throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Error finding creator');
}

mintBuilder.setCreator({
name: collection.creator.name,
walletAddress: collection.creator.publicKey,
});

const totalPriceWei = await getFoundationMintPriceInWei(
8453,
CONTRACT_ADDRESS,
contractAddress,
resources.alchemy,
collection.saleType,
);

if (!totalPriceWei) {
throw new MintIngestorError(MintIngestionErrorName.MissingRequiredData, 'Price not available');
}

mintBuilder.setMintInstructions({
chainId: 8453,
contractAddress: CONTRACT_ADDRESS,
contractMethod:
collection.saleType === 'FIXED_PRICE_DROP'
? 'mintFromFixedPriceSaleV2'
: 'mintFromDutchAuctionV2',
contractParams:
collection.saleType === 'FIXED_PRICE_DROP'
? `["${collection.contractAddress}", 1, address, "0x0000000000000000000000000000000000000000"]`
: `["${collection.contractAddress}", 1, address]`,
abi: FOUNDATION_MINT_ABI,
priceWei: totalPriceWei,
});

const now = new Date();
const nowUTCString = now.toISOString();

const liveDate =
new Date() > collection.generalAvailabilityStartTime
? new Date(nowUTCString)
: new Date(collection.generalAvailabilityStartTime);
mintBuilder
.setAvailableForPurchaseStart(new Date(collection.generalAvailabilityStartTime ? `${collection.generalAvailabilityStartTime}Z` : nowUTCString))
.setAvailableForPurchaseEnd(new Date(collection.endTime ? `${collection.endTime}Z` : '2030-01-01T00:00:00Z'))
.setLiveDate(liveDate);

return mintBuilder.build();
}

async createMintTemplateForUrl(
resources: MintIngestorResources,
url: string,
): Promise<MintTemplate> {
const isCompatible = await this.supportsUrl(resources, url);
if (!isCompatible) {
throw new MintIngestorError(MintIngestionErrorName.IncompatibleUrl, 'Incompatible URL');
}

// Example URL: https://foundation.app/mint/base/0x0C92Ce2aECc651Dd3733008A301f126662ae4A50
const splits = url.split('/');
const tokenAddress = splits.pop();
const chain = splits.pop();

if (!chain || !tokenAddress) {
throw new MintIngestorError(MintIngestionErrorName.CouldNotResolveMint, 'Url error');
}

const chainId = getChainId(chain);
return this.createMintForContract(resources, { chainId, contractAddress: tokenAddress, url});
}
}
Loading

0 comments on commit 30b35bf

Please sign in to comment.