Skip to content

Commit

Permalink
Update client side auth changes
Browse files Browse the repository at this point in the history
  • Loading branch information
oveddan committed Nov 29, 2023
1 parent ba73d27 commit 702ad19
Show file tree
Hide file tree
Showing 6 changed files with 723 additions and 238 deletions.
18 changes: 18 additions & 0 deletions .changeset/rare-wolves-cheat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"@zoralabs/protocol-sdk": patch
---

### Changes to `preminter`

* `isValidSignature` now takes either v1 or v2 of a premint config, along with the premint config version. and both recovers the signer address and validates if the signer can create a premint on the given contract.

### Changes to PremintClient

sdk now supports creating and signing premints for v2 of Premint Config:

* `preminter.isValidSignature`
* new function `preminter.supportsPremintVersion` which checks if a given token contract supports a given premint config version
* new function `preminter.recoverCreatorFromCreatorAttribution` which recovers the creator address from a `CreatorAttribution` event
* `preminter.premintTypedDataDefinition` now takes a premint config version, and returns the correct typed data definition for that version

* premint client methods now work with both v1 and v2 of the premint config, and takes an additional premint config version parameter
11 changes: 7 additions & 4 deletions packages/protocol-sdk/src/anvil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ async function waitForAnvilInit(anvil: any) {
});
}

export type AnvilTestForkSettings = {
forkUrl: string;
forkBlockNumber: number;
};

export const makeAnvilTest = ({
forkUrl,
forkBlockNumber,
}: {
forkUrl: string;
forkBlockNumber: number;
}) =>
}: AnvilTestForkSettings) =>
test.extend<AnvilViemClientsTest>({
viemClients: async ({ task }, use) => {
console.log("setting up clients for ", task.name);
Expand Down Expand Up @@ -93,6 +95,7 @@ export const makeAnvilTest = ({
export const forkUrls = {
zoraMainnet: "https://rpc.zora.co/",
zoraGoerli: "https://testnet.rpc.zora.co",
zoraSepoli: "https://sepolia.rpc.zora.energy",
};

export const anvilTest = makeAnvilTest({
Expand Down
17 changes: 9 additions & 8 deletions packages/protocol-sdk/src/premint/premint-client.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { foundry } from "viem/chains";
import { describe, expect, vi } from "vitest";
import { createPremintClient } from "./premint-client";
import { anvilTest } from "src/anvil";
import { anvilTest, forkUrls, makeAnvilTest } from "src/anvil";
import { PremintSignatureResponse } from "./premint-api-client";

describe("ZoraCreator1155Premint", () => {
anvilTest(
makeAnvilTest({
forkUrl: forkUrls.zoraGoerli,
forkBlockNumber: 1763437,
})(
"can sign on the forked premint contract",
async ({ viemClients: { walletClient, publicClient } }) => {
const [deployerAccount] = await walletClient.getAddresses();
Expand Down Expand Up @@ -76,7 +80,7 @@ describe("ZoraCreator1155Premint", () => {
publicClient,
});

const premintData = {
const premintData: PremintSignatureResponse = {
collection: {
contractAdmin: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
contractName: "Testing Contract",
Expand All @@ -101,15 +105,12 @@ describe("ZoraCreator1155Premint", () => {
"ipfs://bafkreice23maski3x52tsfqgxstx3kbiifnt5jotg3a5ynvve53c4soi2u",
},
},
chain_name: "ZORA-TESTNET",
chain_name: "ZORA-GOERLI",
signature:
"0x588d19641de9ba1dade4d2bb5387c8dc96f4a990fef69787534b60caead759e6334975a6be10a796da948cd7d1d4f5580b3f84d49d9fa4e0b41c97759507975a1c",
} as const;

const signatureValid = await premintClient.isValidSignature({
// @ts-ignore: Fix enum type
data: premintData,
});
const signatureValid = await premintClient.isValidSignature(premintData);
expect(signatureValid.isValid).toBe(true);
},
);
Expand Down
138 changes: 92 additions & 46 deletions packages/protocol-sdk/src/premint/premint-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,17 @@ import {
zoraCreator1155PremintExecutorImplAddress,
zoraCreatorFixedPriceSaleStrategyAddress,
} from "@zoralabs/protocol-deployments";
import { PremintConfig, preminterTypedDataDefinition } from "./preminter";
import {
PremintConfigAndVersion,
PremintConfigV1,
PremintConfigV2,
PremintConfigVersion,
getPremintCollectionAddress,
premintTypedDataDefinition,
ContractCreationConfig,
recoverAndValidateSignature,
isAuthorizedToCreatePremint,
} from "./preminter";
import type {
PremintSignatureGetResponse,
PremintSignatureResponse,
Expand Down Expand Up @@ -96,7 +106,7 @@ export function getPremintedLogFromReceipt(
* @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 = (
export const convertPremintV1 = (
premint: PremintSignatureGetResponse["premint"],
) => ({
...premint,
Expand Down Expand Up @@ -125,10 +135,25 @@ export const convertCollection = (
* @param premint Premint object from viem to convert to a JSON compatible type.
* @returns JSON compatible premint
*/
export const encodePremintForAPI = ({
export const encodePremintV1ForAPI = ({
tokenConfig,
...premint
}: PremintConfigV1) => ({
...premint,
tokenConfig: {
...tokenConfig,
maxSupply: tokenConfig.maxSupply.toString(),
pricePerToken: tokenConfig.pricePerToken.toString(),
mintStart: tokenConfig.mintStart.toString(),
mintDuration: tokenConfig.mintDuration.toString(),
maxTokensPerAddress: tokenConfig.maxTokensPerAddress.toString(),
},
});

export const encodePremintV2ForAPI = ({
tokenConfig,
...premint
}: PremintConfig) => ({
}: PremintConfigV2) => ({
...premint,
tokenConfig: {
...tokenConfig,
Expand All @@ -140,6 +165,19 @@ export const encodePremintForAPI = ({
},
});

export const encodePremintForAPI = ({
premintConfig,
premintConfigVersion,
}: PremintConfigAndVersion) => {
if (premintConfigVersion === PremintConfigVersion.V1) {
return encodePremintV1ForAPI(premintConfig);
}
if (premintConfigVersion === PremintConfigVersion.V2) {
return encodePremintV2ForAPI(premintConfig);
}
throw new Error(`Invalid premint config version ${premintConfigVersion}`);
};

/**
* Preminter API to access ZORA Premint functionality.
* Currently only supports V1 premints.
Expand Down Expand Up @@ -228,7 +266,7 @@ class PremintClient {
uid: uid,
});

const convertedPremint = convertPremint(signatureResponse.premint);
const convertedPremint = convertPremintV1(signatureResponse.premint);
const signerData = {
...signatureResponse,
premint: {
Expand All @@ -251,6 +289,7 @@ class PremintClient {
contractAdmin: signerData.collection.contractAdmin as Address,
},
premintConfig: signerData.premint,
premintConfigVersion: PremintConfigVersion.V1,
});
}

Expand Down Expand Up @@ -288,7 +327,7 @@ class PremintClient {
...signatureResponse,
collection: convertCollection(signatureResponse.collection),
premint: {
...convertPremint(signatureResponse.premint),
...convertPremintV1(signatureResponse.premint),
deleted: true,
},
};
Expand All @@ -301,6 +340,7 @@ class PremintClient {
uid: uid,
collection: signerData.collection,
premintConfig: signerData.premint,
premintConfigVersion: PremintConfigVersion.V1,
});
}

Expand All @@ -313,20 +353,19 @@ class PremintClient {
private async signAndSubmitPremint({
walletClient,
verifyingContract,
premintConfig,
uid,
account,
checkSignature,
collection,
...premintConfigAndVersion
}: {
uid: number;
walletClient: WalletClient;
verifyingContract: Address;
checkSignature: boolean;
account?: Address | Account;
premintConfig: PremintConfig;
collection: PremintSignatureGetResponse["collection"];
}) {
} & PremintConfigAndVersion) {
if (!account) {
account = walletClient.account;
}
Expand All @@ -336,28 +375,37 @@ class PremintClient {

const signature = await walletClient.signTypedData({
account,
...preminterTypedDataDefinition({
...premintTypedDataDefinition({
verifyingContract,
premintConfig,
...premintConfigAndVersion,
chainId: this.chain.id,
}),
});

if (checkSignature) {
const [isValidSignature] = await this.publicClient.readContract({
abi: zoraCreator1155PremintExecutorImplABI,
address: this.getExecutorAddress(),
functionName: "isValidSignature",
args: [convertCollection(collection), premintConfig, signature],
const convertedCollection = convertCollection(collection);
const isAuthorized = await isAuthorizedToCreatePremint({
collection: convertCollection(collection),
signature,
publicClient: this.publicClient,
signer: typeof account === "string" ? account : account.address,
collectionAddress: await this.getCollectionAddres(convertedCollection),
...premintConfigAndVersion,
});
if (!isValidSignature) {
throw new Error("Invalid signature");
if (!isAuthorized) {
throw new Error("Not authorized to create premint");
}
}

if (
premintConfigAndVersion.premintConfigVersion === PremintConfigVersion.V2
) {
throw new Error("premint config v2 not supported yet");
}

const apiData = {
collection,
premint: encodePremintForAPI(premintConfig),
premint: encodePremintV1ForAPI(premintConfigAndVersion.premintConfig),
signature: signature,
};

Expand Down Expand Up @@ -404,11 +452,9 @@ class PremintClient {
uid?: number;
};
}) {
const newContractAddress = await this.publicClient.readContract({
address: this.getExecutorAddress(),
abi: zoraCreator1155PremintExecutorImplABI,
functionName: "getContractAddress",
args: [convertCollection(collection)],
const newContractAddress = await getPremintCollectionAddress({
publicClient: this.publicClient,
collection: convertCollection(collection),
});

const tokenConfig = {
Expand All @@ -432,7 +478,7 @@ class PremintClient {

let deleted = executionSettings?.deleted || false;

const premintConfig = {
const premintConfig: PremintConfigV1 = {
tokenConfig: tokenConfig,
uid,
version: 1,
Expand All @@ -443,6 +489,7 @@ class PremintClient {
uid,
verifyingContract: newContractAddress,
premintConfig,
premintConfigVersion: PremintConfigVersion.V1,
checkSignature,
account,
walletClient,
Expand Down Expand Up @@ -470,34 +517,33 @@ class PremintClient {
});
}

async getCollectionAddres(collection: ContractCreationConfig) {
return await getPremintCollectionAddress({
collection,
publicClient: this.publicClient,
});
}

/**
* 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
* @returns isValid = signature is valid or not, recoveredSigner = signer from contract
*/
async isValidSignature({
data,
}: {
data: PremintSignatureGetResponse;
}): Promise<{
async isValidSignature(data: PremintSignatureResponse): Promise<{
isValid: boolean;
contractAddress: Address;
recoveredSigner: Address;
recoveredSigner: Address | undefined;
}> {
const [isValid, contractAddress, recoveredSigner] =
await this.publicClient.readContract({
abi: zoraCreator1155PremintExecutorImplABI,
address: this.getExecutorAddress(),
functionName: "isValidSignature",
args: [
convertCollection(data.collection),
convertPremint(data.premint),
data.signature as Hex,
],
});
const {isAuthorized, recoveredAddress }= await recoverAndValidateSignature({
chainId: this.chain.id,
signature: data.signature as Hex,
premintConfig: convertPremintV1(data.premint),
premintConfigVersion: PremintConfigVersion.V1,
collection: convertCollection(data.collection),
publicClient: this.publicClient,
});

return { isValid, contractAddress, recoveredSigner };
return { isValid: isAuthorized, recoveredSigner: recoveredAddress };
}

protected makeUrls({
Expand Down Expand Up @@ -566,7 +612,7 @@ class PremintClient {
const numberToMint = BigInt(mintArguments?.quantityToMint || 1);
const args = [
convertCollection(data.collection),
convertPremint(data.premint),
convertPremintV1(data.premint),
data.signature as Hex,
numberToMint,
mintArguments?.mintComment || "",
Expand Down
Loading

0 comments on commit 702ad19

Please sign in to comment.