diff --git a/.changeset/soft-dots-live.md b/.changeset/soft-dots-live.md new file mode 100644 index 000000000..c56ddf7dc --- /dev/null +++ b/.changeset/soft-dots-live.md @@ -0,0 +1,5 @@ +--- +"@zoralabs/premint-sdk": minor +--- + +Added new premint api that abstracts out calls to the chain signature and submission logic around submitting a premint. This change also incorporates test helpers for premints and introduces docs and an api client for the zora api's premint module. diff --git a/.gitignore b/.gitignore index 9ba31bd41..ae6e5aa50 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ dist/ .env .env.* !.env.example +!.env.anvil package/wagmiGenerated.ts package/chainConfigs.ts diff --git a/packages/1155-contracts/package.json b/packages/1155-contracts/package.json index b54d00e11..9f34caf5a 100644 --- a/packages/1155-contracts/package.json +++ b/packages/1155-contracts/package.json @@ -1,10 +1,10 @@ { "name": "@zoralabs/zora-1155-contracts", - "version": "2.1.0", + "version": "2.2.0", "repository": "git@github.com:ourzora/zora-protocol.git", "author": "Iain ", "license": "MIT", - "main": "./dist/index.cjs", + "main": "./dist/index.js", "types": "./dist/package/index.d.ts", "type": "module", "scripts": { @@ -45,29 +45,25 @@ ], "dependencies": { "@openzeppelin/contracts": "4.9.2", - "@wagmi/cli": "^1.0.1", "@zoralabs/openzeppelin-contracts-upgradeable": "4.8.4", "@zoralabs/protocol-rewards": "*", - "abitype": "^0.8.7", "ds-test": "https://github.com/dapphub/ds-test#cd98eff28324bfac652e63a239a60632a761790b", - "es-main": "^1.2.0", "forge-std": "https://github.com/foundry-rs/forge-std#705263c95892a906d7af65f0f73ce8a4a0c80b80", - "glob": "^10.2.2", - "prettier": "^2.8.8", - "prettier-plugin-solidity": "^1.1.3", "solady": "^0.0.123", - "solmate": "^6.1.0", - "tsup": "^7.2.0", - "tsx": "^3.13.0", - "typescript": "^5.0.4", - "viem": "^1.16.2", - "vite": "^4.1.4", - "vitest": "~0.30.1" + "solmate": "^6.1.0" }, "devDependencies": { + "@wagmi/cli": "^1.0.1", + "tsx": "^3.13.0", + "glob": "^10.2.2", + "prettier": "^2.8.8", + "es-main": "^1.2.0", "@turnkey/api-key-stamper": "^0.1.1", + "prettier-plugin-solidity": "^1.1.3", "@turnkey/http": "^1.2.0", "@turnkey/viem": "^0.2.4", - "@types/node": "^20.1.2" + "@types/node": "^20.1.2", + "tsup": "^7.2.0", + "typescript": "^5.0.4" } } diff --git a/packages/1155-contracts/src/version/ContractVersionBase.sol b/packages/1155-contracts/src/version/ContractVersionBase.sol index 93eacb140..2795dd3a1 100644 --- a/packages/1155-contracts/src/version/ContractVersionBase.sol +++ b/packages/1155-contracts/src/version/ContractVersionBase.sol @@ -1,5 +1,5 @@ // This file is automatically generated by code; do not manually update -// Last updated on 2023-10-18T17:43:11.859Z +// Last updated on 2023-10-30T19:58:54.830Z // SPDX-License-Identifier: MIT pragma solidity 0.8.17; @@ -10,6 +10,6 @@ import {IVersionedContract} from "../interfaces/IVersionedContract.sol"; contract ContractVersionBase is IVersionedContract { /// @notice The version of the contract function contractVersion() external pure override returns (string memory) { - return "2.1.0"; + return "2.2.0"; } } diff --git a/packages/1155-contracts/tsconfig.json b/packages/1155-contracts/tsconfig.json index 6f9b11b7e..7a2947026 100644 --- a/packages/1155-contracts/tsconfig.json +++ b/packages/1155-contracts/tsconfig.json @@ -21,8 +21,5 @@ "outDir": "dist" }, "exclude": ["node_modules/**", "dist/**"], - "include": [ - "package/**/*.ts", - "script/*.ts" - ], -} \ No newline at end of file + "include": ["package/**/*.ts", "script/*.ts"] +} diff --git a/packages/1155-contracts/tsup.config.ts b/packages/1155-contracts/tsup.config.ts index 3be140a45..1f0e7d5a4 100644 --- a/packages/1155-contracts/tsup.config.ts +++ b/packages/1155-contracts/tsup.config.ts @@ -5,6 +5,6 @@ export default defineConfig({ sourcemap: true, clean: true, dts: false, - format: ['cjs'], + format: ['cjs', 'esm'], onSuccess: 'tsc --emitDeclarationOnly --declaration --declarationMap' }) \ No newline at end of file diff --git a/packages/1155-contracts/.env.anvil b/packages/premint-sdk/.env.anvil similarity index 100% rename from packages/1155-contracts/.env.anvil rename to packages/premint-sdk/.env.anvil diff --git a/packages/premint-sdk/CHANGELOG.md b/packages/premint-sdk/CHANGELOG.md new file mode 100644 index 000000000..fc9d782fa --- /dev/null +++ b/packages/premint-sdk/CHANGELOG.md @@ -0,0 +1,22 @@ +# @zoralabs/premint-sdk + +## 0.0.2-premint-api.2 + +### Patch Changes + +- c29e080: Update retry and error reporting + +## 0.0.2-premint-api.1 + +### Patch Changes + +- 6eaf7bb: add retries + +## 0.0.2-premint-api.0 + +### Patch Changes + +- Updated dependencies [8395b8e] +- Updated dependencies [aae756b] +- Updated dependencies [cf184b3] + - @zoralabs/zora-1155-contracts@2.1.1-premint-api.0 diff --git a/packages/premint-sdk/README.md b/packages/premint-sdk/README.md new file mode 100644 index 000000000..f9d8a69ac --- /dev/null +++ b/packages/premint-sdk/README.md @@ -0,0 +1,131 @@ +# Premint SDK + +Premint SDK allows users to manage zora premints + +### Creating a premint: + +```js +import {PremintAPI} from '@zoralabs/premint-sdk'; +import type {Address, WalletClient} from 'viem'; + +async function makePremint(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.createPremint({ + // Extra step to check the signature on-chain before attempting to sign + checkSignature: true, + // Collection information that this premint NFT will exist in once minted. + collection: { + contractAdmin: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + contractName: "Testing Contract", + contractURI: "ipfs://bafkreiainxen4b4wz4ubylvbhons6rembxdet4a262nf2lziclqvv7au3e", + }, + // WalletClient doing the signature + walletClient, + // Token information, falls back to defaults set in DefaultMintArguments. + token: { + tokenURI: + "ipfs://bafkreice23maski3x52tsfqgxstx3kbiifnt5jotg3a5ynvve53c4soi2u", + }, + }); + + console.log(`created ZORA premint, link: ${premint.url}`) + return premint; +} + +``` + +### 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 +import {PremintAPI} from '@zoralabs/premint-sdk'; +import type {Address, WalletClient} from 'viem'; + +async function executePremint(walletClient: WalletClient, premintAddress: Address, premintUID: number) { + const premintAPI = new PremintAPI(walletClient.chain); + + return await premintAPI.executePremintWithWallet({ + data: premintAPI.getPremintData(premintAddress, premintUID), + walletClient, + mintArguments: { + quantityToMint: 1, + } + }); +} + +``` + +### Deleting a premint: + +```js +import {PremintAPI} from '@zoralabs/premint-sdk'; +import type {Address, WalletClient} from 'viem'; + +async function deletePremint(walletClient: WalletClient, collection: Address, uid: number) { + const premintAPI = new PremintAPI(walletClient.chain); + + return await premintAPI.deletePremint({ + walletClient, + uid, + collection + }); +} + +``` diff --git a/packages/premint-sdk/package.json b/packages/premint-sdk/package.json new file mode 100644 index 000000000..4f3104963 --- /dev/null +++ b/packages/premint-sdk/package.json @@ -0,0 +1,30 @@ +{ + "name": "@zoralabs/premint-sdk", + "version": "0.0.2-premint-api.2", + "repository": "https://github.com/ourzora/zora-protocol", + "license": "MIT", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "type": "module", + "scripts": { + "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 && 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": { + "@zoralabs/zora-1155-contracts": "*", + "abitype": "^0.8.7", + "vite": "4.5.0", + "vitest": "0.34.6" + }, + "peerDependencies": { + "viem": "^1.16.6" + }, + "devDependencies": { + "typescript": "^5.2.2", + "vite": "4.5.0", + "vitest": "0.34.6" + } +} diff --git a/packages/premint-sdk/src/generated/premint-api-types.ts b/packages/premint-sdk/src/generated/premint-api-types.ts new file mode 100644 index 000000000..f6ef83fa5 --- /dev/null +++ b/packages/premint-sdk/src/generated/premint-api-types.ts @@ -0,0 +1,363 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + + +export interface paths { + "/signature": { + /** Upsert Premint Signature */ + post: operations["upsert_premint_signature_signature_post"]; + }; + "/signature/{chain_name}/{collection_address}": { + /** Get Premint Signatures For Collection */ + get: operations["get_premint_signatures_for_collection_signature__chain_name___collection_address__get"]; + }; + "/signature/{chain_name}/{collection_address}/next_uid": { + /** Get Next Uid */ + get: operations["get_next_uid_signature__chain_name___collection_address__next_uid_get"]; + }; + "/signature/{chain_name}/{collection_address}/{uid}": { + /** Get Premint Signature */ + get: operations["get_premint_signature_signature__chain_name___collection_address___uid__get"]; + }; + "/created/{chain_name}/{signer}": { + /** Get Premint Signatures By Signer */ + get: operations["get_premint_signatures_by_signer_created__chain_name___signer__get"]; + }; +} + +export type webhooks = Record; + +export interface components { + schemas: { + /** + * ChainName + * @description An enumeration. + * @enum {string} + */ + ChainName: "ETHEREUM-MAINNET" | "ETHEREUM-ROPSTEN" | "ETHEREUM-RINKEBY" | "ETHEREUM-GOERLI" | "ETHEREUM-SEPOLIA" | "OPTIMISM-MAINNET" | "OPTIMISM-GOERLI" | "ZORA-GOERLI" | "ZORA-MAINNET" | "BASE-MAINNET" | "BASE-GOERLI" | "PGN-MAINNET"; + /** + * CollectionCreationConfig + * @description ObjectBase extends Pydantic's BaseModel class to support extra functionality + * (store_as, override_name), as well as provides other convinience methods. + * ObjectBase allows for validation and type enforcement, and should be used inheritedfor any + * complex type we include on a stored entity. + * + * Example:: + * >>> class Foo(ObjectBase): + * ...: found_at_height: int = field(override_name="address", store_as=str) + * ...: other: str = "default" + */ + CollectionCreationConfig: { + /** Contractadmin */ + contractAdmin: string; + /** Contracturi */ + contractURI: string; + /** Contractname */ + contractName: string; + }; + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: components["schemas"]["ValidationError"][]; + }; + /** + * PremintCollection + * @description ObjectBase extends Pydantic's BaseModel class to support extra functionality + * (store_as, override_name), as well as provides other convinience methods. + * ObjectBase allows for validation and type enforcement, and should be used inheritedfor any + * complex type we include on a stored entity. + * + * Example:: + * >>> class Foo(ObjectBase): + * ...: found_at_height: int = field(override_name="address", store_as=str) + * ...: other: str = "default" + */ + PremintCollection: { + /** Contract Address */ + contract_address: string; + /** Contract Admin */ + contract_admin: string; + /** Contract Uri */ + contract_uri: string; + /** Contract Name */ + contract_name: string; + /** Premints */ + premints: components["schemas"]["SignedPremintConfig"][]; + }; + /** + * PremintConfig + * @description ObjectBase extends Pydantic's BaseModel class to support extra functionality + * (store_as, override_name), as well as provides other convinience methods. + * ObjectBase allows for validation and type enforcement, and should be used inheritedfor any + * complex type we include on a stored entity. + * + * Example:: + * >>> class Foo(ObjectBase): + * ...: found_at_height: int = field(override_name="address", store_as=str) + * ...: other: str = "default" + */ + PremintConfig: { + tokenConfig: components["schemas"]["TokenCreationConfig"]; + /** Uid */ + uid: number; + /** Version */ + version: number; + /** Deleted */ + deleted: boolean; + }; + /** + * PremintNextUid + * @description ObjectBase extends Pydantic's BaseModel class to support extra functionality + * (store_as, override_name), as well as provides other convinience methods. + * ObjectBase allows for validation and type enforcement, and should be used inheritedfor any + * complex type we include on a stored entity. + * + * Example:: + * >>> class Foo(ObjectBase): + * ...: found_at_height: int = field(override_name="address", store_as=str) + * ...: other: str = "default" + */ + PremintNextUid: { + /** Next Uid */ + next_uid: number; + }; + /** + * PremintRequest + * @description ObjectBase extends Pydantic's BaseModel class to support extra functionality + * (store_as, override_name), as well as provides other convinience methods. + * ObjectBase allows for validation and type enforcement, and should be used inheritedfor any + * complex type we include on a stored entity. + * + * Example:: + * >>> class Foo(ObjectBase): + * ...: found_at_height: int = field(override_name="address", store_as=str) + * ...: other: str = "default" + */ + PremintRequest: { + collection: components["schemas"]["CollectionCreationConfig"]; + premint: components["schemas"]["PremintConfig"]; + chain_name: components["schemas"]["ChainName"]; + /** Signature */ + signature: string; + }; + /** + * PremintSignature + * @description ObjectBase extends Pydantic's BaseModel class to support extra functionality + * (store_as, override_name), as well as provides other convinience methods. + * ObjectBase allows for validation and type enforcement, and should be used inheritedfor any + * complex type we include on a stored entity. + * + * Example:: + * >>> class Foo(ObjectBase): + * ...: found_at_height: int = field(override_name="address", store_as=str) + * ...: other: str = "default" + */ + PremintSignature: { + collection: components["schemas"]["CollectionCreationConfig"]; + premint: components["schemas"]["PremintConfig"]; + chain_name: components["schemas"]["ChainName"]; + /** Signature */ + signature: string; + /** Collection Address */ + collection_address: string; + /** Signer */ + signer: string; + }; + /** + * SignedPremintConfig + * @description ObjectBase extends Pydantic's BaseModel class to support extra functionality + * (store_as, override_name), as well as provides other convinience methods. + * ObjectBase allows for validation and type enforcement, and should be used inheritedfor any + * complex type we include on a stored entity. + * + * Example:: + * >>> class Foo(ObjectBase): + * ...: found_at_height: int = field(override_name="address", store_as=str) + * ...: other: str = "default" + */ + SignedPremintConfig: { + tokenConfig: components["schemas"]["TokenCreationConfig"]; + /** Uid */ + uid: number; + /** Version */ + version: number; + /** Deleted */ + deleted: boolean; + /** Signature */ + signature: string; + }; + /** + * TokenCreationConfig + * @description ObjectBase extends Pydantic's BaseModel class to support extra functionality + * (store_as, override_name), as well as provides other convinience methods. + * ObjectBase allows for validation and type enforcement, and should be used inheritedfor any + * complex type we include on a stored entity. + * + * Example:: + * >>> class Foo(ObjectBase): + * ...: found_at_height: int = field(override_name="address", store_as=str) + * ...: other: str = "default" + */ + TokenCreationConfig: { + /** Tokenuri */ + tokenURI: string; + /** Maxsupply */ + maxSupply: string; + /** Maxtokensperaddress */ + maxTokensPerAddress: string; + /** Pricepertoken */ + pricePerToken: string; + /** Mintstart */ + mintStart: string; + /** Mintduration */ + mintDuration: string; + /** Royaltymintschedule */ + royaltyMintSchedule: number; + /** Royaltybps */ + royaltyBPS: number; + /** Royaltyrecipient */ + royaltyRecipient: string; + /** Fixedpriceminter */ + fixedPriceMinter: string; + }; + /** ValidationError */ + ValidationError: { + /** Location */ + loc: (string | number)[]; + /** Message */ + msg: string; + /** Error Type */ + type: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} + +export type $defs = Record; + +export type external = Record; + +export interface operations { + + /** Upsert Premint Signature */ + upsert_premint_signature_signature_post: { + requestBody: { + content: { + "application/json": components["schemas"]["PremintRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["PremintRequest"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** Get Premint Signatures For Collection */ + get_premint_signatures_for_collection_signature__chain_name___collection_address__get: { + parameters: { + path: { + chain_name: components["schemas"]["ChainName"]; + collection_address: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["PremintCollection"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** Get Next Uid */ + get_next_uid_signature__chain_name___collection_address__next_uid_get: { + parameters: { + path: { + chain_name: components["schemas"]["ChainName"]; + collection_address: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["PremintNextUid"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** Get Premint Signature */ + get_premint_signature_signature__chain_name___collection_address___uid__get: { + parameters: { + path: { + chain_name: components["schemas"]["ChainName"]; + collection_address: string; + uid: number; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["PremintSignature"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** Get Premint Signatures By Signer */ + get_premint_signatures_by_signer_created__chain_name___signer__get: { + parameters: { + path: { + chain_name: components["schemas"]["ChainName"]; + signer: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["PremintCollection"][]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; +} diff --git a/packages/premint-sdk/src/http-api-base.ts b/packages/premint-sdk/src/http-api-base.ts new file mode 100644 index 000000000..6625a9083 --- /dev/null +++ b/packages/premint-sdk/src/http-api-base.ts @@ -0,0 +1,91 @@ +export class BadResponseError extends Error { + status: number; + json: T; + constructor(message: string, status: number, json: any) { + super(message); + this.name = "BadResponseError"; + this.status = status; + this.json = json; + } +} + +async function wait(delayMs: number) { + return new Promise((resolve) => { + setTimeout(resolve, delayMs); + }); +} + +/** + * A simple fetch() wrapper for HTTP gets. + * Can be overridden as needed. + * + * @param path Path to run HTTP JSON get against + * @returns JSON object response + * @throws Error when HTTP response fails + */ +export const get = async (url: string) => { + const response = await fetch(url, { method: "GET" }); + if (response.status !== 200) { + let json; + try { + json = await response.json(); + } catch (e: any) {} + throw new BadResponseError( + `Invalid response, status ${response.status}`, + response.status, + json + ); + } + return (await response.json()) as T; +}; + +/** + * A simple fetch() wrapper for HTTP post. + * Can be overridden as needed. + * + * @param path Path to run HTTP JSON POST against + * @param data Data to POST to the server, converted to JSON + * @returns JSON object response + * @throws Error when HTTP response fails + */ +export const post = async (url: string, data: any) => { + const response = await fetch(url, { + method: "POST", + headers: { + "content-type": "application/json", + accept: "application/json", + }, + body: JSON.stringify(data), + }); + if (response.status !== 200) { + let json; + try { + json = await response.json(); + } catch (e: any) {} + throw new BadResponseError( + `Bad response: ${response.status}`, + response.status, + json + ); + } + return (await response.json()) as T; +}; + +export const retries = async ( + tryFn: () => T, + maxTries: number = 3, + atTry: number = 1, + linearBackoffMS: number = 200 +): Promise => { + try { + return await tryFn(); + } catch (err: any) { + if (err instanceof BadResponseError) { + if (atTry <= maxTries) { + await wait(atTry * linearBackoffMS); + return await retries(tryFn, maxTries, atTry++); + } + } + throw err; + } +}; diff --git a/packages/premint-sdk/src/index.ts b/packages/premint-sdk/src/index.ts new file mode 100644 index 000000000..ac4355a77 --- /dev/null +++ b/packages/premint-sdk/src/index.ts @@ -0,0 +1,5 @@ +export * from "./premint-client"; + +export * from "./preminter"; + +export * from "./premint-api-client"; diff --git a/packages/premint-sdk/src/premint-api-client.ts b/packages/premint-sdk/src/premint-api-client.ts new file mode 100644 index 000000000..546b4c5ba --- /dev/null +++ b/packages/premint-sdk/src/premint-api-client.ts @@ -0,0 +1,57 @@ +import { post, retries, get } from "./http-api-base"; +import { components, paths } from "./generated/premint-api-types"; + +export const ZORA_API_BASE = "https://api.zora.co/premint/"; + +type SignaturePostType = paths["/signature"]["post"]; +type PremintSignatureRequestBody = + SignaturePostType["requestBody"]["content"]["application/json"]; +export type PremintSignatureResponse = + SignaturePostType["responses"][200]["content"]["application/json"]; + +type PremintNextUIDGetType = + paths["/signature/{chain_name}/{collection_address}/next_uid"]["get"]; +type PremintNextUIDGetPathParameters = + PremintNextUIDGetType["parameters"]["path"]; +export type PremintNextUIDGetResponse = + PremintNextUIDGetType["responses"][200]["content"]["application/json"]; + +type SignaturePremintGetType = + paths["/signature/{chain_name}/{collection_address}/{uid}"]["get"]; +type PremintSignatureGetPathParameters = + SignaturePremintGetType["parameters"]["path"]; +export type PremintSignatureGetResponse = + SignaturePremintGetType["responses"][200]["content"]["application/json"]; + +export type BackendChainNames = components["schemas"]["ChainName"]; + + +const postSignature = async ( + data: PremintSignatureRequestBody +): Promise => { + return retries(() => post("signature", data)); +}; + +const getNextUID = async ( + path: PremintNextUIDGetPathParameters +): Promise => + retries(() => + get( + `${ZORA_API_BASE}signature/${path.chain_name}/${path.collection_address}/next_uid` + ) + ); + +const getSignature = async ( + path: PremintSignatureGetPathParameters +): Promise => + retries(() => + get( + `signature/${path.chain_name}/${path.collection_address}/${path.uid}` + ) + ); + +export const PremintAPIClient = { + postSignature, + getSignature, + getNextUID, +}; diff --git a/packages/premint-sdk/src/premint-client.test.ts b/packages/premint-sdk/src/premint-client.test.ts new file mode 100644 index 000000000..3075e4973 --- /dev/null +++ b/packages/premint-sdk/src/premint-client.test.ts @@ -0,0 +1,241 @@ +import { + createTestClient, + http, + createWalletClient, + createPublicClient, + Address, +} from "viem"; +import { foundry } from "viem/chains"; +import { describe, it, beforeEach, expect, vi, afterEach } from "vitest"; +import { parseEther } from "viem"; +import { BackendChainNamesLookup, PremintClient } from "./premint-client"; + +const chain = foundry; + +const walletClient = createWalletClient({ + chain, + transport: http(), +}); + +const testClient = createTestClient({ + chain, + mode: "anvil", + transport: http(), +}); + +const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +// JSON-RPC Account +const [deployerAccount, secondWallet] = (await walletClient.getAddresses()) as [ + Address, + Address +]; + +describe("ZoraCreator1155Premint", () => { + beforeEach(async () => { + // deploy signature minter contract + await testClient.setBalance({ + address: deployerAccount, + value: parseEther("1"), + }); + + await testClient.setBalance({ + address: secondWallet, + value: parseEther("1"), + }); + }, 20 * 1000); + + afterEach(() => { + testClient.reset(); + }, 4 * 1000); + + it( + "can sign on the forked premint contract", + async () => { + const premintClient = new PremintClient(chain); + + premintClient.apiClient.getNextUID = vi + .fn() + .mockResolvedValue({ next_uid: 3 }); + premintClient.apiClient.postSignature = vi + .fn() + .mockResolvedValue({ ok: true }); + + await premintClient.createPremint({ + walletClient, + publicClient, + account: deployerAccount, + checkSignature: true, + collection: { + contractAdmin: deployerAccount, + contractName: "Testing Contract", + contractURI: + "ipfs://bafkreiainxen4b4wz4ubylvbhons6rembxdet4a262nf2lziclqvv7au3e", + }, + token: { + tokenURI: + "ipfs://bafkreice23maski3x52tsfqgxstx3kbiifnt5jotg3a5ynvve53c4soi2u", + }, + }); + + expect(premintClient.apiClient.postSignature).toHaveBeenCalledWith({ + chain_name: BackendChainNamesLookup.ZORA_GOERLI, + collection: { + contractAdmin: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + contractName: "Testing Contract", + contractURI: + "ipfs://bafkreiainxen4b4wz4ubylvbhons6rembxdet4a262nf2lziclqvv7au3e", + }, + premint: { + deleted: false, + tokenConfig: { + fixedPriceMinter: "0x04E2516A2c207E84a1839755675dfd8eF6302F0a", + maxSupply: "18446744073709551615", + maxTokensPerAddress: "0", + mintDuration: "604800", + mintStart: "0", + pricePerToken: "0", + royaltyBPS: 1000, + royaltyMintSchedule: 0, + royaltyRecipient: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + tokenURI: + "ipfs://bafkreice23maski3x52tsfqgxstx3kbiifnt5jotg3a5ynvve53c4soi2u", + }, + uid: 3, + version: 1, + }, + signature: + "0x588d19641de9ba1dade4d2bb5387c8dc96f4a990fef69787534b60caead759e6334975a6be10a796da948cd7d1d4f5580b3f84d49d9fa4e0b41c97759507975a1c", + }); + }, + 20 * 1000 + ); + + it("can validate premint on network", async () => { + const premintClient = new PremintClient(chain); + + const premintData = { + collection: { + contractAdmin: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + contractName: "Testing Contract", + contractURI: + "ipfs://bafkreiainxen4b4wz4ubylvbhons6rembxdet4a262nf2lziclqvv7au3e", + }, + premint: { + uid: 3, + version: 1, + deleted: false, + tokenConfig: { + maxSupply: "18446744073709551615", + maxTokensPerAddress: "0", + pricePerToken: "0", + mintDuration: "604800", + mintStart: "0", + royaltyMintSchedule: 0, + royaltyBPS: 1000, + fixedPriceMinter: "0x04E2516A2c207E84a1839755675dfd8eF6302F0a", + royaltyRecipient: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + tokenURI: + "ipfs://bafkreice23maski3x52tsfqgxstx3kbiifnt5jotg3a5ynvve53c4soi2u", + }, + }, + chain_name: "ZORA-TESTNET", + signature: + "0x588d19641de9ba1dade4d2bb5387c8dc96f4a990fef69787534b60caead759e6334975a6be10a796da948cd7d1d4f5580b3f84d49d9fa4e0b41c97759507975a1c", + } as const; + const publicClient = createPublicClient({ + chain: foundry, + transport: http(), + }); + 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 premintClient = new PremintClient(chain); + + premintClient.apiClient.getSignature = vi.fn().mockResolvedValue({ + chain_name: "ZORA-TESTNET", + collection: { + contractAdmin: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + contractName: "Testing Contract", + contractURI: + "ipfs://bafkreiainxen4b4wz4ubylvbhons6rembxdet4a262nf2lziclqvv7au3e", + }, + premint: { + deleted: false, + tokenConfig: { + fixedPriceMinter: "0x04E2516A2c207E84a1839755675dfd8eF6302F0a", + maxSupply: "18446744073709551615", + maxTokensPerAddress: "0", + mintDuration: "604800", + mintStart: "0", + pricePerToken: "0", + royaltyBPS: 1000, + royaltyMintSchedule: 0, + royaltyRecipient: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + tokenURI: + "ipfs://bafkreice23maski3x52tsfqgxstx3kbiifnt5jotg3a5ynvve53c4soi2u", + }, + uid: 3, + version: 1, + }, + signature: + "0x588d19641de9ba1dade4d2bb5387c8dc96f4a990fef69787534b60caead759e6334975a6be10a796da948cd7d1d4f5580b3f84d49d9fa4e0b41c97759507975a1c", + }); + premintClient.apiClient.postSignature = vi.fn(); + + const premint = await premintClient.executePremintWithWallet({ + data: await premintClient.getPremintData({ + address: "0xf8dA7f53c283d898818af7FB9d98103F559bDac2", + uid: 3, + }), + account: deployerAccount, + walletClient, + publicClient, + mintArguments: { + quantityToMint: 1, + mintComment: "", + }, + }); + + expect(premint.premintedLog).toEqual({ + contractAddress: "0xf8dA7f53c283d898818af7FB9d98103F559bDac2", + contractConfig: { + contractAdmin: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + contractName: "Testing Contract", + contractURI: + "ipfs://bafkreiainxen4b4wz4ubylvbhons6rembxdet4a262nf2lziclqvv7au3e", + }, + createdNewContract: expect.any(Boolean), + minter: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + quantityMinted: 1n, + tokenConfig: { + fixedPriceMinter: "0x04E2516A2c207E84a1839755675dfd8eF6302F0a", + maxSupply: 18446744073709551615n, + maxTokensPerAddress: 0n, + mintDuration: 604800n, + mintStart: 0n, + pricePerToken: 0n, + royaltyBPS: 1000, + royaltyMintSchedule: 0, + royaltyRecipient: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + tokenURI: + "ipfs://bafkreice23maski3x52tsfqgxstx3kbiifnt5jotg3a5ynvve53c4soi2u", + }, + tokenId: 1n, + uid: 3, + }); + }, + 20 * 1000 + ); +}); diff --git a/packages/premint-sdk/src/premint-client.ts b/packages/premint-sdk/src/premint-client.ts new file mode 100644 index 000000000..f9798df35 --- /dev/null +++ b/packages/premint-sdk/src/premint-client.ts @@ -0,0 +1,673 @@ +import { createPublicClient, decodeEventLog, http, parseEther } from "viem"; +import type { + Account, + Address, + Chain, + Hex, + PublicClient, + TransactionReceipt, + WalletClient, +} from "viem"; +import { + zoraCreator1155PremintExecutorImplABI, + zoraCreator1155PremintExecutorImplAddress, + zoraCreatorFixedPriceSaleStrategyAddress, +} from "@zoralabs/zora-1155-contracts"; +import { foundry, zora, zoraTestnet } from "viem/chains"; +import { PremintConfig, preminterTypedDataDefinition } from "./preminter"; +import type { + BackendChainNames as BackendChainNamesType, + PremintSignatureGetResponse, + PremintSignatureResponse, +} from "./premint-api-client"; +import { PremintAPIClient } from "./premint-api-client"; +import type { DecodeEventLogReturnType } from "viem"; + +export const BackendChainNamesLookup = { + ZORA_MAINNET: "ZORA-MAINNET", + ZORA_GOERLI: "ZORA-GOERLI", +} as const; + +export type NetworkConfig = { + chainId: number; + zoraPathChainName: string; + zoraBackendChainName: BackendChainNamesType; + isTestnet: boolean; +}; + +export const REWARD_PER_TOKEN = parseEther("0.000777"); + +export const networkConfigByChain: Record = { + [zora.id]: { + chainId: zora.id, + isTestnet: false, + zoraPathChainName: "zora", + zoraBackendChainName: BackendChainNamesLookup.ZORA_MAINNET, + }, + [zoraTestnet.id]: { + chainId: zora.id, + isTestnet: true, + zoraPathChainName: "zgor", + zoraBackendChainName: BackendChainNamesLookup.ZORA_GOERLI, + }, + [foundry.id]: { + chainId: foundry.id, + isTestnet: true, + zoraPathChainName: "zgor", + zoraBackendChainName: BackendChainNamesLookup.ZORA_GOERLI, + }, +}; + +type MintArgumentsSettings = { + tokenURI: string; + maxSupply?: bigint; + maxTokensPerAddress?: bigint; + pricePerToken?: bigint; + mintStart?: bigint; + mintDuration?: bigint; + royaltyMintSchedule?: number; + royaltyBPS?: number; + royaltyRecipient?: Address; + fixedPriceMinter?: Address; +}; + +type PremintedLogType = DecodeEventLogReturnType< + typeof zoraCreator1155PremintExecutorImplABI, + "Preminted" +>["args"]; + +type URLSReturnType = { + explorer: null | string; + zoraCollect: null | string; + zoraManage: null | string; +}; + +type SignedPremintResponse = { + urls: URLSReturnType; + uid: number; + verifyingContract: Address; + premint: PremintSignatureResponse; +}; + +type ExecutedPremintResponse = { + receipt: TransactionReceipt; + premintedLog?: PremintedLogType; + urls: URLSReturnType; +}; + +const OPEN_EDITION_MINT_SIZE = BigInt("18446744073709551615"); +export const DefaultMintArguments = { + maxSupply: OPEN_EDITION_MINT_SIZE, + maxTokensPerAddress: 0n, + pricePerToken: 0n, + mintDuration: BigInt(60 * 60 * 24 * 7), // 1 week + mintStart: 0n, + royaltyMintSchedule: 0, + royaltyBPS: 1000, // 10%, +}; + +/** + * 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) { + try { + const decodedLog = decodeEventLog({ + abi: zoraCreator1155PremintExecutorImplABI, + eventName: "Preminted", + ...data, + }); + if (decodedLog.eventName === "Preminted") { + return decodedLog.args; + } + } catch (err: any) {} + } +} + +/** + * 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 + */ +export const convertPremint = ( + premint: PremintSignatureGetResponse["premint"] +) => ({ + ...premint, + tokenConfig: { + ...premint.tokenConfig, + fixedPriceMinter: premint.tokenConfig.fixedPriceMinter as Address, + royaltyRecipient: premint.tokenConfig.royaltyRecipient as Address, + maxSupply: BigInt(premint.tokenConfig.maxSupply), + pricePerToken: BigInt(premint.tokenConfig.pricePerToken), + mintStart: BigInt(premint.tokenConfig.mintStart), + mintDuration: BigInt(premint.tokenConfig.mintDuration), + maxTokensPerAddress: BigInt(premint.tokenConfig.maxTokensPerAddress), + }, +}); + +export const convertCollection = ( + collection: PremintSignatureGetResponse["collection"] +) => ({ + ...collection, + contractAdmin: collection.contractAdmin as Address, +}); + +/** + * 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 + */ +export const encodePremintForAPI = ({ + tokenConfig, + ...premint +}: PremintConfig) => ({ + ...premint, + tokenConfig: { + ...tokenConfig, + maxSupply: tokenConfig.maxSupply.toString(), + pricePerToken: tokenConfig.pricePerToken.toString(), + mintStart: tokenConfig.mintStart.toString(), + mintDuration: tokenConfig.mintDuration.toString(), + maxTokensPerAddress: tokenConfig.maxTokensPerAddress.toString(), + }, +}); + +/** + * Preminter API to access ZORA Premint functionality. + * Currently only supports V1 premints. + */ +export class PremintClient { + network: NetworkConfig; + chain: Chain; + apiClient: typeof PremintAPIClient; + + constructor(chain: Chain, apiClient?: typeof PremintAPIClient) { + this.chain = chain; + if (!apiClient) { + apiClient = PremintAPIClient; + } + this.apiClient = apiClient; + const networkConfig = networkConfigByChain[chain.id]; + if (!networkConfig) { + throw new Error(`Not configured for chain ${chain.id}`); + } + this.network = networkConfig; + } + + /** + * The premint executor address is deployed to the same address across all chains. + * Can be overridden as needed by making a parent class. + * + * @returns Executor address for premints + */ + getExecutorAddress() { + return zoraCreator1155PremintExecutorImplAddress[999]; + } + + /** + * The fixed price minter address is the same across all chains for our current + * deployer strategy. + * Can be overridden as needed by making a parent class. + * + * @returns Fixed price sale strategy + */ + getFixedPriceMinterAddress() { + return zoraCreatorFixedPriceSaleStrategyAddress[999]; + } + + /** + * Getter for public client that instantiates a publicClient as needed + * + * @param publicClient Optional viem public client + * @returns Existing public client or makes a new one for the given chain as needed. + */ + protected getPublicClient(publicClient?: PublicClient): PublicClient { + if (publicClient) { + return publicClient; + } + return createPublicClient({ chain: this.chain, transport: http() }); + } + + /** + * Update existing premint given collection address and UID of existing premint. + * + * 1. Loads existing premint token + * 2. Updates with settings passed into function + * 3. Increments the version field + * 4. Re-signs the premint + * 5. Uploads the premint to the ZORA API + * + * Updates existing premint + * @param settings Settings for the new premint + * @param settings.account Account to sign the premint update from. Taken from walletClient if none passed in. + * @param settings.collection Collection information for the mint + * @param settings.walletClient viem wallet client to use to sign + * @param settings.uid UID + * @param settings.token Mint argument settings, optional settings are overridden with sensible defaults. + * + */ + async updatePremint({ + walletClient, + uid, + collection, + token, + account, + }: { + walletClient: WalletClient; + uid: number; + token: MintArgumentsSettings; + account?: Account | Address; + collection: Address; + }): Promise { + const signatureResponse = await this.apiClient.getSignature({ + chain_name: this.network.zoraBackendChainName, + collection_address: collection.toLowerCase(), + uid: uid, + }); + + const convertedPremint = convertPremint(signatureResponse.premint); + const signerData = { + ...signatureResponse, + premint: { + ...convertedPremint, + tokenConfig: { + ...convertedPremint.tokenConfig, + ...token, + }, + }, + }; + + return await this.signAndSubmitPremint({ + walletClient, + account, + checkSignature: false, + verifyingContract: collection, + publicClient: this.getPublicClient(), + uid: uid, + collection: { + ...signerData.collection, + contractAdmin: signerData.collection.contractAdmin as Address, + }, + premintConfig: signerData.premint, + }); + } + + /** + * Delete premint. + * + * 1. Loads current premint from collection address with UID + * 2. Increments version and marks as deleted + * 3. Signs new premint version + * 4. Sends to ZORA Premint API + * + * Deletes existing premint + * @param settings.collection collection address + * @param settings.uid UID + * @param settings.walletClient viem wallet client to use to sign + * + */ + async deletePremint({ + walletClient, + uid, + account, + collection, + publicClient, + }: { + walletClient: WalletClient; + publicClient: PublicClient; + uid: number; + account?: Account | Address; + collection: Address; + }) { + const signatureResponse = await this.apiClient.getSignature({ + chain_name: this.network.zoraBackendChainName, + collection_address: collection.toLowerCase(), + uid: uid, + }); + + const signerData = { + ...signatureResponse, + collection: convertCollection(signatureResponse.collection), + premint: { + ...convertPremint(signatureResponse.premint), + deleted: true, + }, + }; + + return await this.signAndSubmitPremint({ + walletClient, + account, + checkSignature: false, + verifyingContract: collection, + publicClient: this.getPublicClient(publicClient), + uid: uid, + collection: signerData.collection, + premintConfig: signerData.premint, + }); + } + + /** + * Internal function to sign and submit a premint request. + * + * @param premintArguments Arguments to premint + * @returns + */ + private async signAndSubmitPremint({ + walletClient, + publicClient, + verifyingContract, + premintConfig, + uid, + account, + checkSignature, + collection, + }: { + publicClient: PublicClient; + uid: number; + walletClient: WalletClient; + verifyingContract: Address; + checkSignature: boolean; + account?: Address | Account; + premintConfig: PremintConfig; + collection: PremintSignatureGetResponse["collection"]; + }) { + if (!account) { + account = walletClient.account; + } + if (!account) { + throw new Error("No account provided"); + } + + const signature = await walletClient.signTypedData({ + account, + ...preminterTypedDataDefinition({ + verifyingContract, + premintConfig, + chainId: this.chain.id, + }), + }); + + if (checkSignature) { + const [isValidSignature] = await publicClient.readContract({ + abi: zoraCreator1155PremintExecutorImplABI, + address: this.getExecutorAddress(), + functionName: "isValidSignature", + args: [convertCollection(collection), premintConfig, signature], + }); + if (!isValidSignature) { + throw new Error("Invalid signature"); + } + } + + const apiData = { + collection, + premint: encodePremintForAPI(premintConfig), + chain_name: this.network.zoraBackendChainName, + signature: signature, + }; + + const premint = await this.apiClient.postSignature(apiData); + + return { + urls: this.makeUrls({ address: verifyingContract, uid }), + uid, + verifyingContract, + premint, + }; + } + + /** + * Create premint + * + * @param settings Settings for the new premint + * @param settings.account Account to sign the premint with. Taken from walletClient if none passed in. + * @param settings.collection Collection information for the mint + * @param settings.token Mint argument settings, optional settings are overridden with sensible defaults. + * @param settings.publicClient Public client (optional) – instantiated if not passed in with defaults. + * @param settings.walletClient Required wallet client for signing the premint message. + * @param settings.executionSettings Execution settings for premint options + * @param settings.executionSettings.deleted If this UID should be deleted. If omitted, set to false. + * @param settings.executionSettings.uid the UID to use – optional and retrieved as a fresh UID from ZORA by default. + * @param settings.checkSignature if the signature should have a pre-flight check. Not required but helpful for debugging. + * @returns premint url, uid, newContractAddress, and premint object + */ + async createPremint({ + account, + collection, + token, + publicClient, + walletClient, + executionSettings, + checkSignature = false, + }: { + account: Address; + checkSignature?: boolean; + walletClient: WalletClient; + collection: PremintSignatureGetResponse["collection"]; + token: MintArgumentsSettings; + publicClient?: PublicClient; + executionSettings?: { + deleted?: boolean; + uid?: number; + }; + }) { + publicClient = this.getPublicClient(publicClient); + + const newContractAddress = await publicClient.readContract({ + address: this.getExecutorAddress(), + abi: zoraCreator1155PremintExecutorImplABI, + functionName: "getContractAddress", + args: [convertCollection(collection)], + }); + + const tokenConfig = { + ...DefaultMintArguments, + fixedPriceMinter: this.getFixedPriceMinterAddress(), + royaltyRecipient: account, + ...token, + }; + + let uid = executionSettings?.uid; + if (!uid) { + const uidResponse = await this.apiClient.getNextUID({ + chain_name: this.network.zoraBackendChainName, + collection_address: newContractAddress.toLowerCase(), + }); + uid = uidResponse.next_uid; + } + + if (!uid) { + throw new Error("UID is missing but required"); + } + + let deleted = executionSettings?.deleted || false; + + const premintConfig = { + tokenConfig: tokenConfig, + uid, + version: 1, + deleted, + }; + + return await this.signAndSubmitPremint({ + uid, + verifyingContract: newContractAddress, + premintConfig, + checkSignature, + account, + publicClient, + walletClient, + collection, + }); + } + + /** + * Fetches given premint data from the ZORA API. + * + * @param address Address for the premint contract + * @param uid UID for the desired premint + * @returns PremintSignatureGetResponse of premint data from the API + */ + async getPremintData({ + address, + uid, + }: { + address: string; + uid: number; + }): Promise { + return await this.apiClient.getSignature({ + chain_name: this.network.zoraBackendChainName, + collection_address: address, + uid, + }); + } + + /** + * Check user signature for v1 + * + * @param data Signature data from the API + * @returns isValid = signature is valid or not, contractAddress = assumed contract address, recoveredSigner = signer from contract + */ + async isValidSignature({ + data, + publicClient, + }: { + data: PremintSignatureGetResponse; + publicClient?: PublicClient; + }): Promise<{ + isValid: boolean; + contractAddress: Address; + recoveredSigner: Address; + }> { + publicClient = this.getPublicClient(publicClient); + + const [isValid, contractAddress, recoveredSigner] = + await publicClient.readContract({ + abi: zoraCreator1155PremintExecutorImplABI, + address: this.getExecutorAddress(), + functionName: "isValidSignature", + args: [ + convertCollection(data.collection), + convertPremint(data.premint), + data.signature as Hex, + ], + }); + + return { isValid, contractAddress, recoveredSigner }; + } + + protected makeUrls({ + uid, + address, + tokenId, + }: { + uid?: number; + tokenId?: bigint; + address?: Address; + }): URLSReturnType { + if ((!uid || !tokenId) && !address) { + return { explorer: null, zoraCollect: null, zoraManage: null }; + } + + const zoraTokenPath = uid ? `premint-${uid}` : tokenId; + + return { + explorer: tokenId + ? `https://${this.chain.blockExplorers?.default.url}/token/${address}/instance/${tokenId}` + : null, + zoraCollect: `https://${ + this.network.isTestnet ? "testnet." : "" + }zora.co/collect/${ + this.network.zoraPathChainName + }:${address}/${zoraTokenPath}`, + zoraManage: `https://${ + this.network.isTestnet ? "testnet." : "" + }zora.co/collect/${ + this.network.zoraPathChainName + }:${address}/${zoraTokenPath}`, + }; + } + + /** + * Execute premint on-chain + * + * @param settings.data Data from the API for the mint + * @param settings.account Optional account (if omitted taken from wallet client) for the account executing the premint. + * @param settings.walletClient WalletClient to send execution from. + * @param settings.mintArguments User minting arguments. + * @param settings.mintArguments.quantityToMint Quantity to mint, optional, defaults to 1. + * @param settings.mintArguments.mintComment Optional mint comment, optional, omits when not included. + * @param settings.publicClient Optional public client for preflight checks. + * @returns receipt, log, zoraURL + */ + async executePremintWithWallet({ + data, + account, + walletClient, + mintArguments, + publicClient, + }: { + data: PremintSignatureGetResponse; + walletClient: WalletClient; + account?: Account | Address; + mintArguments?: { + quantityToMint: number; + mintComment?: string; + }; + publicClient?: PublicClient; + }): Promise { + publicClient = this.getPublicClient(publicClient); + + if (mintArguments && mintArguments?.quantityToMint < 1) { + throw new Error("Quantity to mint cannot be below 1"); + } + + const targetAddress = this.getExecutorAddress(); + const numberToMint = BigInt(mintArguments?.quantityToMint || 1); + const args = [ + convertCollection(data.collection), + convertPremint(data.premint), + data.signature as Hex, + numberToMint, + mintArguments?.mintComment || "", + ] as const; + + if (!account) { + account = walletClient.account; + } + + if (!account) { + throw new Error("Wallet not passed in"); + } + + const value = numberToMint * REWARD_PER_TOKEN; + + const { request } = await publicClient.simulateContract({ + account, + abi: zoraCreator1155PremintExecutorImplABI, + functionName: "premint", + value, + address: targetAddress, + args, + }); + const hash = await walletClient.writeContract(request); + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + const premintedLog = getPremintedLogFromReceipt(receipt); + + return { + receipt, + premintedLog, + urls: this.makeUrls({ + address: premintedLog?.contractAddress, + tokenId: premintedLog?.tokenId, + }), + }; + } +} diff --git a/packages/1155-contracts/package/preminter.test.ts b/packages/premint-sdk/src/preminter.test.ts similarity index 98% rename from packages/1155-contracts/package/preminter.test.ts rename to packages/premint-sdk/src/preminter.test.ts index 79c94b6d8..bb2c1dd2f 100644 --- a/packages/1155-contracts/package/preminter.test.ts +++ b/packages/premint-sdk/src/preminter.test.ts @@ -10,7 +10,7 @@ import { hashDomain, } from "viem"; import { foundry, zora } from "viem/chains"; -import { describe, it, beforeEach, expect } from "vitest"; +import { describe, it, beforeEach, expect, afterEach } from "vitest"; import { parseEther } from "viem"; import { zoraCreator1155PremintExecutorImplABI as preminterAbi, @@ -18,7 +18,7 @@ import { zoraCreator1155ImplABI, zoraCreator1155FactoryImplAddress, zoraCreator1155FactoryImplConfig, -} from "./wagmiGenerated"; +} from "@zoralabs/zora-1155-contracts"; import { ContractCreationConfig, @@ -74,7 +74,7 @@ const defaultContractConfig = ({ }); const defaultTokenConfig = ( - fixedPriceMinterAddress: Address + fixedPriceMinterAddress: Address, ): TokenCreationConfig => ({ tokenURI: "ipfs://tokenIpfsId0", maxSupply: 100n, @@ -117,6 +117,10 @@ describe("ZoraCreator1155Preminter", () => { zoraCreator1155PremintExecutorAddress[ctx.forkedChainId]; }, 20 * 1000); + afterEach(() => { + testClient.reset(); + }, 4 * 1000); + // skip for now - we need to make this work on zora testnet chain too it( "can sign on the forked premint contract", @@ -154,7 +158,7 @@ describe("ZoraCreator1155Preminter", () => { contractAddress, }); }, - 20 * 1000 + 20 * 1000, ); it( "can sign and recover a signature", @@ -197,7 +201,7 @@ describe("ZoraCreator1155Preminter", () => { expect(recoveredAddress).to.equal(creatorAccount); }, - 20 * 1000 + 20 * 1000, ); it( "can sign and mint multiple tokens", @@ -352,7 +356,7 @@ describe("ZoraCreator1155Preminter", () => { expect( (await publicClient.waitForTransactionReceipt({ hash: mintHash2 })) - .status + .status, ).toBe("success"); // now premint status for the second mint, it should be minted @@ -376,7 +380,7 @@ describe("ZoraCreator1155Preminter", () => { expect(tokenBalance2).toBe(quantityToMint2); }, // 10 second timeout - 40 * 1000 + 40 * 1000, ); it("can decode the CreatorAttribution event", async ({ diff --git a/packages/1155-contracts/package/preminter.ts b/packages/premint-sdk/src/preminter.ts similarity index 97% rename from packages/1155-contracts/package/preminter.ts rename to packages/premint-sdk/src/preminter.ts index 488fda2a4..beeea8ebb 100644 --- a/packages/1155-contracts/package/preminter.ts +++ b/packages/premint-sdk/src/preminter.ts @@ -1,6 +1,6 @@ import { Address } from "abitype"; import { ExtractAbiFunction, AbiParametersToPrimitiveTypes } from "abitype"; -import { zoraCreator1155PremintExecutorImplABI as preminterAbi } from "./wagmiGenerated"; +import { zoraCreator1155PremintExecutorImplABI as preminterAbi } from "@zoralabs/zora-1155-contracts/package/wagmiGenerated"; import { TypedDataDefinition } from "viem"; type PremintInputs = ExtractAbiFunction< diff --git a/packages/premint-sdk/tsconfig.json b/packages/premint-sdk/tsconfig.json new file mode 100644 index 000000000..3da7aa253 --- /dev/null +++ b/packages/premint-sdk/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "allowJs": true, + "baseUrl": ".", + "downlevelIteration": true, + "esModuleInterop": true, + "isolatedModules": true, + "lib": ["es2021", "DOM"], + "module": "esnext", + "moduleResolution": "node", + "noImplicitAny": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "strictNullChecks": true, + "target": "es2021", + "types": ["node"], + "outDir": "dist" + }, + "exclude": ["node_modules/**", "dist/**"], + "include": ["src/**/*.ts"] +} diff --git a/packages/premint-sdk/tsup.config.js b/packages/premint-sdk/tsup.config.js new file mode 100644 index 000000000..f47dfbef8 --- /dev/null +++ b/packages/premint-sdk/tsup.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + sourcemap: true, + clean: true, + dts: false, + format: ["cjs", "esm"], + onSuccess: "tsc --emitDeclarationOnly --declaration --declarationMap", +}); diff --git a/yarn.lock b/yarn.lock index 81dc7ae64..edd286f4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -524,6 +524,13 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jridgewell/gen-mapping@^0.3.2": version "0.3.3" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" @@ -643,6 +650,11 @@ "@noble/hashes" "~1.3.0" "@scure/base" "~1.1.0" +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@solidity-parser/parser@^0.16.0": version "0.16.1" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.1.tgz#f7c8a686974e1536da0105466c4db6727311253c" @@ -685,11 +697,16 @@ dependencies: "@types/chai" "*" -"@types/chai@*", "@types/chai@^4.3.4": +"@types/chai@*": version "4.3.6" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.6.tgz#7b489e8baf393d5dd1266fb203ddd4ea941259e6" integrity sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw== +"@types/chai@^4.3.5": + version "4.3.9" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.9.tgz#144d762491967db8c6dea38e03d2206c2623feec" + integrity sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg== + "@types/is-ci@^3.0.0": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/is-ci/-/is-ci-3.0.1.tgz#802da8f2376d9bf5c20ac17c9f869aed2b532297" @@ -746,49 +763,48 @@ dependencies: "@types/node" "*" -"@vitest/expect@0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.30.1.tgz#3c92a3fc23a198315ce8cd16689dc2d5aeac40b8" - integrity sha512-c3kbEtN8XXJSeN81iDGq29bUzSjQhjES2WR3aColsS4lPGbivwLtas4DNUe0jD9gg/FYGIteqOenfU95EFituw== +"@vitest/expect@0.34.6": + version "0.34.6" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.34.6.tgz#608a7b7a9aa3de0919db99b4cc087340a03ea77e" + integrity sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw== dependencies: - "@vitest/spy" "0.30.1" - "@vitest/utils" "0.30.1" - chai "^4.3.7" + "@vitest/spy" "0.34.6" + "@vitest/utils" "0.34.6" + chai "^4.3.10" -"@vitest/runner@0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.30.1.tgz#534db590091e5d40682f47b9478f64b776073c50" - integrity sha512-W62kT/8i0TF1UBCNMRtRMOBWJKRnNyv9RrjIgdUryEe0wNpGZvvwPDLuzYdxvgSckzjp54DSpv1xUbv4BQ0qVA== +"@vitest/runner@0.34.6": + version "0.34.6" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.34.6.tgz#6f43ca241fc96b2edf230db58bcde5b974b8dcaf" + integrity sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ== dependencies: - "@vitest/utils" "0.30.1" - concordance "^5.0.4" + "@vitest/utils" "0.34.6" p-limit "^4.0.0" - pathe "^1.1.0" + pathe "^1.1.1" -"@vitest/snapshot@0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-0.30.1.tgz#25e912557b357ecb89d5ee35e8d7c4c7a5ecfe32" - integrity sha512-fJZqKrE99zo27uoZA/azgWyWbFvM1rw2APS05yB0JaLwUIg9aUtvvnBf4q7JWhEcAHmSwbrxKFgyBUga6tq9Tw== +"@vitest/snapshot@0.34.6": + version "0.34.6" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-0.34.6.tgz#b4528cf683b60a3e8071cacbcb97d18b9d5e1d8b" + integrity sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w== dependencies: - magic-string "^0.30.0" - pathe "^1.1.0" - pretty-format "^27.5.1" + magic-string "^0.30.1" + pathe "^1.1.1" + pretty-format "^29.5.0" -"@vitest/spy@0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.30.1.tgz#e3344d4513407afd922963737fb9733a7787a2bf" - integrity sha512-YfJeIf37GvTZe04ZKxzJfnNNuNSmTEGnla2OdL60C8od16f3zOfv9q9K0nNii0NfjDJRt/CVN/POuY5/zTS+BA== +"@vitest/spy@0.34.6": + version "0.34.6" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.34.6.tgz#b5e8642a84aad12896c915bce9b3cc8cdaf821df" + integrity sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ== dependencies: - tinyspy "^2.1.0" + tinyspy "^2.1.1" -"@vitest/utils@0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.30.1.tgz#0e5bf8c1b81a6dfa2b70120c2aa092a651440cda" - integrity sha512-/c8Xv2zUVc+rnNt84QF0Y0zkfxnaGhp87K2dYJMLtLOIckPzuxLVzAtFCicGFdB4NeBHNzTRr1tNn7rCtQcWFA== +"@vitest/utils@0.34.6": + version "0.34.6" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.34.6.tgz#38a0a7eedddb8e7291af09a2409cb8a189516968" + integrity sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A== dependencies: - concordance "^5.0.4" + diff-sequences "^29.4.3" loupe "^2.3.6" - pretty-format "^27.5.1" + pretty-format "^29.5.0" "@wagmi/cli@^1.0.1": version "1.5.2" @@ -855,7 +871,7 @@ acorn-walk@^8.2.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^8.10.0, acorn@^8.8.2, acorn@^8.9.0: +acorn@^8.10.0, acorn@^8.9.0: version "8.10.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== @@ -1021,11 +1037,6 @@ bl@^5.0.0: inherits "^2.0.4" readable-stream "^3.4.0" -blueimp-md5@^2.10.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0" - integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w== - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1131,7 +1142,7 @@ capital-case@^1.0.4: tslib "^2.0.3" upper-case-first "^2.0.2" -chai@^4.3.7: +chai@^4.3.10: version "4.3.10" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.10.tgz#d784cec635e3b7e2ffb66446a63b4e33bd390384" integrity sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g== @@ -1285,20 +1296,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concordance@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/concordance/-/concordance-5.0.4.tgz#9896073261adced72f88d60e4d56f8efc4bbbbd2" - integrity sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw== - dependencies: - date-time "^3.1.0" - esutils "^2.0.3" - fast-diff "^1.2.0" - js-string-escape "^1.0.1" - lodash "^4.17.15" - md5-hex "^3.0.1" - semver "^7.3.2" - well-known-symbols "^2.0.0" - constant-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1" @@ -1363,13 +1360,6 @@ data-uri-to-buffer@^4.0.0: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== -date-time@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/date-time/-/date-time-3.1.0.tgz#0d1e934d170579f481ed8df1e2b8ff70ee845e1e" - integrity sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg== - dependencies: - time-zone "^1.0.0" - debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -1444,6 +1434,11 @@ detect-package-manager@^2.0.1: dependencies: execa "^5.1.1" +diff-sequences@^29.4.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -1745,7 +1740,7 @@ estraverse@^5.1.0, estraverse@^5.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== -esutils@^2.0.2, esutils@^2.0.3: +esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== @@ -1804,11 +1799,6 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-diff@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" - integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== - fast-glob@^3.2.9, fast-glob@^3.3.0: version "3.3.1" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" @@ -2468,11 +2458,6 @@ isomorphic-ws@5.0.0: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== -isows@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.3.tgz#93c1cf0575daf56e7120bab5c8c448b0809d0d74" - integrity sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg== - jackspeak@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" @@ -2487,11 +2472,6 @@ joycon@^3.0.1: resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== -js-string-escape@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" - integrity sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg== - js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -2644,11 +2624,6 @@ lodash.startcase@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.startcase/-/lodash.startcase-4.4.0.tgz#9436e34ed26093ed7ffae1936144350915d9add8" integrity sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg== -lodash@^4.17.15: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - log-symbols@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-5.1.0.tgz#a20e3b9a5f53fac6aeb8e2bb22c07cf2c8f16d93" @@ -2691,10 +2666,10 @@ lru-cache@^6.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== -magic-string@^0.30.0: - version "0.30.4" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.4.tgz#c2c683265fc18dda49b56fc7318d33ca0332c98c" - integrity sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg== +magic-string@^0.30.1: + version "0.30.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9" + integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA== dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" @@ -2708,13 +2683,6 @@ map-obj@^4.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== -md5-hex@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-3.0.1.tgz#be3741b510591434b2784d79e556eefc2c9a8e5c" - integrity sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw== - dependencies: - blueimp-md5 "^2.10.0" - meow@^6.0.0: version "6.1.1" resolved "https://registry.yarnpkg.com/meow/-/meow-6.1.1.tgz#1ad64c4b76b2a24dfb2f635fddcadf320d251467" @@ -2798,7 +2766,7 @@ mixme@^0.5.1: resolved "https://registry.yarnpkg.com/mixme/-/mixme-0.5.9.tgz#a5a58e17354632179ff3ce5b0fc130899c8ba81c" integrity sha512-VC5fg6ySUscaWUpI4gxCBTQMH2RdUpNrk+MsbpCYtIvf9SBJdiUey4qE7BXviJsJR4nDQxCZ+3yaYNW3guz/Pw== -mlly@^1.2.0: +mlly@^1.2.0, mlly@^1.4.0: version "1.4.2" resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.2.tgz#7cf406aa319ff6563d25da6b36610a93f2a8007e" integrity sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg== @@ -3213,14 +3181,14 @@ prettier@^3.0.3: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== -pretty-format@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" - integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== +pretty-format@^29.5.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== dependencies: - ansi-regex "^5.0.1" + "@jest/schemas" "^29.6.3" ansi-styles "^5.0.0" - react-is "^17.0.1" + react-is "^18.0.0" pseudomap@^1.0.2: version "1.0.2" @@ -3242,10 +3210,10 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== -react-is@^17.0.1: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" - integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== read-pkg-up@^7.0.1: version "7.0.1" @@ -3416,7 +3384,7 @@ safe-regex-test@^1.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^7.3.2, semver@^7.3.8, semver@^7.5.3: +semver@^7.3.8, semver@^7.5.3: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -3559,7 +3527,7 @@ source-map@0.8.0-beta.0: dependencies: whatwg-url "^7.0.0" -source-map@^0.6.0, source-map@^0.6.1: +source-map@^0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -3608,7 +3576,7 @@ stackback@0.0.2: resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== -std-env@^3.3.2: +std-env@^3.3.3: version "3.4.3" resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.4.3.tgz#326f11db518db751c83fd58574f449b7c3060910" integrity sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q== @@ -3788,25 +3756,20 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" -time-zone@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/time-zone/-/time-zone-1.0.0.tgz#99c5bf55958966af6d06d83bdf3800dc82faec5d" - integrity sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA== - -tinybench@^2.4.0: +tinybench@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.5.1.tgz#3408f6552125e53a5a48adee31261686fd71587e" integrity sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg== -tinypool@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.4.0.tgz#3cf3ebd066717f9f837e8d7d31af3c127fdb5446" - integrity sha512-2ksntHOKf893wSAH4z/+JbPpi92esw8Gn9N2deXX+B0EO92hexAVI9GIZZPx7P5aYo5KULfeOSt3kMOmSOy6uA== +tinypool@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.7.0.tgz#88053cc99b4a594382af23190c609d93fddf8021" + integrity sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww== -tinyspy@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.1.1.tgz#9e6371b00c259e5c5b301917ca18c01d40ae558c" - integrity sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w== +tinyspy@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.0.tgz#9dc04b072746520b432f77ea2c2d17933de5d6ce" + integrity sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg== tmp@^0.0.33: version "0.0.33" @@ -4026,7 +3989,7 @@ typescript@5.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== -typescript@^5.0.4: +typescript@^5.0.4, typescript@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== @@ -4110,36 +4073,22 @@ viem@^1.0.0: isomorphic-ws "5.0.0" ws "8.13.0" -viem@^1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/viem/-/viem-1.16.2.tgz#7e9719dd19e7464284b94d9c00f94f86f5858ccd" - integrity sha512-ZQ8kemNvRVwucwcsj4/SjKohK+wZv9Vxx/gXAlwqGMCW7r+niOeECtFku/1L7UPTmPgdmq4kic9R71t6XQDmGw== - dependencies: - "@adraffy/ens-normalize" "1.9.4" - "@noble/curves" "1.2.0" - "@noble/hashes" "1.3.2" - "@scure/bip32" "1.3.2" - "@scure/bip39" "1.2.1" - abitype "0.9.8" - isows "1.0.3" - ws "8.13.0" - -vite-node@0.30.1: - version "0.30.1" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.30.1.tgz#ab0ed1553019c7d81ac95529c57ab8ac9e82347d" - integrity sha512-vTikpU/J7e6LU/8iM3dzBo8ZhEiKZEKRznEMm+mJh95XhWaPrJQraT/QsT2NWmuEf+zgAoMe64PKT7hfZ1Njmg== +vite-node@0.34.6: + version "0.34.6" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.34.6.tgz#34d19795de1498562bf21541a58edcd106328a17" + integrity sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA== dependencies: cac "^6.7.14" debug "^4.3.4" - mlly "^1.2.0" - pathe "^1.1.0" + mlly "^1.4.0" + pathe "^1.1.1" picocolors "^1.0.0" - vite "^3.0.0 || ^4.0.0" + vite "^3.0.0 || ^4.0.0 || ^5.0.0-0" -"vite@^3.0.0 || ^4.0.0", vite@^4.1.4: - version "4.4.9" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.9.tgz#1402423f1a2f8d66fd8d15e351127c7236d29d3d" - integrity sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA== +vite@4.5.0, "vite@^3.0.0 || ^4.0.0 || ^5.0.0-0", "vite@^3.1.0 || ^4.0.0 || ^5.0.0-0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.0.tgz#ec406295b4167ac3bc23e26f9c8ff559287cff26" + integrity sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw== dependencies: esbuild "^0.18.10" postcss "^8.4.27" @@ -4147,36 +4096,34 @@ vite-node@0.30.1: optionalDependencies: fsevents "~2.3.2" -vitest@~0.30.1: - version "0.30.1" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.30.1.tgz#351d4a2f27aa8cc0245e3583e3ed45e30efc71d6" - integrity sha512-y35WTrSTlTxfMLttgQk4rHcaDkbHQwDP++SNwPb+7H8yb13Q3cu2EixrtHzF27iZ8v0XCciSsLg00RkPAzB/aA== +vitest@0.34.6: + version "0.34.6" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.34.6.tgz#44880feeeef493c04b7f795ed268f24a543250d7" + integrity sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q== dependencies: - "@types/chai" "^4.3.4" + "@types/chai" "^4.3.5" "@types/chai-subset" "^1.3.3" "@types/node" "*" - "@vitest/expect" "0.30.1" - "@vitest/runner" "0.30.1" - "@vitest/snapshot" "0.30.1" - "@vitest/spy" "0.30.1" - "@vitest/utils" "0.30.1" - acorn "^8.8.2" + "@vitest/expect" "0.34.6" + "@vitest/runner" "0.34.6" + "@vitest/snapshot" "0.34.6" + "@vitest/spy" "0.34.6" + "@vitest/utils" "0.34.6" + acorn "^8.9.0" acorn-walk "^8.2.0" cac "^6.7.14" - chai "^4.3.7" - concordance "^5.0.4" + chai "^4.3.10" debug "^4.3.4" local-pkg "^0.4.3" - magic-string "^0.30.0" - pathe "^1.1.0" + magic-string "^0.30.1" + pathe "^1.1.1" picocolors "^1.0.0" - source-map "^0.6.1" - std-env "^3.3.2" + std-env "^3.3.3" strip-literal "^1.0.1" - tinybench "^2.4.0" - tinypool "^0.4.0" - vite "^3.0.0 || ^4.0.0" - vite-node "0.30.1" + tinybench "^2.5.0" + tinypool "^0.7.0" + vite "^3.1.0 || ^4.0.0 || ^5.0.0-0" + vite-node "0.34.6" why-is-node-running "^2.2.2" wcwidth@^1.0.1: @@ -4201,11 +4148,6 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -well-known-symbols@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/well-known-symbols/-/well-known-symbols-2.0.0.tgz#e9c7c07dbd132b7b84212c8174391ec1f9871ba5" - integrity sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q== - whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"