Skip to content
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
110 changes: 110 additions & 0 deletions packages/sdk/src/react/hooks/use-update-allowlist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { useQuery } from "@tanstack/react-query";
import { UpdateAllowlistParams } from "../../utils/update-allowlist";
import { useSdk } from "./use-sdk";
import {
useSimulateContract,
useWaitForTransactionReceipt,
useWriteContract,
UseWaitForTransactionReceiptReturnType,
} from "wagmi";

type UseUpdateAllowlistResult = {
submitRegisterAuction: () => void;
isWaitingForRegisterAuction: boolean;
registerAuctionReceipt: UseWaitForTransactionReceiptReturnType;
registerAuctionError: Error | null;

submitMerkleRootTx: () => void;
isWaitingForMerkleRoot: boolean;
merkleRootReceipt: UseWaitForTransactionReceiptReturnType;
merkleRootError: Error | null;
};

/**
* Updates an allowlist by saving it to a metadata server (defaulting to IPFS),
* updating the IPFS CID on the subgraph, and calculating and submitting the allowlist Merkle root.
*
* @param {UpdateAllowlistParams} params - Parameters for updating the allowlist
* @returns {Object} - The hook's return values and functions.
* @returns {Function} return.submitRegisterAuction - Submits the register auction transaction.
* @returns {boolean} return.isWaitingForRegisterAuction - Indicates if the register auction transaction is pending.
* @returns {Object} return.registerAuctionReceipt - Transaction receipt for the register auction.
* @returns {Error | null} return.registerAuctionError - Any error that occurred during the register auction process.
*
* @returns {Function} return.submitMerkleRootTx - Submits the Merkle root transaction.
* @returns {boolean} return.isWaitingForMerkleRoot - Indicates if the Merkle root transaction is pending.
* @returns {Object} return.merkleRootReceipt - Transaction receipt for setting the Merkle root.
* @returns {Error | null} return.merkleRootError - Any error that occurred during the Merkle root process.
*/
export function useUpdateAllowlist(
params: UpdateAllowlistParams,
): UseUpdateAllowlistResult {
const sdk = useSdk();

const config = useQuery({
queryFn: async () => sdk.updateAllowlist(params),
queryKey: ["update-allowlist", params.lotId, params.chainId],
});

const merkleRootSim = useSimulateContract({
...config.data?.setMerkleRootConfig,
query: {
enabled: config.isSuccess,
},
});

const registerAuctionSim = useSimulateContract({
...config.data?.registerAuctionConfig,
query: {
enabled: config.isSuccess,
},
});

const registerAuctionTx = useWriteContract();
const merkleRootTx = useWriteContract();

const submitRegisterAuction = () => {
if (registerAuctionSim.data) {
registerAuctionTx.writeContract(registerAuctionSim.data.request);
}
};

const submitMerkleRootTx = () => {
if (merkleRootSim.data) {
merkleRootTx.writeContract(merkleRootSim.data.request);
}
};

const registerAuctionReceipt = useWaitForTransactionReceipt({
hash: registerAuctionTx.data,
});

const merkleRootReceipt = useWaitForTransactionReceipt({
hash: merkleRootTx.data,
});

const isWaitingForRegisterAuction =
registerAuctionTx.isPending || registerAuctionReceipt.isLoading;
const isWaitingForMerkleRoot =
merkleRootTx.isPending || merkleRootReceipt.isLoading;

const registerAuctionError =
registerAuctionSim.error ||
registerAuctionTx.error ||
registerAuctionTx.error;

const merkleRootError =
merkleRootSim.error || merkleRootTx.error || merkleRootReceipt.error;

return {
submitRegisterAuction,
isWaitingForRegisterAuction,
registerAuctionReceipt,
registerAuctionError,

submitMerkleRootTx,
merkleRootError,
merkleRootReceipt,
isWaitingForMerkleRoot,
};
}
58 changes: 58 additions & 0 deletions packages/sdk/src/sdk/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as periphery from "../periphery";
import * as registry from "../registry";
import {
MetadataClient,
SdkError,
type CuratorClient,
type CuratorRouter,
type OriginConfig,
Expand Down Expand Up @@ -48,6 +49,8 @@ import type {
RegisterAuctionParams,
RegisterAuctionConfig,
} from "../registry";
import { updateAllowlist } from "../utils";
import type { UpdateAllowlistParams } from "../utils";

const defaultConfig: OriginConfig = {
environment: Environment.PRODUCTION,
Expand Down Expand Up @@ -308,6 +311,61 @@ class OriginSdk {
registerAuction(params: RegisterAuctionParams): RegisterAuctionConfig {
return this.registry.registerAuction.getConfig(params);
}

/**
* Generates the contract configurations required for updating the allowlist of an auction,
* which involves storing the new allowlist offchain and calculating the merkle root
*
* This function validates the provided parameters, fetches auction metadata, updates the allowlist,
* and stores the new metadata on IPFS.
*
* @param {UpdateAllowlistParams} params - The parameters required for updating the allowlist.
* @returns {Promise<Array[RegisterConfig, SetMerkleRootConfig]>} A promise that resolves to an array containing contract configurations:
* - `RegisterConfig`: Configuration for registering auction metadata.
* - `SetMerkleRootConfig`: Configuration for updating the Merkle root of the allowlist.
*
* @throws {SdkError} If the provided parameters are invalid.
* @throws {SdkError} If the auction lot cannot be found on the specified chain.
* @throws {SdkError} If the auction lot doesn't have a callback address defined.
* @throws {Error} If the metadata is invalid.
* @throws {Error} If the {MetadataClient} is unable to store the data.
*
* @example
* import { sdk } from "./sdk";
*
* try {
* const config = await sdk.updateAllowlist({
* id: "auction-123",
* lotId: 1,
* auctionHouse: "0x123456789",
* isTestnet: true,
* allowlist: ["0xabc..."],
* callback: "0x987654321"
* chainId: 1
* }, metadataClient);
*
* console.log(config);
* } catch (error) {
* if (error instanceof SdkError) {
* console.error(error.message, error.issues);
* } else {
* console.error("Unexpected error:", error);
* }
* }
*/
async updateAllowlist(params: UpdateAllowlistParams) {
if (!this.metadataClient) {
throw new SdkError(
"Unable to use updateAllowlist due to unexisting MetadataClient",
);
}

return updateAllowlist.getConfig(
params,
this.metadataClient,
this.config.subgraph,
);
}
}

const createSdk = (config?: OriginConfig) => new OriginSdk(config);
Expand Down
20 changes: 20 additions & 0 deletions packages/sdk/src/utils/get-auction-lot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {
request,
GetBatchAuctionLotDocument,
GetBatchAuctionLotQuery,
} from "@axis-finance/subgraph-client";

type GetAuctionLotParams = {
endpoint: string;
lotId: number;
};

export const getAuctionLot = (params: GetAuctionLotParams) => {
return request<GetBatchAuctionLotQuery>(
params.endpoint,
GetBatchAuctionLotDocument,
{
id: params.lotId,
},
);
};
1 change: 1 addition & 0 deletions packages/sdk/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./update-allowlist";
92 changes: 92 additions & 0 deletions packages/sdk/src/utils/update-allowlist/get-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { describe, it, expect, vi } from "vitest";
import { getConfig } from "./get-config";
import { zeroAddress } from "viem";
import { MetadataClient } from "../metadata-client";
import { abis } from "@axis-finance/abis";
import { deployments } from "@axis-finance/deployments";
import { base } from "viem/chains";
import { UpdateAllowlistParams } from "./types";

const mockAllowlist = [
["0x0000000000000000000000000000000000000004", "5000000000000000000"],
["0x0000000000000000000000000000000000000020", "0"],
["0x0000000000000000000000000000000000000021", "50"],
["0x0000000000000000000000000000000000000022", "60"],
];

const mockMerkleRootResult =
"0xae177673a8db8795a6e8287bbfe67c9425307898c97394d8c87dae3ecf0c5e3c";
const mockCID = "bafkreihv223uekzoo24w3zxkzjj25yenpvr5xfc6jychur5wjhy4rhnme4";

const mockAddress = zeroAddress;
const lotId = 1;

const registryAddress = deployments[base.id].registry;

const mockParams = {
lotId,
allowlist: {
types: ["address", "uint256"],
values: mockAllowlist,
},
auctionHouse: mockAddress,
chainId: base.id,
} satisfies UpdateAllowlistParams;

const mockAuction = {
batchAuctionLot: {
callbacks: mockAddress,
info: {
id: "auction-12341234",
},
},
};
vi.mock("../get-auction-lot.ts", () => {
return {
getAuctionLot: vi.fn(() => mockAuction),
};
});

describe("getConfig()", () => {
it("returns contract configuration", async () => {
const metadataClient = new MetadataClient({
fleekApplicationClientId: "1337",
});

vi.spyOn(metadataClient, "save").mockResolvedValue(mockCID);

const result = await getConfig(mockParams, metadataClient);

expect(result.registerAuctionConfig).toStrictEqual({
chainId: base.id,
abi: abis.axisMetadataRegistry,
address: registryAddress,
functionName: "registerAuction",
args: [mockAddress, BigInt(mockParams.lotId), mockCID],
});

expect(result.setMerkleRootConfig).toStrictEqual({
abi: abis.merkleAllowlist,
address: zeroAddress,
functionName: "setMerkleRoot",
args: [BigInt(mockParams.lotId), mockMerkleRootResult],
});
});

it("throws an error if invalid params are supplied", async () => {
const invalidParams = { ...mockParams, lotId: "invalid" };
const metadataClient = new MetadataClient({
fleekApplicationClientId: "1337",
});

const result = getConfig(
// @ts-expect-error - deliberately testing invalid params
invalidParams,
metadataClient,
);

expect(result).rejects.toThrowErrorMatchingInlineSnapshot(
`[OriginSdkError: Invalid parameters supplied to getConfig()]`,
);
});
});
72 changes: 72 additions & 0 deletions packages/sdk/src/utils/update-allowlist/get-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as v from "valibot";
import { setMerkleRoot } from "../../periphery/callbacks";
import { registerAuction } from "../../registry";
import { MetadataClient } from "../metadata-client";
import { schema } from "./schema";
import { SdkError } from "../../types";
import { getAuctionLot } from "../get-auction-lot";
import { deployments } from "@axis-finance/deployments";
import type { OriginConfig } from "../../types";
import { UpdateAllowlistParams, UpdateAllowlistResult } from "./types";
import { Address, isAddress } from "viem";

export const getConfig = async (
params: UpdateAllowlistParams,
metadataClient: MetadataClient,
subgraph?: OriginConfig["subgraph"],
): Promise<UpdateAllowlistResult> => {
const parsedParams = v.safeParse(schema, params);

if (!parsedParams.success) {
throw new SdkError(
"Invalid parameters supplied to getConfig()",
parsedParams.issues,
);
}

const { lotId, auctionHouse, allowlist, chainId } = params;

const subgraphEndpoint = subgraph
? subgraph[chainId].url
: deployments[chainId].subgraphURL;

const { chain } = deployments[chainId];
const isTestnet = !!chain.testnet;

const result = await getAuctionLot({
endpoint: subgraphEndpoint,
lotId,
});

const auction = result.batchAuctionLot;

if (!auction) {
throw new SdkError(`Auction with ${lotId} not found on ${chainId}`);
}

if (!isAddress(auction.callbacks)) {
throw new SdkError(`Provided Auction doesn't have a callback address`);
}

const metadata = { ...auction.info, allowlist };

const ipfsCID = await metadataClient.save({
metadata: JSON.stringify(metadata),
id: auction.info?.key ?? undefined,
});

const registerAuctionConfig = registerAuction.getConfig({
ipfsCID,
lotId,
auctionHouse,
isTestnet,
});

const setMerkleRootConfig = setMerkleRoot.getConfig({
lotId,
allowlist,
callback: auction.callbacks as Address,
});

return { registerAuctionConfig, setMerkleRootConfig };
};
2 changes: 2 additions & 0 deletions packages/sdk/src/utils/update-allowlist/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * as updateAllowlist from "./get-config";
export * from "./types";
15 changes: 15 additions & 0 deletions packages/sdk/src/utils/update-allowlist/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as v from "valibot";
import { schema as registerAuctionSchema } from "../../registry/register-auction/schema";
import { schema as setMerkleRootSchema } from "../../periphery/callbacks/set-merkle-root/schema";

const _registerAuctionSchema = v.omit(registerAuctionSchema, [
"ipfsCID",
"isTestnet",
]);

const _setMerkleRootSchema = v.omit(setMerkleRootSchema, ["callback"]);

export const schema = v.object({
..._registerAuctionSchema.entries,
..._setMerkleRootSchema.entries,
});
Loading
Loading