diff --git a/README.md b/README.md index 4954ab4a..4abbf7ad 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,23 @@ Welcome to the documents for Story Protocol SDK. The SDK provides the APIs for d ## How to use Story Protocol SDK in Your Project ### Install Story Protocol core SDK + Suppose you already have a node project or created a new node project. First, you need to install `@story-protocol/core-sdk` in your project. You can use one of the following command to install the package: Use `npm`: + ``` npm install --save @story-protocol/core-sdk viem ``` Use `pnpm`: + ``` pnpm install @story-protocol/core-sdk viem ``` Use `yarn`: + ``` yarn add @story-protocol/core-sdk viem ``` @@ -57,39 +61,38 @@ Next, we need create a client to access Story Protocol by using private key or f Here is the way to create a Story Protocol client with the private key: ```typescript -import { privateKeyToAccount } from "viem/accounts"; +import { privateKeyToAccount } from 'viem/accounts'; -const PRIVATE_KEY = process.env.PRIVATE_KEY || "0x"; +const PRIVATE_KEY = process.env.PRIVATE_KEY || '0x'; const account = privateKeyToAccount(PRIVATE_KEY as `0x${string}`); -// Instantiate the Story Client for readonly operations, using default +// Instantiate the Story Client for readonly operations, using default export const realonlyClient = StoryClient.newReadOnlyClient({}); // Instantiate the Story Client, test environment required for alpha release. // The private key is also required for written operations. -export const client = StoryClient.newClient({account}); +export const client = StoryClient.newClient({ account }); ``` Here is the way to create a Story Protocol with wallet app: ```typescript -import { createWalletClient, custom } from 'viem' -import { sepolia } from 'viem/chains' +import { createWalletClient, custom } from 'viem'; const walletClient = createWalletClient({ - chain: sepolia, - transport: custom(window.ethereum) -}) + chainId: 'sepolia', + transport: custom(window.ethereum), +}); // Retrieve the first account for eth_requestAccounts method -const account = await walletClient.requestAddresses()[0] +const account = await walletClient.requestAddresses()[0]; -// Instantiate the Story Client for readonly operations, using default +// Instantiate the Story Client for readonly operations, using default export const realonlyClient = StoryClient.newReadOnlyClient({}); // Instantiate the Story Client, test environment required for alpha release. // The private key is also required for written operations. -export const client = StoryClient.newClient({account}); +export const client = StoryClient.newClient({ account }); ``` ### Use `Client` or `ReadOnlyClient` to access Story Protocol @@ -104,32 +107,40 @@ This section provides the instructions on how to build Story Protocol SDK from s ### Prerequisite -* Install PNPM: Execute `npm install -g pnpm` -* Install TypeScript: Run `pnpm add typescript -D` -* Install Yalc: Use `npm install -g yalc` +- Install PNPM: Execute `npm install -g pnpm` +- Install TypeScript: Run `pnpm add typescript -D` +- Install Yalc: Use `npm install -g yalc` ### Steps for Using Yalc for Local Testing of Core-SDK + For manual testing of the core-sdk, set up a separate web project. The guide below uses `yalc` to link the `core-sdk` locally, enabling its installation and import for testing. Under the `typescript-sdk/packages/core-sdk` directory: -* Navigate to the `core-sdk` directory. -* Execute `npm run build` to build your latest code. -* Run `yalc publish`. You should see a message like `@story-protocol/core-sdk@ published in store.` (Note: The version number may vary). + +- Navigate to the `core-sdk` directory. +- Execute `npm run build` to build your latest code. +- Run `yalc publish`. You should see a message like `@story-protocol/core-sdk@ published in store.` (Note: The version number may vary). To set up your testing environment (e.g., a new Next.js project), use `yalc add @story-protocol/core-sdk@` (ensure the version number is updated accordingly). -* Run `pnpm install`. This installs `@story-protocol/core-sdk@` with your local changes. + +- Run `pnpm install`. This installs `@story-protocol/core-sdk@` with your local changes. ### Steps to Refresh the Changes + Under the `typescript-sdk/packages/core-sdk` directory: -* Execute `npm run build` to build your latest code. -* Run `yalc push`. + +- Execute `npm run build` to build your latest code. +- Run `yalc push`. In your testing environment: -* Run `yalc update` to pull the latest changes. + +- Run `yalc update` to pull the latest changes. ## Steps to pull and compile latest smart contract ABIs (Currently pulls from the protocol-contracts `dev` branch) + Must have `solc` installed (https://docs.soliditylang.org/en/v0.8.9/installing-solidity.html) -* run `make compile_contracts` + +- run `make compile_contracts` ## Release @@ -149,4 +160,3 @@ Please make sure to update tests as appropriate. [MIT License](/LICENSE.md) ## Contact Us - diff --git a/packages/core-sdk/src/client.ts b/packages/core-sdk/src/client.ts index ae025e4d..6c498255 100644 --- a/packages/core-sdk/src/client.ts +++ b/packages/core-sdk/src/client.ts @@ -1,6 +1,5 @@ import axios, { AxiosInstance } from "axios"; import { createPublicClient, createWalletClient, http, PublicClient, WalletClient } from "viem"; -import { sepolia } from "viem/chains"; import * as dotenv from "dotenv"; import { StoryConfig, StoryReadOnlyConfig } from "./types/config"; @@ -16,6 +15,7 @@ import { IPAssetClient } from "./resources/ipAsset"; import { IPAssetReadOnlyClient } from "./resources/ipAssetReadOnly"; import { PermissionClient } from "./resources/permission"; import { PermissionReadOnlyClient } from "./resources/permissionReadOnly"; +import { chainStringToViemChain } from "./utils/utils"; if (typeof process !== "undefined") { dotenv.config(); @@ -46,7 +46,7 @@ export class StoryClient { this.isReadOnly = isReadOnly; const clientConfig = { - chain: this.config.chain || sepolia, + chain: chainStringToViemChain(this.config.chainId || "sepolia"), transport: this.config.transport || http(process.env.RPC_PROVIDER_URL), }; diff --git a/packages/core-sdk/src/types/config.ts b/packages/core-sdk/src/types/config.ts index c196bffe..b57cff3d 100644 --- a/packages/core-sdk/src/types/config.ts +++ b/packages/core-sdk/src/types/config.ts @@ -1,4 +1,18 @@ -import { Account, Chain, Transport } from "viem"; +import { Account, Transport } from "viem"; + +/** + * Supported chains. For convenience, both name or chain ID are supported. + * + * @public + */ +export type SupportedChainIds = + | "11155111" + | "sepolia" + | "1" + | "mainnet" + | "80001" + | "mumbai" + | "polygonMumbai"; /** * Configuration for the SDK Client. @@ -15,6 +29,6 @@ export interface StoryConfig extends StoryReadOnlyConfig { * @public */ export interface StoryReadOnlyConfig { - readonly chain?: Chain; + readonly chainId?: SupportedChainIds; readonly transport?: Transport; } diff --git a/packages/core-sdk/src/utils/utils.ts b/packages/core-sdk/src/utils/utils.ts index f7e57aa5..22f5dd88 100644 --- a/packages/core-sdk/src/utils/utils.ts +++ b/packages/core-sdk/src/utils/utils.ts @@ -1,10 +1,19 @@ import { Hash } from "viem/types/misc"; import { DecodeEventLogReturnType } from "viem/_types/utils/abi/decodeEventLog"; -import { Abi, decodeEventLog, PublicClient, encodeAbiParameters, parseAbiParameters } from "viem"; +import { + Abi, + decodeEventLog, + PublicClient, + encodeAbiParameters, + parseAbiParameters, + Chain, +} from "viem"; import { InferEventName } from "viem/types/contract"; +import { mainnet, polygonMumbai, sepolia } from "viem/chains"; import { Hex, TypedData } from "../types/common"; import { DERIVATIVES_ALLOWED_OPTIONS, PARAMS_TAG } from "../constants/license"; +import { SupportedChainIds } from "../types/config"; export function isIntegerString(s: string): boolean { const num = Number(s); @@ -177,3 +186,20 @@ export function paramsTagValueDecoder(paramTag: Hex, paramValue: unknown) { return { tag: parsedTag, value, type }; } + +export function chainStringToViemChain(chainId: SupportedChainIds): Chain { + switch (chainId) { + case "1": + case "mainnet": + return mainnet; + case "11155111": + case "sepolia": + return sepolia; + case "80001": + case "mumbai": + case "polygonMumbai": + return polygonMumbai; + default: + throw new Error(`chainId ${chainId as string} not supported`); + } +} diff --git a/packages/core-sdk/test/integration/ipAsset.test.ts b/packages/core-sdk/test/integration/ipAsset.test.ts index d2db6941..8f67a533 100644 --- a/packages/core-sdk/test/integration/ipAsset.test.ts +++ b/packages/core-sdk/test/integration/ipAsset.test.ts @@ -1,6 +1,5 @@ import { expect } from "chai"; import { StoryClient, StoryConfig, Client } from "../../src"; -import { sepolia } from "viem/chains"; import { Hex, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; @@ -10,7 +9,7 @@ describe.skip("IP Asset Functions", () => { before(function () { const config: StoryConfig = { - chain: sepolia, + chainId: "sepolia", transport: http(process.env.RPC_PROVIDER_URL), account: privateKeyToAccount((process.env.WALLET_PRIVATE_KEY || "0x") as Hex), }; diff --git a/packages/core-sdk/test/integration/permission.test.ts b/packages/core-sdk/test/integration/permission.test.ts index 606517f9..a11b9b64 100644 --- a/packages/core-sdk/test/integration/permission.test.ts +++ b/packages/core-sdk/test/integration/permission.test.ts @@ -10,7 +10,7 @@ describe("Permission Functions", () => { before(function () { const config: StoryConfig = { - chain: sepolia, + chainId: "sepolia", transport: http(process.env.RPC_PROVIDER_URL), account: privateKeyToAccount((process.env.WALLET_PRIVATE_KEY || "0x") as Hex), }; diff --git a/packages/core-sdk/test/integration/platform.test.ts b/packages/core-sdk/test/integration/platform.test.ts index 5aca9ff3..b1b06146 100644 --- a/packages/core-sdk/test/integration/platform.test.ts +++ b/packages/core-sdk/test/integration/platform.test.ts @@ -1,7 +1,6 @@ import { expect } from "chai"; import { StoryClient, StoryConfig, Client } from "../../src"; import { createFileReaderMock } from "../unit/testUtils"; -import { goerli } from "viem/chains"; import { Hex, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; @@ -16,7 +15,7 @@ describe.skip("Platform client integration tests", () => { beforeEach(function () { const config: StoryConfig = { - chain: goerli, + chainId: "sepolia", transport: http(process.env.RPC_PROVIDER_URL), account: privateKeyToAccount((process.env.WALLET_PRIVATE_KEY || "0x") as Hex), }; diff --git a/packages/core-sdk/test/integration/tagging.test.ts b/packages/core-sdk/test/integration/tagging.test.ts index 9c34895d..f5c2af6f 100644 --- a/packages/core-sdk/test/integration/tagging.test.ts +++ b/packages/core-sdk/test/integration/tagging.test.ts @@ -9,6 +9,7 @@ describe("Tagging Functions", () => { before(function () { const config: StoryConfig = { + chainId: "sepolia", transport: http(process.env.RPC_PROVIDER_URL), account: privateKeyToAccount((process.env.WALLET_PRIVATE_KEY || "0x") as Hex), }; diff --git a/packages/core-sdk/test/integration/taggingReadOnly.test.ts b/packages/core-sdk/test/integration/taggingReadOnly.test.ts index 56256083..56c47f07 100644 --- a/packages/core-sdk/test/integration/taggingReadOnly.test.ts +++ b/packages/core-sdk/test/integration/taggingReadOnly.test.ts @@ -10,6 +10,7 @@ describe("Tagging Indexer Functions", () => { before(function () { const config: StoryConfig = { + chainId: "sepolia", transport: http(process.env.RPC_PROVIDER_URL), account: privateKeyToAccount((process.env.WALLET_PRIVATE_KEY || "0x") as Hex), }; diff --git a/packages/core-sdk/test/unit/client.test.ts b/packages/core-sdk/test/unit/client.test.ts index 3e96b670..ed38fee9 100644 --- a/packages/core-sdk/test/unit/client.test.ts +++ b/packages/core-sdk/test/unit/client.test.ts @@ -5,23 +5,19 @@ import { Account } from "viem"; describe("Test StoryClient", function () { describe("Test constructor", function () { - it("should succeed when passing in valid params", function () { - try { - StoryClient.newClient({ - account: privateKeyToAccount(generatePrivateKey()), - }); - } catch (error) { - expect.fail(`Function should not have thrown any error, but it threw: ${error}`); - } + it("should succeed when passing in default params", function () { + const client = StoryClient.newClient({ + account: privateKeyToAccount(generatePrivateKey()), + }); + expect(client).to.be.instanceOf(StoryClient); }); - it("throw error when wallet account is null", function () { - try { - StoryClient.newClient({ + it("should throw error when wallet account is null", function () { + expect(() => { + const client = StoryClient.newClient({ account: null as any as Account, }); - expect.fail(`Function should not get here, it should throw an error `); - } catch (error) {} + }).to.throw("account is null"); }); }); diff --git a/packages/core-sdk/test/unit/clientReadOnly.test.ts b/packages/core-sdk/test/unit/clientReadOnly.test.ts index 244283c1..3fa772d3 100644 --- a/packages/core-sdk/test/unit/clientReadOnly.test.ts +++ b/packages/core-sdk/test/unit/clientReadOnly.test.ts @@ -1,27 +1,38 @@ import { expect } from "chai"; import { StoryClient, ReadOnlyClient } from "../../src"; -import { fantom } from "viem/chains"; import { http } from "viem"; describe("Test StoryReadOnlyClient", function () { describe("Test constructor", function () { - it("should succeed when passing in valid params", function () { - try { - StoryClient.newReadOnlyClient({}); - } catch (error) { - expect.fail(`Function should not have thrown any error, but it threw: ${error}`); - } + it("should succeed when passing in default params", function () { + const client = StoryClient.newReadOnlyClient({}); + expect(client).to.be.instanceOf(StoryClient); + }); + it("should succeed when passing in chainId = '1' ", function () { + const client = StoryClient.newReadOnlyClient({ chainId: "1" }); + expect(client).to.be.instanceOf(StoryClient); + }); + it("should succeed when passing in chainId = 'mainnet' ", function () { + const client = StoryClient.newReadOnlyClient({ chainId: "mainnet" }); + expect(client).to.be.instanceOf(StoryClient); + }); + it("should succeed when passing in chainId = '11155111' ", function () { + const client = StoryClient.newReadOnlyClient({ chainId: "11155111" }); + expect(client).to.be.instanceOf(StoryClient); + }); + it("should succeed when passing in chainId = 'sepolia' ", function () { + const client = StoryClient.newReadOnlyClient({ chainId: "sepolia" }); + expect(client).to.be.instanceOf(StoryClient); }); - it("should succeed when passing in valid params w/ provider", function () { - try { + it("should fail when passing in unsupported chain ID", function () { + expect(() => StoryClient.newReadOnlyClient({ - chain: fantom, + //@ts-ignore + chainId: "fantom", transport: http(process.env.RPC_PROVIDER_URL), - }); - } catch (error) { - expect.fail(`Function should not have thrown any error, but it threw: ${error}`); - } + }), + ).to.throw(`chainId fantom not supported`); }); }); @@ -40,35 +51,4 @@ describe("Test StoryReadOnlyClient", function () { }); }); }); - - // describe("Test getters w/ provider", function () { - // let client: ReadOnlyClient; - - // beforeEach(function () { - // client = StoryClient.newReadOnlyClient({ - // environment: Environment.TEST, - // provider: new providers.JsonRpcProvider(), - // }); - // }); - - // describe("Test franchise getter w/ provider", function () { - // it("should return the same franchise when every time it's called", function () { - // const franchise1 = client.franchise; - // const franchise2 = client.franchise; - // expect(franchise1).to.be.equal(franchise2); - // }); - // }); - // }); - - // describe("Test franchise getter w/o creating a client", function () { - // let client: ReadOnlyClient; - - // it("should throw error when a client hasn't been created", function () { - // try { - // client.franchise; - - // expect.fail(`You haven't created a client yet.`); - // } catch (error) {} - // }); - // }); });