diff --git a/packages/premint-sdk/README.md b/packages/premint-sdk/README.md index e0fe25c50..f9d8a69ac 100644 --- a/packages/premint-sdk/README.md +++ b/packages/premint-sdk/README.md @@ -37,6 +37,61 @@ async function makePremint(walletClient: WalletClient) { ``` +### Updating a premint: + +```js +import {PremintAPI} from '@zoralabs/premint-sdk'; +import type {Address, WalletClient} from 'viem'; + +async function updatePremint(walletClient: WalletClient) { + // Create premint API object passing in the current wallet chain (only zora and zora testnet are supported currently). + const premintAPI = new PremintAPI(walletClient.chain); + + // Create premint + const premint = await premintAPI.updatePremint({ + // Extra step to check the signature on-chain before attempting to sign + collection: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + uid: 23, + // WalletClient doing the signature + walletClient, + // Token information, falls back to defaults set in DefaultMintArguments. + token: { + tokenURI: + "ipfs://bafkreice23maski3x52tsfqgxstx3kbiifnt5jotg3a5ynvve53c4soi2u", + }, + }); + + console.log(`updated ZORA premint, link: ${premint.url}`) + return premint; +} + +``` + +### Deleting a premint: + +```js +import {PremintAPI} from '@zoralabs/premint-sdk'; +import type {Address, WalletClient} from 'viem'; + +async function deletePremint(walletClient: WalletClient) { + // Create premint API object passing in the current wallet chain (only zora and zora testnet are supported currently). + const premintAPI = new PremintAPI(walletClient.chain); + + // Create premint + const premint = await premintAPI.deletePremint({ + // Extra step to check the signature on-chain before attempting to sign + collection: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + uid: 23, + // WalletClient doing the signature + walletClient, + }); + + console.log(`updated ZORA premint, link: ${premint.url}`) + return premint; +} + +``` + ### Executing a premint: ```js diff --git a/packages/premint-sdk/package.json b/packages/premint-sdk/package.json index c5c62a801..4116f9d73 100644 --- a/packages/premint-sdk/package.json +++ b/packages/premint-sdk/package.json @@ -10,7 +10,7 @@ "build": "tsup", "prepack": "yarn build", "test": "vitest src", - "generate-types": "npx openapi-typescript https://api.zora.co/premint/openapi.json -o src/generated/premint-api-types.ts", + "generate-types": "npx openapi-typescript https://api.zora.co/premint/openapi.json -o src/generated/premint-api-types.ts && npx openapi-typescript https://api.zora.co/discover/openapi.json -o src/generated/discover-api-types.ts", "anvil": "source .env.anvil && anvil --fork-url $FORK_RPC_URL --fork-block-number $FORK_BLOCK_NUMBER --chain-id 31337" }, "dependencies": { diff --git a/packages/premint-sdk/src/http-api-base.ts b/packages/premint-sdk/src/http-api-base.ts index 84a34ddb4..6625a9083 100644 --- a/packages/premint-sdk/src/http-api-base.ts +++ b/packages/premint-sdk/src/http-api-base.ts @@ -1,9 +1,9 @@ -export class BadResponse extends Error { +export class BadResponseError extends Error { status: number; json: T; constructor(message: string, status: number, json: any) { super(message); - this.name = "BadResponse"; + this.name = "BadResponseError"; this.status = status; this.json = json; } @@ -30,7 +30,7 @@ export const get = async (url: string) => { try { json = await response.json(); } catch (e: any) {} - throw new BadResponse( + throw new BadResponseError( `Invalid response, status ${response.status}`, response.status, json @@ -62,7 +62,7 @@ export const post = async (url: string, data: any) => { try { json = await response.json(); } catch (e: any) {} - throw new BadResponse( + throw new BadResponseError( `Bad response: ${response.status}`, response.status, json @@ -74,14 +74,15 @@ export const post = async (url: string, data: any) => { export const retries = async ( tryFn: () => T, maxTries: number = 3, - atTry: number = 1 + atTry: number = 1, + linearBackoffMS: number = 200 ): Promise => { try { return await tryFn(); } catch (err: any) { - if (err instanceof BadResponse) { + if (err instanceof BadResponseError) { if (atTry <= maxTries) { - await wait(500); + await wait(atTry * linearBackoffMS); return await retries(tryFn, maxTries, atTry++); } } diff --git a/packages/premint-sdk/src/premint-api-client.ts b/packages/premint-sdk/src/premint-api-client.ts index 013285f66..39b4c0b04 100644 --- a/packages/premint-sdk/src/premint-api-client.ts +++ b/packages/premint-sdk/src/premint-api-client.ts @@ -34,15 +34,19 @@ const postSignature = async ( const getNextUID = async ( path: PremintNextUIDGetPathParameters ): Promise => - get( - `${ZORA_API_BASE}signature/${path.chain_name}/${path.collection_address}/next_uid` + retries(() => + get( + `${ZORA_API_BASE}signature/${path.chain_name}/${path.collection_address}/next_uid` + ) ); const getSignature = async ( path: PremintSignatureGetPathParameters ): Promise => - get( - `signature/${path.chain_name}/${path.collection_address}/${path.uid}` + retries(() => + get( + `signature/${path.chain_name}/${path.collection_address}/${path.uid}` + ) ); export const PremintAPIClient = { diff --git a/packages/premint-sdk/src/premint-client.test.ts b/packages/premint-sdk/src/premint-client.test.ts index 93c475b05..888c2a51c 100644 --- a/packages/premint-sdk/src/premint-client.test.ts +++ b/packages/premint-sdk/src/premint-client.test.ts @@ -8,7 +8,7 @@ import { import { foundry } from "viem/chains"; import { describe, it, beforeEach, expect, vi } from "vitest"; import { parseEther } from "viem"; -import { BackendChainNames, PremintAPI } from "./premint-client"; +import { BackendChainNames, PremintClient } from "./premint-client"; const chain = foundry; @@ -48,16 +48,19 @@ describe("ZoraCreator1155Premint", () => { }); }, 20 * 1000); - // skip for now - we need to make this work on zora testnet chain too it( "can sign on the forked premint contract", async () => { - const premintApi = new PremintAPI(chain); + const premintClient = new PremintClient(chain); - premintApi.get = vi.fn().mockResolvedValue({ next_uid: 3 }); - premintApi.post = vi.fn().mockResolvedValue({ ok: true }); + premintClient.premintAPIClient.getNextUID = vi + .fn() + .mockResolvedValue({ next_uid: 3 }); + premintClient.premintAPIClient.postSignature = vi + .fn() + .mockResolvedValue({ ok: true }); - const premint = await premintApi.createPremint({ + const premint = await premintClient.createPremint({ walletClient, publicClient, account: deployerAccount, @@ -74,8 +77,7 @@ describe("ZoraCreator1155Premint", () => { }, }); - expect(premintApi.post).toHaveBeenCalledWith( - "https://api.zora.co/premint/signature", + expect(premintClient.premintAPIClient.postSignature).toHaveBeenCalledWith( { chain_name: BackendChainNames.ZORA_GOERLI, collection: { @@ -111,7 +113,7 @@ describe("ZoraCreator1155Premint", () => { ); it("can validate premint on network", async () => { - const premint = new PremintAPI(chain); + const premintClient = new PremintClient(chain); const premintData = { collection: { @@ -146,19 +148,20 @@ describe("ZoraCreator1155Premint", () => { chain: foundry, transport: http(), }); - const signatureValid = await premint.isValidSignature({ + const signatureValid = await premintClient.isValidSignature({ // @ts-ignore: Fix enum type data: premintData, publicClient, }); + expect(signatureValid.isValid).toBe(true); }); it( "can execute premint on network", async () => { - const premintApi = new PremintAPI(chain); + const premintClient = new PremintClient(chain); - premintApi.get = vi.fn().mockResolvedValue({ + premintClient.premintAPIClient.getSignature = vi.fn().mockResolvedValue({ chain_name: "ZORA-TESTNET", collection: { contractAdmin: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", @@ -187,10 +190,10 @@ describe("ZoraCreator1155Premint", () => { signature: "0x588d19641de9ba1dade4d2bb5387c8dc96f4a990fef69787534b60caead759e6334975a6be10a796da948cd7d1d4f5580b3f84d49d9fa4e0b41c97759507975a1c", }); - premintApi.post = vi.fn(); + premintClient.premintAPIClient.postSignature = vi.fn(); - const premint = await premintApi.executePremintWithWallet({ - data: await premintApi.getPremintData({ + const premint = await premintClient.executePremintWithWallet({ + data: await premintClient.getPremintData({ address: "0xf8dA7f53c283d898818af7FB9d98103F559bDac2", uid: 3, }), @@ -203,7 +206,7 @@ describe("ZoraCreator1155Premint", () => { }, }); - expect(premint.log).toEqual({ + expect(premint.premintedLog).toEqual({ contractAddress: "0xf8dA7f53c283d898818af7FB9d98103F559bDac2", contractConfig: { contractAdmin: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", diff --git a/packages/premint-sdk/src/premint-client.ts b/packages/premint-sdk/src/premint-client.ts index 9304863ea..6909d77a9 100644 --- a/packages/premint-sdk/src/premint-client.ts +++ b/packages/premint-sdk/src/premint-client.ts @@ -106,7 +106,13 @@ export const DefaultMintArguments = { royaltyBPS: 1000, // 10%, }; -function getPremintedLogFromReceipt( +/** + * Gets the preminted log from receipt + * + * @param receipt Preminted log from receipt + * @returns Premint event arguments + */ +export function getPremintedLogFromReceipt( receipt: TransactionReceipt ): PremintedLogType | undefined { for (const data of receipt.logs) { @@ -125,6 +131,7 @@ function getPremintedLogFromReceipt( /** * Convert server to on-chain types for a premint + * * @param premint Premint object from the server to convert to one that's compatible with viem * @returns Viem type-compatible premint object */ @@ -153,6 +160,7 @@ export const convertCollection = ( /** * Convert on-chain types for a premint to a server safe type + * * @param premint Premint object from viem to convert to a JSON compatible type. * @returns JSON compatible premint */ @@ -175,13 +183,16 @@ export const encodePremintForAPI = ({ * Preminter API to access ZORA Premint functionality. * Currently only supports V1 premints. */ -export class PremintAPI { +export class PremintClient { network: NetworkConfig; chain: Chain; premintAPIClient: typeof PremintAPIClient; - constructor(chain: Chain, premintAPIClient: typeof PremintAPIClient) { + constructor(chain: Chain, premintAPIClient?: typeof PremintAPIClient) { this.chain = chain; + if (!premintAPIClient) { + premintAPIClient = PremintAPIClient; + } this.premintAPIClient = premintAPIClient; const networkConfig = networkConfigByChain[chain.id]; if (!networkConfig) { @@ -255,7 +266,7 @@ export class PremintAPI { account?: Account | Address; collection: Address; }): Promise { - const signatureResponse = await getSignature({ + const signatureResponse = await this.premintAPIClient.getSignature({ chain_name: this.network.zoraBackendChainName, collection_address: collection.toLowerCase(), uid: uid, @@ -315,7 +326,7 @@ export class PremintAPI { account?: Account | Address; collection: Address; }) { - const signatureResponse = await getSignature({ + const signatureResponse = await this.premintAPIClient.getSignature({ chain_name: this.network.zoraBackendChainName, collection_address: collection.toLowerCase(), uid: uid, @@ -343,6 +354,7 @@ export class PremintAPI { } /** + * Internal function to sign and submit a premint request. * * @param premintArguments Arguments to premint * @returns @@ -401,7 +413,7 @@ export class PremintAPI { signature: signature, }; - const premint = await postSignature(apiData); + const premint = await this.premintAPIClient.postSignature(apiData); return { urls: this.makeUrls({ address: verifyingContract, uid }), @@ -464,7 +476,7 @@ export class PremintAPI { let uid = executionSettings?.uid; if (!uid) { - const uidResponse = await getNextUID({ + const uidResponse = await this.premintAPIClient.getNextUID({ chain_name: this.network.zoraBackendChainName, collection_address: newContractAddress.toLowerCase(), }); @@ -489,6 +501,7 @@ export class PremintAPI { verifyingContract: newContractAddress, premintConfig, checkSignature, + account, publicClient, walletClient, collection, @@ -509,7 +522,7 @@ export class PremintAPI { address: string; uid: number; }): Promise { - return await getSignature({ + return await this.premintAPIClient.getSignature({ chain_name: this.network.zoraBackendChainName, collection_address: address, uid,