Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial soundxyz/sdk v0.13.2 integration with hardcoded values … #1

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions contexts/AppContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// global state management context

// src/contexts/AppContext.tsx
import React, { createContext, Reducer, useReducer } from "react";
import { Action, initialState, reducer, State } from "../state";

export const AppContext = createContext<{
state: State;
dispatch: React.Dispatch<Action>;
}>({
state: initialState,
dispatch: () => null,
});

export const AppContextProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const [state, dispatch] = useReducer<Reducer<State, Action>>(
reducer,
initialState,
);

return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};
254 changes: 254 additions & 0 deletions hooks/useSound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
import { useContext, useEffect, useState } from "react";
import { useAccount } from "wagmi";
import {
ContractCall,
EditionConfig,
MintConfig,
SoundClient,
} from "@soundxyz/sdk";
import { contractAddresses } from "@soundxyz/sound-protocol";
import {
MerkleDropMinter__factory,
RangeEditionMinter__factory,
SoundCreatorV1__factory,
ISoundEditionV1_1__factory,
} from "@soundxyz/sound-protocol/typechain/index";
import { editionInitFlags, MINTER_ROLE } from "@soundxyz/sdk/utils/constants";
import { Overrides } from "ethers";

import { AppContext } from "../contexts/AppContext";
import { MinterType } from "../types";
import { getSaltAsBytes32 } from "../utils/helpers";

interface OptionalOverrides {
gasLimit?: number;
maxFeePerGas?: number;
maxPriorityFeePerGas?: number;
}

export const useSound = ({
gasLimit = undefined,
maxFeePerGas = undefined,
maxPriorityFeePerGas = undefined,
}: OptionalOverrides) => {
const { address, connector } = useAccount();

const { dispatch, state } = useContext(AppContext);
const [client, setClient] = useState<SoundClient>();
const [signer, setSigner] = useState<any>();
const [soundCreatorAddress, setSoundCreatorAddress] = useState<string>();

useEffect(() => {
async function initSoundClient() {
if (!connector) return;

const mySigner = await connector?.getSigner();

setSigner(mySigner);

// check chainid and set soundCreatorAddress dpeending if it's mainnet or goerli
const chainId = await connector.getChainId();
if (chainId === 1) {
setSoundCreatorAddress(contractAddresses.mainnet.soundCreatorV1);
} else if (chainId === 5) {
setSoundCreatorAddress(contractAddresses.goerli.soundCreatorV1);
} else {
throw new Error("Unsupported chainId");
}

const _client = SoundClient({
signer: mySigner,
soundCreatorAddress: contractAddresses.mainnet.soundCreatorV1,
});

setClient(_client);
}

initSoundClient();
}, [connector, signer]);

const generateProposalPayload = async () => {
if (!client) return;
if (!address) return;
if (!signer) return;
if (!soundCreatorAddress) return;

dispatch({ type: "GENERATE_BYTECODE" });

const customSalt = "0x" + Math.random().toString(16).slice(2);

const editionConfig: EditionConfig = {
name: state.songName,
symbol: state.songSymbol,
metadataModule: state.metadataModule,
contractURI: state.contractUri,
baseURI: state.baseUri,
fundingRecipient: state.fundingRecipient,
royaltyBPS: state.royaltyBps,
editionMaxMintableUpper: state.editionMaxMintable,
editionMaxMintableLower: 0,
editionCutoffTime: state.editionCutoffTime,
shouldFreezeMetadata: false,
shouldEnableMintRandomness: true,
enableOperatorFiltering: true,
};

const mintConfigs: MintConfig[] = [];

switch (MinterType[state.minter]) {
case "RANGE_MINTER":
mintConfigs.push({
mintType: "RangeEdition",
cutoffTime: state.editionCutoffTime,
minterAddress: contractAddresses.mainnet.rangeEditionMinter,
price: 0,
startTime: 0,
endTime: 0,
affiliateFeeBPS: 0,
maxMintableUpper: state.editionMaxMintable,
maxMintableLower: 0,
maxMintablePerAccount: 1,
});
break;
default:
break;
}

// Transaction
const txnOverrides: Overrides = {
gasLimit,
maxFeePerGas,
maxPriorityFeePerGas,
};

const formattedSalt = getSaltAsBytes32(
customSalt || Math.random() * 1_000_000_000_000_000,
);

// Precompute the edition address.
const [editionAddress, _] = await SoundCreatorV1__factory.connect(
soundCreatorAddress,
signer,
).soundEditionAddress(address, formattedSalt);

console.log("Precompute Edition Address ", editionAddress);

const editionInterface = ISoundEditionV1_1__factory.createInterface();

/**
* Encode all the bundled contract calls.
*/
const contractCalls: ContractCall[] = [];

console.log("Grant Roles");
// Grant MINTER_ROLE for each minter.
const mintersToGrantRole = Array.from(
new Set(mintConfigs.map((m) => m.minterAddress)),
);
for (const minterAddress of mintersToGrantRole) {
contractCalls.push({
contractAddress: editionAddress,
// TODO: The new contract interface removed grantRoles, but there should be an equivalent function.
calldata: editionInterface.encodeFunctionData("grantRoles", [
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs fix to make it build. I'm not sure if they require granting roles anymore in the new contracts

minterAddress,
MINTER_ROLE,
]),
});
}

// Add the createEditionMint calls.
for (const mintConfig of mintConfigs) {
/**
* Set up the createEditionMint call for each mint config.
*/
switch (mintConfig.mintType) {
case "RangeEdition": {
const minterInterface = RangeEditionMinter__factory.createInterface();
contractCalls.push({
contractAddress: mintConfig.minterAddress,

calldata: minterInterface.encodeFunctionData("createEditionMint", [
editionAddress,
mintConfig.price,
Math.floor(mintConfig.startTime),
Math.floor(mintConfig.cutoffTime),
Math.floor(mintConfig.endTime),
mintConfig.affiliateFeeBPS,
Math.floor(mintConfig.maxMintableLower),
Math.floor(mintConfig.maxMintableUpper),
Math.floor(mintConfig.maxMintablePerAccount),
]),
});
break;
}
case "MerkleDrop": {
const minterInterface = MerkleDropMinter__factory.createInterface();
contractCalls.push({
contractAddress: mintConfig.minterAddress,
calldata: minterInterface.encodeFunctionData("createEditionMint", [
editionAddress,
mintConfig.merkleRoot,
mintConfig.price,
Math.floor(mintConfig.startTime),
Math.floor(mintConfig.endTime),
mintConfig.affiliateFeeBPS,
Math.floor(mintConfig.maxMintable),
Math.floor(mintConfig.maxMintablePerAccount),
]),
});
break;
}
}
}

let flags = 0;
if (editionConfig.shouldFreezeMetadata)
flags |= editionInitFlags.METADATA_IS_FROZEN;
if (editionConfig.shouldEnableMintRandomness)
flags |= editionInitFlags.MINT_RANDOMNESS_ENABLED;
if (editionConfig.enableOperatorFiltering)
flags |= editionInitFlags.OPERATOR_FILTERING_ENABLED;

console.log("encode Initialize for edition ");
/**
* Encode the SoundEdition.initialize call.
*/
const editionInitData = editionInterface.encodeFunctionData("initialize", [
editionConfig.name,
editionConfig.symbol,
editionConfig.metadataModule,
editionConfig.baseURI,
editionConfig.contractURI,
editionConfig.fundingRecipient,
editionConfig.royaltyBPS,
Math.floor(editionConfig.editionMaxMintableLower),
Math.floor(editionConfig.editionMaxMintableUpper),
Math.floor(editionConfig.editionCutoffTime),
flags,
]);

console.log("connect sound creator contract");
const soundCreatorContract = SoundCreatorV1__factory.connect(
soundCreatorAddress,
signer,
);

const bytecode = soundCreatorContract.interface.encodeFunctionData(
"createSoundAndMints",
[
formattedSalt,
editionInitData,
contractCalls.map((d) => d.contractAddress),
contractCalls.map((d) => d.calldata),
],
);

console.log("createSoundAndMints bytecode ", bytecode);

dispatch({ type: "SET_PROPOSAL_BYTECODE", payload: bytecode });

return bytecode;
};

return { client, generateProposalPayload };
};
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,22 @@
"lint": "next lint"
},
"dependencies": {
"@rainbow-me/rainbowkit": "^0.12.4",
"@soundxyz/sdk": "0.13.2",
"@soundxyz/sound-protocol": "1.3.0",
"@types/node": "18.15.10",
"@types/react": "18.0.30",
"@types/react-dom": "18.0.11",
"autoprefixer": "^10.4.14",
"eslint": "8.36.0",
"eslint-config-next": "13.2.4",
"ethers": "^5",
"next": "13.2.4",
"postcss": "^8.4.21",
"react": "18.2.0",
"react-dom": "18.2.0",
"tailwindcss": "^3.3.0",
"typescript": "5.0.2",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.21",
"tailwindcss": "^3.3.0"
"wagmi": "^0.12.7"
}
}
9 changes: 6 additions & 3 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
import "@/styles/globals.css";
import type { AppProps } from "next/app";
import Layout from "@/pages/components/layout";
import { AppContextProvider } from "@/contexts/AppContext";

export default function App({ Component, pageProps }: AppProps) {
return (
<AppContextProvider>
<Layout>
<Component {...pageProps} />
</Layout>
)
</AppContextProvider>
);
}
Loading