diff --git a/packages/core-sdk/src/client.ts b/packages/core-sdk/src/client.ts index 2dcca20d8..15708a8f6 100644 --- a/packages/core-sdk/src/client.ts +++ b/packages/core-sdk/src/client.ts @@ -170,7 +170,7 @@ export class StoryClient { */ public get ipAccount(): IPAccountClient { if (this._ipAccount === null) { - this._ipAccount = new IPAccountClient(this.rpcClient, this.wallet); + this._ipAccount = new IPAccountClient(this.rpcClient, this.wallet, this.chainId); } return this._ipAccount; diff --git a/packages/core-sdk/src/resources/ipAccount.ts b/packages/core-sdk/src/resources/ipAccount.ts index 0f3910bfc..c4b29fc7d 100644 --- a/packages/core-sdk/src/resources/ipAccount.ts +++ b/packages/core-sdk/src/resources/ipAccount.ts @@ -1,4 +1,4 @@ -import { Address, PublicClient } from "viem"; +import { Address, Hex, encodeFunctionData, PublicClient } from "viem"; import { IPAccountExecuteRequest, @@ -6,19 +6,27 @@ import { IPAccountExecuteWithSigRequest, IPAccountExecuteWithSigResponse, IpAccountStateResponse, + SetIpMetadataRequest, TokenResponse, } from "../types/resources/ipAccount"; import { handleError } from "../utils/errors"; -import { IpAccountImplClient, SimpleWalletClient } from "../abi/generated"; -import { getAddress } from "../utils/utils"; +import { + coreMetadataModuleAbi, + coreMetadataModuleAddress, + IpAccountImplClient, + SimpleWalletClient, +} from "../abi/generated"; +import { getAddress, validateAddress } from "../utils/utils"; +import { ChainIds } from "../types/config"; export class IPAccountClient { private readonly wallet: SimpleWalletClient; private readonly rpcClient: PublicClient; - - constructor(rpcClient: PublicClient, wallet: SimpleWalletClient) { + private readonly chainId: ChainIds; + constructor(rpcClient: PublicClient, wallet: SimpleWalletClient, chainId: ChainIds) { this.wallet = wallet; this.rpcClient = rpcClient; + this.chainId = chainId; } /** Executes a transaction from the IP Account. @@ -150,4 +158,34 @@ export class IPAccountClient { handleError(error, "Failed to get the token"); } } + /** + * Sets the metadataURI for an IP asset. + */ + public async setIpMetadata({ + ipId, + metadataURI, + metadataHash, + txOptions, + }: SetIpMetadataRequest): Promise { + try { + const data = encodeFunctionData({ + abi: coreMetadataModuleAbi, + functionName: "setMetadataURI", + args: [validateAddress(ipId), metadataURI, metadataHash], + }); + const { txHash } = await this.execute({ + ipId: ipId, + to: coreMetadataModuleAddress[this.chainId], + data: data, + value: 0, + txOptions: { + ...txOptions, + encodedTxDataOnly: false, + }, + }); + return txHash!; + } catch (error) { + handleError(error, "Failed to set the IP metadata"); + } + } } diff --git a/packages/core-sdk/src/resources/royalty.ts b/packages/core-sdk/src/resources/royalty.ts index f0652a818..bc1b76cc2 100644 --- a/packages/core-sdk/src/resources/royalty.ts +++ b/packages/core-sdk/src/resources/royalty.ts @@ -33,7 +33,6 @@ import { SimpleWalletClient, WrappedIpClient, } from "../abi/generated"; -import { IPAccountClient } from "./ipAccount"; import { getAddress, validateAddress, validateAddresses } from "../utils/utils"; import { WIP_TOKEN_ADDRESS } from "../constants/common"; import { contractCallWithFees } from "../utils/feeUtils"; @@ -43,7 +42,6 @@ import { simulateAndWriteContract } from "../utils/contract"; export class RoyaltyClient { public royaltyModuleClient: RoyaltyModuleClient; public ipAssetRegistryClient: IpAssetRegistryClient; - public ipAccountClient: IPAccountClient; public ipRoyaltyVaultImplReadOnlyClient: IpRoyaltyVaultImplReadOnlyClient; public ipRoyaltyVaultImplEventClient: IpRoyaltyVaultImplEventClient; public multicall3Client: Multicall3Client; @@ -57,7 +55,6 @@ export class RoyaltyClient { this.ipAssetRegistryClient = new IpAssetRegistryClient(rpcClient, wallet); this.ipRoyaltyVaultImplReadOnlyClient = new IpRoyaltyVaultImplReadOnlyClient(rpcClient); this.ipRoyaltyVaultImplEventClient = new IpRoyaltyVaultImplEventClient(rpcClient); - this.ipAccountClient = new IPAccountClient(rpcClient, wallet); this.multicall3Client = new Multicall3Client(rpcClient, wallet); this.wrappedIpClient = new WrappedIpClient(rpcClient, wallet); this.rpcClient = rpcClient; diff --git a/packages/core-sdk/src/types/resources/ipAccount.ts b/packages/core-sdk/src/types/resources/ipAccount.ts index 745ea2f0c..4ebf6395a 100644 --- a/packages/core-sdk/src/types/resources/ipAccount.ts +++ b/packages/core-sdk/src/types/resources/ipAccount.ts @@ -12,7 +12,7 @@ export type IPAccountExecuteRequest = { }; export type IPAccountExecuteResponse = { - txHash?: string; + txHash?: Hex; encodedTxData?: EncodedTxData; }; @@ -28,7 +28,7 @@ export type IPAccountExecuteWithSigRequest = { }; export type IPAccountExecuteWithSigResponse = { - txHash?: string; + txHash?: Hex; encodedTxData?: EncodedTxData; }; @@ -39,3 +39,12 @@ export type TokenResponse = { tokenContract: Address; tokenId: bigint; }; + +export type SetIpMetadataRequest = { + ipId: Address; + /** The metadataURI to set for the IP asset. */ + metadataURI: string; + /** The hash of metadata at metadataURI. */ + metadataHash: Hex; + txOptions?: Omit; +}; diff --git a/packages/core-sdk/test/integration/ipAccount.test.ts b/packages/core-sdk/test/integration/ipAccount.test.ts index abd0a7990..23a1761ff 100644 --- a/packages/core-sdk/test/integration/ipAccount.test.ts +++ b/packages/core-sdk/test/integration/ipAccount.test.ts @@ -2,7 +2,7 @@ import chai from "chai"; import chaiAsPromised from "chai-as-promised"; import { AccessPermission, StoryClient } from "../../src"; import { mockERC721, getStoryClient, getTokenId, aeneid } from "./utils/util"; -import { Hex, encodeFunctionData, getAddress, toFunctionSelector } from "viem"; +import { Hex, encodeFunctionData, getAddress, toFunctionSelector, toHex } from "viem"; import { accessControllerAbi, accessControllerAddress, @@ -150,4 +150,13 @@ describe("IPAccount Functions", () => { .rejected; }); }); + + it("should successfully set ip metadata", async () => { + const txHash = await client.ipAccount.setIpMetadata({ + ipId: ipId, + metadataURI: "https://example.com", + metadataHash: toHex("test", { size: 32 }), + }); + expect(txHash).to.be.a("string").and.not.empty; + }); }); diff --git a/packages/core-sdk/test/unit/mockData.ts b/packages/core-sdk/test/unit/mockData.ts index 73e37faa6..4019ef041 100644 --- a/packages/core-sdk/test/unit/mockData.ts +++ b/packages/core-sdk/test/unit/mockData.ts @@ -1,5 +1,5 @@ export const txHash = "0x063834efe214f4199b1ad7181ce8c5ced3e15d271c8e866da7c89e86ee629cfb"; export const ipId = "0x73fcb515cee99e4991465ef586cfe2b072ebb512"; -export const aeneid = 13_15; +export const aeneid = "1315"; export const mockERC20 = "0x73fcb515cee99e4991465ef586cfe2b072ebb512"; export const walletAddress = "0x73fcb515cee99e4991465ef586cfe2b072ebb512"; diff --git a/packages/core-sdk/test/unit/resources/ipAccount.test.ts b/packages/core-sdk/test/unit/resources/ipAccount.test.ts index d6a9e2201..5f48aeea7 100644 --- a/packages/core-sdk/test/unit/resources/ipAccount.test.ts +++ b/packages/core-sdk/test/unit/resources/ipAccount.test.ts @@ -4,20 +4,20 @@ import * as sinon from "sinon"; import { IPAccountClient } from "../../../src/resources/ipAccount"; import { IPAccountExecuteRequest, IPAccountExecuteWithSigRequest } from "../../../src"; import * as utils from "../../../src/utils/utils"; -import { Account, PublicClient, WalletClient, zeroAddress } from "viem"; -const { IpAccountImplClient } = require("../../../src/abi/generated"); +import { Account, PublicClient, toHex, WalletClient, zeroAddress } from "viem"; +import { aeneid, ipId, txHash } from "../mockData"; +import { IpAccountImplClient } from "../../../src/abi/generated"; describe("Test IPAccountClient", () => { let ipAccountClient: IPAccountClient; let rpcMock: PublicClient; let walletMock: WalletClient; - const txHash = "0x129f7dd802200f096221dd89d5b086e4bd3ad6eafb378a0c75e3b04fc375f997"; beforeEach(() => { rpcMock = createMock(); walletMock = createMock(); const accountMock = createMock(); walletMock.account = accountMock; - ipAccountClient = new IPAccountClient(rpcMock, walletMock); + ipAccountClient = new IPAccountClient(rpcMock, walletMock, aeneid); sinon.stub(IpAccountImplClient.prototype, "execute").resolves(txHash); sinon.stub(IpAccountImplClient.prototype, "executeEncode").returns({ data: "0x", to: "0x" }); sinon.stub(IpAccountImplClient.prototype, "executeWithSig").resolves(txHash); @@ -195,4 +195,26 @@ describe("Test IPAccountClient", () => { expect(token).to.deep.equal({ chainId: 1513n, tokenContract: zeroAddress, tokenId: 1n }); }); }); + + describe("Test setIpMetadata", () => { + it("should throw error when call setIpMetadata given wrong ipId", async () => { + try { + await ipAccountClient.setIpMetadata({ + ipId: "0x", + metadataURI: "https://example.com", + metadataHash: toHex("test", { size: 32 }), + }); + } catch (err) { + expect((err as Error).message).equal("Failed to set the IP metadata: Invalid address: 0x."); + } + }); + it("should return txHash when call setIpMetadata successfully", async () => { + const result = await ipAccountClient.setIpMetadata({ + ipId: ipId, + metadataURI: "https://example.com", + metadataHash: toHex("test", { size: 32 }), + }); + expect(result).to.equal(txHash); + }); + }); });