diff --git a/.env.example b/.env.example index cb8ffa8..8fcdcbf 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,5 @@ -TEST_WALLET_A_ADDRESS = 0x0000000000000000000000000000000000000000 -TEST_WALLET_B_ADDRESS = 0x0000000000000000000000000000000000000000 -TEST_WALLET_C_ADDRESS = 0x0000000000000000000000000000000000000000 -PRIVATE_KEYS = privateKey1,privateKey2,privateKey3 -NFT_CONTRACT_ADDRESS = 0x0000000000000000000000000000000000000000 -RPC_URL = https://rpc.ankr.com/eth_sepolia -LICENSE_MODULE = 0x0000000000000000000000000000000000000000 -MINT_FEE_TOKEN = 0x0000000000000000000000000000000000000000 -ROYALTY_POLICY = 0x0000000000000000000000000000000000000000 \ No newline at end of file +RPC_PROVIDER_URL=https://rpc.ankr.com/eth_sepolia +WALLET_PRIVATE_KEY_A=0x0000000000000000000000000000000000000000 +WALLET_PRIVATE_KEY_B=0x0000000000000000000000000000000000000000 +WALLET_PRIVATE_KEY_C=0x0000000000000000000000000000000000000000 +MY_NFT_CONTRACT_ADDRESS=0x0000000000000000000000000000000000000000 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 438657a..fb9d4fe 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ pnpm-debug.log* lerna-debug.log* node_modules +mochawesome-report dist dist-ssr *.local @@ -23,3 +24,4 @@ dist-ssr *.njsproj *.sln *.sw? +package-lock.json diff --git a/README b/README index 9ddbdd6..386357c 100644 --- a/README +++ b/README @@ -18,15 +18,13 @@ This project dedicates to perform e2e test for the story protocol typescript sdk - Run specific test (recommend): - run `npm run test:flow1` + run `mocha -r ts-node/register -r mocha-steps ./test/**/**/*.test.ts --timeout 240000` - Run all test together: run `npm run test` -## How can I extend test flows to this project +- Run all test together: + + run `npm run open:report` -- wrap your test flow into a function and default export this function. -- put the new test file into `/test/e2e/flows` -- import the function from `/test/e2e/e2e.ts`, and register it into `flowsTestMap` -- extend the `scripts` field of the `/package.json` file with a reference to the existing other scrip command, and finally add the flow name to `TEST_FLOWS` in 'test' of the 'scripts' diff --git a/abi/mintNFT.json b/abi/mintNFT.json deleted file mode 100644 index 7069b6a..0000000 --- a/abi/mintNFT.json +++ /dev/null @@ -1,234 +0,0 @@ -[ - { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, - { - "inputs": [ - { "internalType": "address", "name": "sender", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, - { "internalType": "address", "name": "owner", "type": "address" } - ], - "name": "ERC721IncorrectOwner", - "type": "error" - }, - { - "inputs": [ - { "internalType": "address", "name": "operator", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "ERC721InsufficientApproval", - "type": "error" - }, - { - "inputs": [{ "internalType": "address", "name": "approver", "type": "address" }], - "name": "ERC721InvalidApprover", - "type": "error" - }, - { - "inputs": [{ "internalType": "address", "name": "operator", "type": "address" }], - "name": "ERC721InvalidOperator", - "type": "error" - }, - { - "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }], - "name": "ERC721InvalidOwner", - "type": "error" - }, - { - "inputs": [{ "internalType": "address", "name": "receiver", "type": "address" }], - "name": "ERC721InvalidReceiver", - "type": "error" - }, - { - "inputs": [{ "internalType": "address", "name": "sender", "type": "address" }], - "name": "ERC721InvalidSender", - "type": "error" - }, - { - "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "name": "ERC721NonexistentToken", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "approved", "type": "address" }, - { "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "operator", "type": "address" }, - { "indexed": false, "internalType": "bool", "name": "approved", "type": "bool" } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, - { "indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [], - "name": "DEFAULT_URI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "approve", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }], - "name": "balanceOf", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "name": "burn", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "name": "getApproved", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "owner", "type": "address" }, - { "internalType": "address", "name": "operator", "type": "address" } - ], - "name": "isApprovedForAll", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "to", "type": "address" }], - "name": "mint", - "outputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "string", "name": "customUri", "type": "string" } - ], - "name": "mint", - "outputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "mintBatch", - "outputs": [{ "internalType": "uint256[]", "name": "", "type": "uint256[]" }], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "name": "ownerOf", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, - { "internalType": "bytes", "name": "data", "type": "bytes" } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "operator", "type": "address" }, - { "internalType": "bool", "name": "approved", "type": "bool" } - ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [{ "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" }], - "name": "supportsInterface", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }], - "name": "tokenURI", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "tokenId", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/config/config.ts b/config/config.ts index d62a598..17dbb98 100644 --- a/config/config.ts +++ b/config/config.ts @@ -1,54 +1,25 @@ -import "dotenv/config"; -import { - Hex, - createWalletClient, - http, - PrivateKeyAccount, -} from "viem"; +import { Hex, http, Address } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { StoryClient, StoryConfig } from "@story-protocol/core-sdk"; import { sepolia } from "viem/chains"; -export const getStoryConfig = (account: PrivateKeyAccount) => ({ - transport: http(RPC_URL), - account, -}) as StoryConfig; +export const transport = http(process.env.RPC_PROVIDER_URL); +export const privateKeyA = process.env.WALLET_PRIVATE_KEY_A as Hex; +export const privateKeyB = process.env.WALLET_PRIVATE_KEY_B as Hex; -export const TOKEN_CONTRACT_ADDRESS = "0x7ee32b8B515dEE0Ba2F25f612A04a731eEc24F49"; -export const TEST_WALLET_A_ADDRESS = (process.env.TEST_WALLET_A_ADDRESS || "0x") as `0x${string}`; -export const TEST_WALLET_B_ADDRESS = (process.env.TEST_WALLET_B_ADDRESS || "0x") as `0x${string}`; -export const TEST_WALLET_C_ADDRESS = (process.env.TEST_WALLET_C_ADDRESS || "0x") as `0x${string}`; -export const NFT_CONTRACT_ADDRESS = (process.env.NFT_CONTRACT_ADDRESS || "0x") as `0x${string}`; -export const RPC_URL = process.env.RPC_URL || ""; -export const LICENSE_MODULE = (process.env.LICENSE_MODULE || "") as `0x${string}`; -export const MINT_FEE_TOKEN = (process.env.MINT_FEE_TOKEN || "") as `0x${string}`; -export const ROYALTY_POLICY = (process.env.ROYALTY_POLICY || "") as `0x${string}`; +export const accountA = privateKeyToAccount(privateKeyA as Address); +export const accountB = privateKeyToAccount(privateKeyB as Address); -export const accountA = privateKeyToAccount((process.env.PRIVATE_KEYS?.split(',')[0] || '0x') as Hex); -export const accountB = privateKeyToAccount((process.env.PRIVATE_KEYS?.split(',')[1] || '0x') as Hex); -export const accountC = privateKeyToAccount((process.env.PRIVATE_KEYS?.split(',')[2] || '0x') as Hex); - -const configA: StoryConfig = getStoryConfig(accountA); -const configB: StoryConfig = getStoryConfig(accountB); -const configC: StoryConfig = getStoryConfig(accountC); - -export const storyClientA: StoryClient = StoryClient.newClient(configA); -export const storyClientB: StoryClient = StoryClient.newClient(configB); -export const storyClientC: StoryClient = StoryClient.newClient(configC); - -export const walletClientA = createWalletClient({ - transport: http(RPC_URL), - chain: sepolia, +export const configA: StoryConfig = { account: accountA, -}); -export const walletClientB = createWalletClient({ - transport: http(RPC_URL), - chain: sepolia, + transport: transport, +} + +export const configB: StoryConfig = { account: accountB, -}); -export const walletClientC = createWalletClient({ - transport: http(RPC_URL), - chain: sepolia, - account: accountC, -}); + transport: transport, +} + +export const clientA = StoryClient.newClient(configA) +export const clientB = StoryClient.newClient(configB) \ No newline at end of file diff --git a/package.json b/package.json index 2ce80d6..3820ddc 100644 --- a/package.json +++ b/package.json @@ -3,16 +3,9 @@ "version": "1.0.0", "description": "", "main": "index.js", - "type": "module", "scripts": { - "test:flow1": "TEST_FLOWS=flow1 tsx test/e2e/e2e.ts", - "test:flow2": "TEST_FLOWS=flow2 tsx test/e2e/e2e.ts", - "test:flow3": "TEST_FLOWS=flow3 tsx test/e2e/e2e.ts", - "test:flow4": "TEST_FLOWS=flow4 tsx test/e2e/e2e.ts", - "test:flow5": "TEST_FLOWS=flow5 tsx test/e2e/e2e.ts", - "test:flow6": "TEST_FLOWS=flow6 tsx test/e2e/e2e.ts", - "test:flow7": "TEST_FLOWS=flow7 tsx test/e2e/e2e.ts", - "test": "TEST_FLOWS=flow1,flow2,flow3,flow4,flow5,flow6,flow7 tsx test/e2e/e2e.ts" + "test": "mocha -r ts-node/register -r mocha-steps './test/**/**/*.test.ts' --timeout 240000 --reporter mochawesome || true", + "open:report": "open mochawesome-report/mochawesome.html" }, "engines": { "node": "20.0.0" @@ -23,14 +16,25 @@ }, "dependencies": { "@story-protocol/core-sdk": "0.0.1-beta-rc.9", - "dotenv": "^16.4.5", - "tsx": "^4.7.1", - "viem": "^1.18.4" + "viem": "^1.21.4" }, "devDependencies": { - "@types/node": "^20.11.22", - "npm-run-all": "^4.1.5", - "typescript": "^5.2.2" + "@types/chai": "^4.3.12", + "@types/chai-as-promised": "^7.1.6", + "@types/mocha": "^10.0.2", + "@types/mocha-steps": "^1.3.3", + "@types/node": "^20.8.2", + "@types/sinon": "^10.0.18", + "chai": "^4.3.10", + "chai-as-promised": "^7.1.1", + "eslint": "^8.50.0", + "mocha": "^10.2.0", + "mocha-steps": "^1.3.0", + "mochawesome": "^7.1.3", + "prettier": "^2.8.8", + "ts-node": "^10.9.2", + "typechain": "^8.3.1", + "typescript": "^5.4.2" }, "author": "storyprotocol engineering ", "license": "MIT", @@ -38,4 +42,4 @@ "url": "https://github.com/storyprotocol/sdk-e2e-tests/issues" }, "homepage": "https://github.com/storyprotocol/sdk-e2e-tests#readme" -} +} \ No newline at end of file diff --git a/test/e2e/derivativeIP.test.ts b/test/e2e/derivativeIP.test.ts new file mode 100644 index 0000000..b10eed9 --- /dev/null +++ b/test/e2e/derivativeIP.test.ts @@ -0,0 +1,100 @@ +import { privateKeyA, privateKeyB, accountA, accountB } from '../../config/config' +import { mintNFT, sleep } from '../../utils/utils' +import { registerRootIp, registerPILPolicy, addPolicyToIp, mintLicense, registerDerivativeIp } from '../../utils/sdkUtils' +import { expect } from 'chai' + +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +chai.use(chaiAsPromised); + +let tokenIdA: any +let tokenIdB: any +let ipIdA: any +let policyId: any +let licenseId: any + +const waitForTransaction: boolean = true; + +describe('SDK E2E Test', function () { + describe('Register a derivative IP asset ', async function () { + step("Mint a NFT to Wallet A", async function () { + tokenIdA = await mintNFT(privateKeyA); + expect(tokenIdA).not.empty + + await sleep(10) + }) + + step("Wallet A register a root IP Asset with policy id O", async function () { + const response = await expect( + registerRootIp("A", "0", tokenIdA, waitForTransaction) + ).to.not.be.rejected + + expect(response.txHash).to.be.a("string"); + expect(response.txHash).not.empty; + expect(response.ipId).to.be.a("string"); + expect(response.ipId).not.empty; + + ipIdA = response.ipId + await sleep(10) + }); + + step("Create a policy with derivativesAllowed true", async function () { + const policyOptions = { + attribution: true, + derivativesAllowed: true, + derivativesAttribution: true, + derivativesReciprocal: true + } + const response = await expect( + registerPILPolicy("A", true, waitForTransaction, policyOptions) + ).to.not.be.rejected; + + expect(response.policyId).to.be.a("string"); + expect(response.policyId).not.empty; + + policyId = response.policyId + }); + + step("Add policy to the IP asset", async function () { + const response = await expect( + addPolicyToIp("A", ipIdA, policyId, waitForTransaction) + ).to.not.be.rejected; + + expect(response.txHash).to.be.a("string"); + expect(response.txHash).not.empty; + expect(response.index).to.be.a("string"); + expect(response.index).not.empty; + }); + + step("Wallet A mint a license with the receiverAddress set as Wallet B", async function () { + const response = await expect( + mintLicense("A", policyId, ipIdA, accountB.address, waitForTransaction) + ).to.not.be.rejected; + + expect(response.txHash).to.be.a("string"); + expect(response.txHash).not.empty; + expect(response.licenseId).to.be.a("string"); + expect(response.licenseId).not.empty; + + licenseId = response.licenseId + await sleep(10) + }); + + step("Mint a NFT to WalletB", async function () { + tokenIdB = await mintNFT(privateKeyB); + expect(tokenIdB).not.empty + await sleep(10) + }) + + step("Wallet B register a derivative IP asset", async function () { + const response = await expect( + registerDerivativeIp("B", [licenseId], tokenIdB, waitForTransaction) + ).to.not.be.rejected; + + expect(response.txHash).to.be.a("string"); + expect(response.txHash).not.empty; + expect(response.ipId).to.be.a("string"); + expect(response.ipId).not.empty; + }) + }); +}); \ No newline at end of file diff --git a/test/e2e/e2e.ts b/test/e2e/e2e.ts deleted file mode 100644 index 861034a..0000000 --- a/test/e2e/e2e.ts +++ /dev/null @@ -1,32 +0,0 @@ -import testFlow1 from './flows/flow1'; -import testFlow2 from './flows/flow2'; -import testFlow3 from './flows/flow3'; -import testFlow4 from './flows/flow4'; -import testFlow5 from './flows/flow5'; -import testFlow6 from './flows/flow6'; -import testFlow7 from './flows/flow7'; - -type flowsTestMapType = { - [key: string]: Function; -}; -const flowsTestMap: flowsTestMapType = { - flow1: testFlow1, - flow2: testFlow2, - flow3: testFlow3, - flow4: testFlow4, - flow5: testFlow5, - flow6: testFlow6, - flow7: testFlow7, -}; -process.env.TEST_FLOWS?.split(',').forEach(async (flow: keyof flowsTestMapType) => { - if (flowsTestMap[flow]) { - try { - console.log(`testing ${flow}...`); - await flowsTestMap[flow](); - console.log(`${flow} succeeded`); - } catch (err) { - console.log(`${flow} failed with error: ${(err as Error).message}`); - throw new Error(`${flow} failed with error: ${(err as Error).message}`); - } - } -}); diff --git a/test/e2e/linkIP.test.ts b/test/e2e/linkIP.test.ts new file mode 100644 index 0000000..a1e7b70 --- /dev/null +++ b/test/e2e/linkIP.test.ts @@ -0,0 +1,87 @@ +import { privateKeyA, privateKeyB, accountA, accountB } from '../../config/config' +import { mintNFT, sleep } from '../../utils/utils' +import { registerRootIp, mintLicense, registerDerivativeIp, linkIpToParent } from '../../utils/sdkUtils' +import { expect } from 'chai' + +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +chai.use(chaiAsPromised); + +let tokenIdA: any +let tokenIdB: any +let ipIdA: any +let ipIdB: any +let policyId: any +let licenseId: any + +const waitForTransaction: boolean = true; + +describe('SDK E2E Test', function () { + describe('Link an IP asset as a derivative to another IP asset', async function () { + step("Mint a NFT to Wallet A", async function () { + tokenIdA = await mintNFT(privateKeyA); + expect(tokenIdA).not.empty + + await sleep(10) + }) + + step("Wallet A register a root IP Asset (IP1)", async function () { + const response = await expect( + registerRootIp("A", "1", tokenIdA, waitForTransaction) + ).to.not.be.rejected + + expect(response.txHash).to.be.a("string"); + expect(response.txHash).not.empty; + expect(response.ipId).to.be.a("string"); + expect(response.ipId).not.empty; + + ipIdA = response.ipId + await sleep(10) + }); + + step("Wallet A mint a license with the receiverAddress set as Wallet B", async function () { + const response = await expect( + mintLicense("A", policyId, ipIdA, accountB.address, waitForTransaction) + ).to.not.be.rejected; + + expect(response.txHash).to.be.a("string"); + expect(response.txHash).not.empty; + expect(response.licenseId).to.be.a("string"); + expect(response.licenseId).not.empty; + + licenseId = response.licenseId + await sleep(10) + }); + + step("Mint a NFT to WalletB", async function () { + tokenIdB = await mintNFT(privateKeyB); + expect(tokenIdB).not.empty + await sleep(10) + }) + + step("Wallet B register a root IP Asset (IP2)", async function () { + const response = await expect( + registerRootIp("B", "1", tokenIdB, waitForTransaction) + ).to.not.be.rejected + + expect(response.txHash).to.be.a("string"); + expect(response.txHash).not.empty; + expect(response.ipId).to.be.a("string"); + expect(response.ipId).not.empty; + + ipIdB = response.ipId + await sleep(10) + }); + + step("IP2 link to IP1 as it's derivative", async function () { + const response = await expect( + linkIpToParent("B", [licenseId], ipIdB, waitForTransaction) + ).to.not.be.rejected; + + expect(response.txHash).to.be.a("string"); + expect(response.txHash).not.empty; + expect(response.ipId).to.be.a("string"); + expect(response.ipId).not.empty; + }) + }); +}); \ No newline at end of file diff --git a/test/policy/policy.test.ts b/test/policy/policy.test.ts new file mode 100644 index 0000000..d924bfa --- /dev/null +++ b/test/policy/policy.test.ts @@ -0,0 +1,45 @@ +import { privateKeyA, privateKeyB, accountA, accountB } from '../../config/config' +import { mintNFT, sleep } from '../../utils/utils' +import { registerPILPolicy } from '../../utils/sdkUtils' +import { expect } from 'chai' + +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +chai.use(chaiAsPromised); + +let tokenIdA: any +let tokenIdB: any +let ipIdA: any +let ipIdB: any +let policyId: any +let licenseId: any + +const waitForTransaction: boolean = true; + +describe('SDK Test', function () { + describe('Test Policy Function', async function () { + it("Create a policy with derivativesAllowed true", async function () { + const policyOptions = { + attribution: true, + derivativesAllowed: true, + derivativesAttribution: true, + derivativesReciprocal: true + } + const response = await expect( + registerPILPolicy("A", true, waitForTransaction, policyOptions) + ).to.not.be.rejected; + + expect(response.policyId).to.be.a("string"); + expect(response.policyId).not.empty; + }); + + it("Create a policy with all parameters false", async function () { + const response = await expect( + registerPILPolicy("A", false, waitForTransaction) + ).to.not.be.rejected; + + expect(response.policyId).to.be.a("string"); + expect(response.policyId).not.empty; + }); + }); +}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 2aea5fa..a950a9a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,29 +2,10 @@ "exclude": ["node_modules"], "compilerOptions": { "composite": true, + "module": "commonjs", "target": "ES2020", - "module": "ESNext", - "lib": ["dom", "ESNext"], - "importHelpers": true, - "declaration": true, - "sourceMap": true, - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictPropertyInitialization": true, - "noImplicitThis": true, - "alwaysStrict": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "moduleResolution": "node", - "resolveJsonModule": true, "esModuleInterop": true, - "skipLibCheck": true, - }, - "ts-node": { - "esm": true, - "experimentalSpecifierResolution": "node" + "strict": true }, "include": ["test", "config", "utils"], } \ No newline at end of file diff --git a/utils/sdkUtils.ts b/utils/sdkUtils.ts new file mode 100644 index 0000000..feb3ff6 --- /dev/null +++ b/utils/sdkUtils.ts @@ -0,0 +1,121 @@ +import { Hex, Address } from "viem"; +import { clientA, clientB, } from '../config/config' + +const storyClients = { + A: clientA, + B: clientB, +}; + +function getStoryClient(wallet: keyof typeof storyClients) { + return storyClients[wallet]; +} + +interface PolicyOptions { + [key: string]: any; +} + +export const registerRootIp = async function (wallet: keyof typeof storyClients, policyId: string, tokenId: string, waitForTransaction: boolean) { + try { + const storyClient = getStoryClient(wallet); + const response = await storyClient.ipAsset.registerRootIp({ + policyId: policyId, + tokenContractAddress: process.env.MY_NFT_CONTRACT_ADDRESS as Address, + tokenId: tokenId, + txOptions: { + waitForTransaction: waitForTransaction + } + }) + console.log(response) + return response + } catch (error) { + console.log(error) + } +} + +export const registerPILPolicy = async function (wallet: keyof typeof storyClients, transferable: boolean, waitForTransaction: boolean, options?: PolicyOptions) { + try { + const storyClient = getStoryClient(wallet); + const response = await storyClient.policy.registerPILPolicy({ + transferable: transferable, + ...options, + txOptions: { + waitForTransaction: waitForTransaction, + } + }) + console.log(response) + return response + } catch (error) { + console.log(error) + } +} + +export const addPolicyToIp = async function (wallet: keyof typeof storyClients, ipId: Hex, policyId: string, waitForTransaction: boolean) { + try { + const storyClient = getStoryClient(wallet); + const response = await storyClient.policy.addPolicyToIp({ + ipId: ipId, + policyId: policyId, + txOptions: { + waitForTransaction: waitForTransaction, + } + }) + console.log(response) + return response + } catch (error) { + console.log(error) + } +} + +export const mintLicense = async function (wallet: keyof typeof storyClients, policyId: string, ipId: Hex, receiverAddress: Hex, waitForTransaction: boolean) { + try { + const storyClient = getStoryClient(wallet); + const response = await storyClient.license.mintLicense({ + policyId: policyId, + licensorIpId: ipId, + mintAmount: 1, + receiverAddress: receiverAddress, + txOptions: { + waitForTransaction: waitForTransaction, + } + }) + console.log(response) + return response + } catch (error) { + console.log(error) + } +} + +export const registerDerivativeIp = async function (wallet: keyof typeof storyClients, licenseIds: string[], tokenId: string, waitForTransaction: boolean) { + try { + const storyClient = getStoryClient(wallet); + const response = await storyClient.ipAsset.registerDerivativeIp({ + licenseIds: licenseIds, + tokenContractAddress: process.env.MY_NFT_CONTRACT_ADDRESS as Address, + tokenId: tokenId, + txOptions: { + waitForTransaction: waitForTransaction, + }, + }) + console.log(response) + return response + } catch (error) { + console.log(error) + } +} + +export const linkIpToParent = async function (wallet: keyof typeof storyClients, licenseIds: string[], childIpId: Hex, waitForTransaction: boolean) { + try { + const storyClient = getStoryClient(wallet); + const response = await storyClient.license.linkIpToParent({ + licenseIds: licenseIds, + childIpId: childIpId, + txOptions: { + waitForTransaction: waitForTransaction, + }, + }) + console.log(response) + return response + } catch (error) { + console.log(error) + } +} diff --git a/utils/utils.ts b/utils/utils.ts index 7c398e5..a6d426e 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -1,259 +1,59 @@ -import { http, Hex, createPublicClient } from 'viem'; -import { PrivateKeyAccount } from 'viem/accounts'; -import type { StoryConfig } from '@story-protocol/core-sdk'; +import { Hex, http, Address, createWalletClient, createPublicClient } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' import { sepolia } from 'viem/chains'; -import { - walletClientA, - walletClientB, - walletClientC, - accountA, - accountB, - accountC, - storyClientA, - storyClientB, - storyClientC, - TEST_WALLET_A_ADDRESS, - TEST_WALLET_B_ADDRESS, - TEST_WALLET_C_ADDRESS, - NFT_CONTRACT_ADDRESS, - MINT_FEE_TOKEN, - ROYALTY_POLICY, - LICENSE_MODULE, - RPC_URL, -} from '../config/config'; -import mintNFTAbi from '../abi/mintNFT.json'; - -export type Who = 'A' | 'B' | 'C'; - -const publicClient = createPublicClient({ - chain: sepolia, - transport: http(), -}); -export async function getNFTId(txHash: Hex) { - const { logs } = await publicClient.waitForTransactionReceipt({ - hash: txHash, - }); - if (logs[0].topics[3]) { - const tokenId = parseInt(logs[0].topics[3], 16); - return tokenId; - } - return 0; -} -function getWalletAddress(who: Who = 'A') { - let walletAddress = TEST_WALLET_A_ADDRESS; - if (who === 'B') walletAddress = TEST_WALLET_B_ADDRESS; - if (who === 'C') walletAddress = TEST_WALLET_C_ADDRESS; - return walletAddress; -} -export async function mintNFT(who: Who): Promise { - let client = walletClientA; - let account = accountA; - if (who === 'B') { - client = walletClientB; - account = accountB; - } - if (who === 'C') { - client = walletClientC; - account = accountC; - } - const walletAddress = getWalletAddress(who); - const txHash = await client.writeContract({ - account, - address: NFT_CONTRACT_ADDRESS, - chain: sepolia, - abi: mintNFTAbi, - functionName: 'mint', - args: [walletAddress], - }); - console.log('mintNFTHash:', txHash); - await sleep(5); - const tokenId = await getNFTId(txHash); - console.log(`tokenId of ${who}`, tokenId); - return String(tokenId); -} - -function getStoryClient(who?: Who) { - let storyClient = storyClientA; - if (who === 'B') storyClient = storyClientB; - if (who === 'C') storyClient = storyClientC; - return storyClient; -} - -function getReceiverAddress(receiver?: Who) { - let receiverAddress = TEST_WALLET_A_ADDRESS; - if (receiver === 'B') receiverAddress = TEST_WALLET_B_ADDRESS; - if (receiver === 'C') receiverAddress = TEST_WALLET_C_ADDRESS; - return receiverAddress; -} export function sleep(second: number) { return new Promise((resolve) => setTimeout(resolve, second * 1000)); } -export const registerRootIp = async (tokenId: string, who: Who = 'A') => { - const storyClient = getStoryClient(who); - const response = await storyClient.ipAsset.registerRootIp({ - tokenContractAddress: NFT_CONTRACT_ADDRESS, - tokenId, - txOptions: { - waitForTransaction: true, - }, - }); - console.log(`${who} registered ip for NFT id ${tokenId}: `, response); - return response.ipId; -}; - -export const registerIpWithExistingPolicy = async (tokenId: string, policyId: string, who: Who = 'A') => { - const storyClient = getStoryClient(who); - const response = await storyClient.ipAsset.registerRootIp({ - policyId, - tokenContractAddress: NFT_CONTRACT_ADDRESS, - tokenId, - txOptions: { - waitForTransaction: true, - }, - }); - console.log(`${who}registered ip for NFT id ${tokenId} with policy${policyId}: `, response); - - return response.ipId; -}; - -export const registerSocialRemixPolicy = async () => { - const response = await storyClientA.policy.registerPILPolicy({ - transferable: true, - attribution: true, - derivativesAllowed: true, - derivativesAttribution: true, - derivativesApproval: false, - derivativesReciprocal: true, - txOptions: { - waitForTransaction: true, - }, - }); - console.log('registerSocialRemixPolicy', response); - return response.policyId; -}; - -export const registerSocialRemixPolicy2 = async () => { - const response = await storyClientA.policy.registerPILPolicy({ - transferable: true, - attribution: true, - derivativesAllowed: true, - derivativesAttribution: false, - derivativesApproval: false, - derivativesReciprocal: true, - txOptions: { - waitForTransaction: true, - }, - }); - console.log('registerSocialRemixPolicy2', response); - return response.policyId; -}; - -export const registerSocialRemixPolicy3 = async () => { - const response = await storyClientA.policy.registerPILPolicy({ - transferable: true, - attribution: true, - derivativesAllowed: true, - derivativesAttribution: true, - derivativesApproval: false, - derivativesReciprocal: false, - txOptions: { - waitForTransaction: true, - }, - }); - console.log('registerSocialRemixPolicy3', response); - return response.policyId; -}; - -export const registerCommercialUsePolicy = async () => { - const response = await storyClientA.policy.registerPILPolicy({ - transferable: true, - mintingFeeToken: MINT_FEE_TOKEN, - mintingFee: '1000000000000000000', - royaltyPolicy: ROYALTY_POLICY, - commercialRevShare: 100, - attribution: true, - commercialUse: true, - commercialAttribution: true, - derivativesAllowed: true, - derivativesReciprocal: true, - txOptions: { - waitForTransaction: true, - }, - }); +export async function mintNFT(WALLET_PRIVATE_KEY: Hex): Promise { + console.log('Minting a new NFT...') - console.log('register Commercial Use Policy: ', response); - return response.policyId; -}; + const account = privateKeyToAccount(WALLET_PRIVATE_KEY as Address) + const walletClient = createWalletClient({ + account, + chain: sepolia, + transport: http(), + }) + const publicClient = createPublicClient({ + chain: sepolia, + transport: http() + }) + const contractAbi = { + inputs: [{ internalType: 'address', name: 'to', type: 'address' }], + name: 'mint', + outputs: [ + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + } -export const addOnePolicyToIp = async (ipId: Hex, policyId: string, who: Who = 'A') => { - const storyClient = getStoryClient(who); - const response = await storyClient.policy.addPolicyToIp({ - policyId, - ipId, - txOptions: { - waitForTransaction: true, - }, - }); - console.log(`added policy ${policyId} to IP ${ipId}, which belong to wallet ${who}: `, response); -}; + // 3. Mint an NFT to your account + const { result } = await publicClient.simulateContract({ + address: process.env.MY_NFT_CONTRACT_ADDRESS as Address, + functionName: 'mint', + args: [account.address], + abi: [contractAbi] + }) + const hash = await walletClient.writeContract({ + address: process.env.MY_NFT_CONTRACT_ADDRESS as Address, + functionName: 'mint', + args: [account.address], + abi: [contractAbi] + }) -export const grantIp = async (ipId: Hex, receiver: Who = 'B', promoter: Who = 'A') => { - const storyClient = getStoryClient(promoter); - const receiverAddress = getReceiverAddress(receiver); - const response = await storyClient.permission.setPermission({ - ipId, - signer: receiverAddress, - to: LICENSE_MODULE, - func: '0x00000000', - // permission level can be 0 (ABSTAIN), 1 (ALLOW), or * 2 (DENY) - permission: 1, - txOptions: { - waitForTransaction: true, - }, + const { logs } = await publicClient.waitForTransactionReceipt({ + hash: hash, }); - console.log(`${promoter} grant ${receiver} permission for IP ${ipId}`, response); -}; -export const mintLicense = async (ipId: Hex, policyId: string, receiver: Who = 'B', promoter: Who = 'A') => { - const storyClient = getStoryClient(promoter); - const receiverAddress = getReceiverAddress(receiver); - const response = await storyClient.license.mintLicense({ - policyId, - licensorIpId: ipId, - mintAmount: 1, - receiverAddress, - txOptions: { - waitForTransaction: true, - }, - }); - console.log(`${promoter} mint license with policy ${policyId} to ${receiver} derived from IP ${ipId}: `, response); - return response.licenseId; -}; + let tokenId: any + if (logs[0].topics[3]) { + tokenId = parseInt(logs[0].topics[3], 16); + } -export const linkIpToParent = async (childIpId: Hex, licenseIds: string[], who: Who = 'B') => { - const storyClient = getStoryClient(who); - const response = await storyClient.license.linkIpToParent({ - licenseIds, - childIpId, - txOptions: { - waitForTransaction: true, - }, - }); - console.log(`${who} linkIpToParent:`, response); -}; + console.log(`Minted NFT successful with hash: ${hash}`); + console.log(`Minted NFT tokenId: ${tokenId}`); -export const registerDerivativeIP = async (tokenId: string, licenseIds: string[], who: Who = 'A') => { - const storyClient = getStoryClient(who); - const response = await storyClient.ipAsset.registerDerivativeIp({ - licenseIds, - tokenContractAddress: NFT_CONTRACT_ADDRESS, - tokenId, - txOptions: { - waitForTransaction: true, - }, - }); - console.log(`${who} RegisterDerivativeIP with licenses ${licenseIds}: `, response); - return response.ipId; -}; + return String(tokenId); +} \ No newline at end of file