Skip to content
Merged
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
99 changes: 64 additions & 35 deletions packages/cli/src/commands/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,77 @@ export function registerStatus(program: Command): void {
console.log(`Fetching storyline ${storylineId}...`);

// -----------------------------------------------------------------
// 1. On-chain event data (always available)
// 1. Storyline data — Supabase primary, paginated RPC fallback
// -----------------------------------------------------------------
const info = await client.getStoryline(storylineId);
if (!info) {
console.error(`Storyline ${storylineId} not found on-chain.`);
process.exit(1);
}
let title = "";
let creator: Address = "0x0000000000000000000000000000000000000000";
let tokenAddress: Address = "0x0000000000000000000000000000000000000000";
let hasDeadline = false;
let openingCID = "";
let plotCount = 0;

// -----------------------------------------------------------------
// 2. Supabase metadata (optional — richer data when configured)
// -----------------------------------------------------------------
// Supabase-only metadata
let dbRow: {
plot_count: number;
last_plot_time: string | null;
has_deadline: boolean;
sunset: boolean;
writer_type: number | null;
block_timestamp: string | null;
} | null = null;

// Try Supabase first (fast, indexed), fall back to paginated RPC
// if not configured or if the storyline isn't indexed yet
let fromSupabase = false;
if (cfg.supabaseUrl && cfg.supabaseAnonKey) {
const supabase = createClient(cfg.supabaseUrl, cfg.supabaseAnonKey);
const { data } = await supabase
.from("storylines")
.select("plot_count, last_plot_time, has_deadline, sunset, writer_type, block_timestamp")
.select("title, writer_address, token_address, has_deadline, plot_count, last_plot_time, sunset, writer_type, block_timestamp")
.eq("storyline_id", Number(storylineId))
.eq("contract_address", client.storyFactory.toLowerCase())
.single();
dbRow = data;

if (data) {
fromSupabase = true;
title = data.title;
creator = data.writer_address as Address;
tokenAddress = data.token_address as Address;
hasDeadline = data.has_deadline;
plotCount = data.plot_count;
dbRow = {
last_plot_time: data.last_plot_time,
sunset: data.sunset,
writer_type: data.writer_type,
block_timestamp: data.block_timestamp,
};

// Opening CID is only in event logs; fetch via paginated RPC
const info = await client.getStoryline(storylineId);
if (info) {
openingCID = info.openingCID;
}
}
}

if (!fromSupabase) {
// Fallback: paginated RPC log fetching (chunks into RPC-safe ranges)
const info = await client.getStoryline(storylineId);
if (!info) {
console.error(`Storyline ${storylineId} not found on-chain.`);
process.exit(1);
}

title = info.title;
creator = info.creator;
tokenAddress = info.tokenAddress;
hasDeadline = info.hasDeadline;
openingCID = info.openingCID;

const plots = await client.getPlots(storylineId);
plotCount = plots.length;
}

// -----------------------------------------------------------------
// 3. Reserve token metadata (symbol + decimals via tokenBond)
// 2. Reserve token metadata (symbol + decimals via tokenBond)
// -----------------------------------------------------------------
let tokenSymbol = "TOKEN";
let tokenDecimals = 18;
Expand All @@ -62,7 +100,7 @@ export function registerStatus(program: Command): void {
address: client.mcv2Bond,
abi: mcv2BondAbi,
functionName: "tokenBond",
args: [info.tokenAddress],
args: [tokenAddress],
});
bondCreator = (bond as readonly unknown[])[0] as Address;
const reserveToken = (bond as readonly unknown[])[4] as Address;
Expand All @@ -86,12 +124,12 @@ export function registerStatus(program: Command): void {
}

// -----------------------------------------------------------------
// 4. On-chain token price (MCV2_Bond)
// 3. On-chain token price (MCV2_Bond)
// -----------------------------------------------------------------
const tokenPrice = await client.getTokenPrice(info.tokenAddress);
const tokenPrice = await client.getTokenPrice(tokenAddress);

// -----------------------------------------------------------------
// 5. On-chain royalty info
// 4. On-chain royalty info
// -----------------------------------------------------------------
let unclaimedRoyalty: bigint | null = null;
try {
Expand All @@ -103,26 +141,17 @@ export function registerStatus(program: Command): void {
// Token may not have a bond yet
}

// -----------------------------------------------------------------
// 6. Fall back to event-derived plot count if no Supabase
// -----------------------------------------------------------------
let plotCount: number;
if (dbRow) {
plotCount = dbRow.plot_count;
} else {
const plots = await client.getPlots(storylineId);
plotCount = plots.length;
}

// -----------------------------------------------------------------
// Display
// -----------------------------------------------------------------
console.log();
console.log(`Title: ${info.title}`);
console.log(`Creator: ${info.creator}`);
console.log(`Token: ${info.tokenAddress}`);
console.log(`Has deadline: ${info.hasDeadline ? "yes" : "no"}`);
console.log(`Opening CID: ${info.openingCID}`);
console.log(`Title: ${title}`);
console.log(`Creator: ${creator}`);
console.log(`Token: ${tokenAddress}`);
console.log(`Has deadline: ${hasDeadline ? "yes" : "no"}`);
if (openingCID) {
console.log(`Opening CID: ${openingCID}`);
}
console.log(`Plot count: ${plotCount}`);

if (dbRow) {
Expand All @@ -136,7 +165,7 @@ export function registerStatus(program: Command): void {
}

// Deadline remaining (7 days from last plot)
if (dbRow.has_deadline && dbRow.last_plot_time && !dbRow.sunset) {
if (hasDeadline && dbRow.last_plot_time && !dbRow.sunset) {
const DEADLINE_HOURS = 168;
const deadlineMs =
new Date(dbRow.last_plot_time).getTime() + DEADLINE_HOURS * 60 * 60 * 1000;
Expand Down
13 changes: 13 additions & 0 deletions packages/cli/src/sdk/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ export const storyFactoryAbi = [
inputs: [],
outputs: [{ name: "", type: "address" }],
},
{
type: "function",
name: "storylines",
stateMutability: "view",
inputs: [{ name: "storylineId", type: "uint256" }],
outputs: [
{ name: "writer", type: "address" },
{ name: "token", type: "address" },
{ name: "plotCount", type: "uint24" },
{ name: "lastPlotTime", type: "uint40" },
{ name: "hasDeadline", type: "bool" },
],
},
] as const;

// ---------------------------------------------------------------------------
Expand Down
67 changes: 59 additions & 8 deletions packages/cli/src/sdk/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,17 +318,15 @@ export class PlotLink {
* @returns Storyline info or null if not found
*/
async getStoryline(storylineId: bigint): Promise<StorylineInfo | null> {
const logs = await this.publicClient.getLogs({
const logs = await this.getLogsPaginated({
address: this.storyFactory,
event: StorylineCreatedEvent,
args: { storylineId },
fromBlock: this.deploymentBlock,
toBlock: "latest",
});

if (logs.length === 0) return null;

const log = logs[0];
const log = logs[0] as { args: Record<string, unknown> };
const args = log.args as {
writer: Address;
tokenAddress: Address;
Expand All @@ -355,16 +353,14 @@ export class PlotLink {
* @returns Array of plot info objects, ordered by plot index
*/
async getPlots(storylineId: bigint): Promise<PlotInfo[]> {
const logs = await this.publicClient.getLogs({
const logs = await this.getLogsPaginated({
address: this.storyFactory,
event: PlotChainedEvent,
args: { storylineId },
fromBlock: this.deploymentBlock,
toBlock: "latest",
});

return logs.map((log) => {
const args = log.args as {
const args = (log as { args: Record<string, unknown> }).args as {
storylineId: bigint;
plotIndex: bigint;
writer: Address;
Expand All @@ -381,6 +377,32 @@ export class PlotLink {
});
}

/**
* Read storyline struct directly from contract storage.
* Uses readContract instead of getLogs, avoiding RPC block-range limits.
* Does not include title or openingCID (those are only in event logs).
*/
async getStorylineStruct(storylineId: bigint): Promise<{
writer: Address;
token: Address;
plotCount: number;
lastPlotTime: number;
hasDeadline: boolean;
} | null> {
try {
const result = await this.publicClient.readContract({
address: this.storyFactory,
abi: storyFactoryAbi,
functionName: "storylines",
args: [storylineId],
});
const [writer, token, plotCount, lastPlotTime, hasDeadline] = result as [Address, Address, number, number, boolean];
return { writer, token, plotCount, lastPlotTime, hasDeadline };
} catch {
return null;
}
}

// -------------------------------------------------------------------------
// Agent methods
// -------------------------------------------------------------------------
Expand Down Expand Up @@ -601,6 +623,35 @@ export class PlotLink {
);
}
}

/**
* Paginated getLogs that chunks requests into RPC-safe ranges.
* Public RPCs typically limit eth_getLogs to 10,000 blocks per request.
*/
private async getLogsPaginated(params: {
address: Address;
event: (typeof storyFactoryAbi)[number] & { type: "event" };
args?: Record<string, unknown>;
}): Promise<unknown[]> {
const MAX_RANGE = BigInt(9_999);
const latestBlock = await this.publicClient.getBlockNumber();
const from = this.deploymentBlock;
const allLogs: unknown[] = [];

for (let start = from; start <= latestBlock; start += MAX_RANGE + 1n) {
const end = start + MAX_RANGE > latestBlock ? latestBlock : start + MAX_RANGE;
const logs = await this.publicClient.getLogs({
address: params.address,
event: params.event,
args: params.args,
fromBlock: start,
toBlock: end,
} as Parameters<typeof this.publicClient.getLogs>[0]);
allLogs.push(...logs);
}

return allLogs;
}
}

// ---------------------------------------------------------------------------
Expand Down
Loading