Skip to content

Commit

Permalink
added viem and wagmi examples
Browse files Browse the repository at this point in the history
  • Loading branch information
oveddan committed Nov 28, 2023
1 parent c3ec6c2 commit c8619de
Show file tree
Hide file tree
Showing 3 changed files with 300 additions and 142 deletions.
174 changes: 160 additions & 14 deletions packages/protocol-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,175 @@ Protocol SDK allows users to create tokens using the Zora Protocol, and mint the

### Creating a mint from an on-chain contract:

#### Using viem

```ts
import { createMintClient } from "@zoralabs/protocol-sdk";
import type { Address, WalletClient } from "viem";
import {createMintClient} from "@zoralabs/protocol-sdk";
import type {Address, PublicClient, WalletClient} from "viem";

async function mintNFT(
walletClient: WalletClient,
publicClient: PublicClient,
address: Address,
tokenId: bigint,
) {
const mintAPI = createMintClient({ chain: walletClient.chain });
await mintAPI.mintNFT({
walletClient,
address,
async function mintNFT({
walletClient,
publicClient,
tokenContract,
tokenId,
mintToAddress,
quantityToMint,
mintReferral,
}: {
// wallet client that will submit the transaction
walletClient: WalletClient;
// public client that will simulate the transaction
publicClient: PublicClient;
// address of the token contract
tokenContract: Address;
// id of the token to mint
tokenId: bigint;
// address that will receive the minted token
mintToAddress: Address;
// quantity of tokens to mint
quantityToMint: number;
// optional address that will receive a mint referral reward
mintReferral?: Address;
}) {
const mintClient = createMintClient({chain: walletClient.chain!});

// get mintable information about the token.
const mintable = await mintClient.getMintable({
tokenContract,
tokenId,
});

// prepare the mint transaction, which can be simulated via an rpc with the public client.
const prepared = await mintClient.makePrepareMintTokenParams({
// token to mint
mintable,
mintArguments: {
quantityToMint: 23,
mintComment: "Helo",
// address that will receive the token
mintToAddress,
// quantity of tokens to mint
quantityToMint,
// comment to include with the mint
mintComment: "My comment",
// optional address that will receive a mint referral reward
mintReferral,
},
// account that is to invoke the mint transaction
minterAccount: walletClient.account!.address,
});

// simulate the transaction and get any validation errors
const { request } = await publicClient.simulateContract(prepared);

// submit the transaction to the network
const txHash = await walletClient.writeContract(request);

// wait for the transaction to be complete
await publicClient.waitForTransactionReceipt({hash: txHash});
}
```

#### Using wagmi

```tsx
import {createMintClient, Mintable} from "@zoralabs/protocol-sdk";
import {useEffect, useMemo, useState} from "react";
import {BaseError, SimulateContractParameters, stringify} from "viem";
import {Address, useAccount, useContractWrite, useNetwork, usePrepareContractWrite, usePublicClient, useWaitForTransaction} from "wagmi";

// custom hook that gets the mintClient for the current chain
const useMintClient = () => {
const publicClient = usePublicClient();

const {chain} = useNetwork();

const mintClient = useMemo(() => chain && createMintClient({chain, publicClient}), [chain, publicClient]);

return mintClient;
};

export const Mint = ({tokenId, tokenContract}: {tokenId: string; tokenContract: Address}) => {
// call custom hook to get the mintClient
const mintClient = useMintClient();

// value will be set by the form
const [quantityToMint, setQuantityToMint] = useState<number>(1);

// fetched mintable info from the sdk
const [mintable, setMintable] = useState<Mintable>();

useEffect(() => {
// fetch the mintable token info
const fetchMintable = async () => {
if (mintClient) {
const mintable = await mintClient.getMintable({tokenId, tokenContract});
setMintable(mintable);
}
};

fetchMintable();
}, [mintClient, tokenId, tokenContract]);

// params for the prepare contract write hook
const [params, setParams] = useState<SimulateContractParameters>();

const {address} = useAccount();

useEffect(() => {
if (!mintable || !mintClient || !address) return;

const makeParams = async () => {
// make the params for the prepare contract write hook
const params = await mintClient.makePrepareMintTokenParams({
mintable,
minterAccount: address,
mintArguments: {
mintToAddress: address,
quantityToMint,
},
});
setParams(params);
};

makeParams();
}, [mintable, mintClient, address, quantityToMint]);

const {config} = usePrepareContractWrite(params);

const {write, data, error, isLoading, isError} = useContractWrite(config);
const {data: receipt, isLoading: isPending, isSuccess} = useWaitForTransaction({hash: data?.hash});

return (
<>
<h3>Mint a token</h3>
<form
onSubmit={(e) => {
e.preventDefault();
write?.();
}}
>
{/* input for quantity to mint: */}
<input placeholder="quantity to mint" onChange={(e) => setQuantityToMint(Number(e.target.value))} />
<button disabled={!write} type="submit">
Mint
</button>
</form>

{isLoading && <div>Check wallet...</div>}
{isPending && <div>Transaction pending...</div>}
{isSuccess && (
<>
<div>Transaction Hash: {data?.hash}</div>
<div>
Transaction Receipt: <pre>{stringify(receipt, null, 2)}</pre>
</div>
</>
)}
{isError && <div>{(error as BaseError)?.shortMessage}</div>}
</>
);
};
```

### Creating an 1155 contract:

If an object with {name, uri} is passed in to this helper, it uses the creatorAccount and those values to either 1) create or 2) mint to that existing contract.
Expand Down
121 changes: 61 additions & 60 deletions packages/protocol-sdk/src/mint/mint-api-client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { retries, get, post } from "../apis/http-api-base";
import * as httpClientBase from "../apis/http-api-base";
import { paths } from "../apis/generated/discover-api-types";
import { ZORA_API_BASE } from "../constants";
import { NetworkConfig, networkConfigByChain } from "src/apis/chain-constants";
Expand All @@ -17,69 +17,70 @@ function encodeQueryParameters(params: Record<string, string>) {
return new URLSearchParams(params).toString();
}

const getMintable = async (
path: MintableGetTokenPathParameters,
query: MintableGetTokenGetQueryParameters,
): Promise<MintableGetTokenResponse> =>
retries(() => {
return get<MintableGetTokenResponse>(
`${ZORA_API_BASE}discover/mintables/${path.chain_name}/${
path.collection_address
}${query?.token_id ? `?${encodeQueryParameters(query)}` : ""}`,
);
});

export const getSalesConfigFixedPrice = async ({
contractAddress,
tokenId,
subgraphUrl,
}: {
contractAddress: string;
tokenId: string;
subgraphUrl: string;
}): Promise<undefined | string> =>
retries(async () => {
const response = await post<any>(subgraphUrl, {
query:
"query($id: ID!) {\n zoraCreateToken(id: $id) {\n id\n salesStrategies{\n fixedPrice {\n address\n }\n }\n }\n}",
variables: { id: `${contractAddress.toLowerCase()}-${tokenId}` },
});
return response.zoraCreateToken?.salesStrategies?.find(() => true)
?.fixedPriceMinterAddress;
});

export const getMintableForToken = async (
{
tokenContract,
tokenId,
chainId
}: {
tokenContract: Address;
tokenId?: bigint | number | string;
chainId: number;
}
) => {
const chainName = getApiNetworkConfigForChain(chainId).zoraBackendChainName;
if (!chainName) return;
return await getMintable(
{
chain_name: chainName,
collection_address: tokenContract,
},
{ token_id: tokenId?.toString() },
);
}

export const getApiNetworkConfigForChain = (chainId: number): NetworkConfig => {
if (!networkConfigByChain[chainId]) {
throw new Error(`chain id ${chainId} network not configured `);
}
return networkConfigByChain[chainId]!;
};

export const MintAPIClient = {
getMintable,
getSalesConfigFixedPrice,
getApiNetworkConfigForChain,
getMintableForToken
};
export class MintAPIClient {
httpClient: typeof httpClientBase;
networkConfig: NetworkConfig;

constructor(chainId: number, httpClient?: typeof httpClientBase) {
this.httpClient = httpClient || httpClientBase;
this.networkConfig = getApiNetworkConfigForChain(chainId);
}

async getMintable(
path: MintableGetTokenPathParameters,
query: MintableGetTokenGetQueryParameters,
): Promise<MintableGetTokenResponse> {
const httpClient = this.httpClient;
return httpClient.retries(() => {
return httpClient.get<MintableGetTokenResponse>(
`${ZORA_API_BASE}discover/mintables/${path.chain_name}/${
path.collection_address
}${query?.token_id ? `?${encodeQueryParameters(query)}` : ""}`,
);
});
}

async getSalesConfigFixedPrice({
contractAddress,
tokenId,
}: {
contractAddress: string;
tokenId: bigint;
}): Promise<undefined | string> {
const { retries, post } = this.httpClient;
return retries(async () => {
const response = await post<any>(this.networkConfig.subgraphUrl, {
query:
"query($id: ID!) {\n zoraCreateToken(id: $id) {\n id\n salesStrategies{\n fixedPrice {\n address\n }\n }\n }\n}",
variables: {
id: `${contractAddress.toLowerCase()}-${tokenId.toString()}`,
},
});
return response.zoraCreateToken?.salesStrategies?.find(() => true)
?.fixedPriceMinterAddress;
});
}

async getMintableForToken({
tokenContract,
tokenId,
}: {
tokenContract: Address;
tokenId?: bigint | number | string;
}) {
return await this.getMintable(
{
chain_name: this.networkConfig.zoraBackendChainName,
collection_address: tokenContract,
},
{ token_id: tokenId?.toString() },
);
}
}
Loading

0 comments on commit c8619de

Please sign in to comment.