diff --git a/lib/contracts/abi.ts b/lib/contracts/abi.ts index 9f8806d..923f8bd 100644 --- a/lib/contracts/abi.ts +++ b/lib/contracts/abi.ts @@ -16,6 +16,7 @@ export const plotChainedEvent = { { name: "storylineId", type: "uint256", indexed: true }, { name: "plotIndex", type: "uint256", indexed: true }, { name: "writer", type: "address", indexed: true }, + { name: "title", type: "string", indexed: false }, { name: "contentCID", type: "string", indexed: false }, { name: "contentHash", type: "bytes32", indexed: false }, ], @@ -68,6 +69,7 @@ export const chainPlotFunction = { stateMutability: "nonpayable", inputs: [ { name: "storylineId", type: "uint256" }, + { name: "title", type: "string" }, { name: "contentCID", type: "string" }, { name: "contentHash", type: "bytes32" }, ], diff --git a/lib/contracts/constants.ts b/lib/contracts/constants.ts index 462bea6..a52e871 100644 --- a/lib/contracts/constants.ts +++ b/lib/contracts/constants.ts @@ -27,7 +27,7 @@ export const EXPLORER_URL = IS_TESTNET /** StoryFactory — storyline + plot management */ export const STORY_FACTORY = (process.env.NEXT_PUBLIC_CONTRACT_ADDRESS ?? (IS_TESTNET - ? "0x05C4d59529807316D6fA09cdaA509adDfe85b474" + ? "0x6B8d38af1773dd162Ebc6f4A8eb923F3c669605d" : "0x0000000000000000000000000000000000000000")) as `0x${string}`; /** ZapPlotLinkMCV2 — one-click buy (ETH/USDC/HUNT -> storyline token) */ diff --git a/packages/cli/src/commands/chain.ts b/packages/cli/src/commands/chain.ts index b90d7a7..1a9ee68 100644 --- a/packages/cli/src/commands/chain.ts +++ b/packages/cli/src/commands/chain.ts @@ -8,14 +8,15 @@ export function registerChain(program: Command): void { .description("Chain a new plot onto an existing storyline") .requiredOption("-s, --storyline ", "Storyline ID") .requiredOption("-f, --file ", "Path to content file (plain text)") - .action(async (opts: { storyline: string; file: string }) => { + .option("-t, --title ", "Chapter title", "") + .action(async (opts: { storyline: string; file: string; title: string }) => { try { const content = readFileSync(opts.file, "utf-8"); const storylineId = BigInt(opts.storyline); const client = buildClient({ ipfs: true }); console.log(`Chaining plot onto storyline ${storylineId}...`); - const result = await client.chainPlot(storylineId, content); + const result = await client.chainPlot(storylineId, content, opts.title); console.log("Plot chained!"); console.log(` TX: ${result.txHash}`); diff --git a/packages/sdk/src/abi.ts b/packages/sdk/src/abi.ts index 37475f2..b2aa4c0 100644 --- a/packages/sdk/src/abi.ts +++ b/packages/sdk/src/abi.ts @@ -18,6 +18,7 @@ export const storyFactoryAbi = [ { name: "storylineId", type: "uint256", indexed: true }, { name: "plotIndex", type: "uint256", indexed: true }, { name: "writer", type: "address", indexed: true }, + { name: "title", type: "string", indexed: false }, { name: "contentCID", type: "string", indexed: false }, { name: "contentHash", type: "bytes32", indexed: false }, ], @@ -63,6 +64,7 @@ export const storyFactoryAbi = [ stateMutability: "nonpayable", inputs: [ { name: "storylineId", type: "uint256" }, + { name: "title", type: "string" }, { name: "contentCID", type: "string" }, { name: "contentHash", type: "bytes32" }, ], diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index d0df732..3c49067 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -254,11 +254,13 @@ export class PlotLink { * * @param storylineId - The storyline to chain onto * @param content - Plot content (plain text) + * @param title - Optional chapter title (defaults to empty string) * @returns Transaction hash and IPFS CID */ async chainPlot( storylineId: bigint, content: string, + title = "", ): Promise<ChainPlotResult> { this.requireFilebase(); validateNonEmpty("content", content); @@ -272,7 +274,7 @@ export class PlotLink { address: this.storyFactory, abi: storyFactoryAbi, functionName: "chainPlot", - args: [storylineId, contentCid, contentHash], + args: [storylineId, title, contentCid, contentHash], }); const txHash = await this.walletClient.writeContract(request); diff --git a/packages/sdk/src/constants.ts b/packages/sdk/src/constants.ts index 1af7160..b72f7cb 100644 --- a/packages/sdk/src/constants.ts +++ b/packages/sdk/src/constants.ts @@ -31,7 +31,7 @@ export const SUPPORTED_CHAIN_IDS = new Set([BASE_SEPOLIA_CHAIN_ID, BASE_MAINNET_ /** StoryFactory — storyline + plot management. */ export const STORY_FACTORY_ADDRESS = - "0x05C4d59529807316D6fA09cdaA509adDfe85b474" as const; + "0x6B8d38af1773dd162Ebc6f4A8eb923F3c669605d" as const; /** MCV2_Bond — bonding curve trading, token creation, royalty distribution. */ export const MCV2_BOND_ADDRESS = diff --git a/src/app/chain/page.tsx b/src/app/chain/page.tsx index ba89348..1a13f2f 100644 --- a/src/app/chain/page.tsx +++ b/src/app/chain/page.tsx @@ -41,6 +41,7 @@ async function fetchWriterStorylines(address: string): Promise<Storyline[]> { export default function ChainPlotPage() { const { address, isConnected } = useAccount(); const [storylineId, setStorylineId] = useState<number | null>(null); + const [title, setTitle] = useState(""); const [content, setContent] = useState(""); const { data: storylines = [], isLoading: loadingStorylines } = useQuery({ @@ -103,7 +104,7 @@ export default function ChainPlotPage() { <form onSubmit={(e) => { e.preventDefault(); - if (canSubmit) chainPlot(storylineId, content); + if (canSubmit) chainPlot(storylineId, content, title); }} className="mt-8 space-y-6" > @@ -135,6 +136,23 @@ export default function ChainPlotPage() { )} </div> + {/* Chapter title */} + <div> + <label className="text-foreground mb-2 block text-sm"> + Chapter Title <span className="text-muted">(optional)</span> + </label> + <input + type="text" + value={title} + onChange={(e) => setTitle(e.target.value.slice(0, 100))} + disabled={busy || noStoryline} + placeholder={noStoryline ? "Select a storyline first" : "e.g. The Awakening"} + maxLength={100} + className="border-border bg-surface text-foreground placeholder:text-muted w-full rounded border px-3 py-2 text-sm focus:border-accent focus:outline-none disabled:opacity-50" + /> + <span className="text-muted mt-1 block text-xs">{title.length}/100</span> + </div> + {/* Content */} <div> <label className="text-foreground mb-2 block text-sm"> diff --git a/src/hooks/useChainPlot.ts b/src/hooks/useChainPlot.ts index 9b350fd..de72ab4 100644 --- a/src/hooks/useChainPlot.ts +++ b/src/hooks/useChainPlot.ts @@ -13,7 +13,7 @@ export function useChainPlot() { const { state, error, txHash, execute, reset } = usePublish(); const chainPlot = useCallback( - async (storylineId: number, content: string) => { + async (storylineId: number, content: string, title = "") => { await execute({ content, uploadKeyPrefix: `plotlink/plots/${storylineId}`, @@ -22,7 +22,7 @@ export function useChainPlot() { address: STORY_FACTORY, abi: storyFactoryAbi as unknown as [], functionName: "chainPlot", - args: [BigInt(storylineId), cid, contentHash], + args: [BigInt(storylineId), title, cid, contentHash], }), }); },