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
63 changes: 63 additions & 0 deletions contracts/prompt-hash/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,69 @@ fn test_update_prompt_price_appends_history_and_emits_event() {
assert_eq!(third.seq, 3);
}

#[test]
fn test_create_prompt_emits_prompt_created_event() {
let env: Env = Default::default();
let context = setup(&env);
let client = PromptHashContractClient::new(&env, &context.contract);

let creator = Address::generate(&env);
let before = env.events().all().len();
let prompt_id = create_prompt(
&env,
&client,
&creator,
"Event Prompt",
7_500,
&context.xlm,
);
let after = env.events().all().len();

assert!(
after > before,
"expected PromptCreated event, got {} delta",
after - before
);

let last = env.events().all().get(after - 1).unwrap();
assert_eq!(last.topic, String::from_str(&env, "PromptCreated"));
}

#[test]
fn test_buy_prompt_emits_prompt_purchased_event() {
let env: Env = Default::default();
let context = setup(&env);
let client = PromptHashContractClient::new(&env, &context.contract);
let xlm_client = token::StellarAssetClient::new(&env, &context.xlm);

let creator = Address::generate(&env);
let buyer = Address::generate(&env);
let price: i128 = 15_000;
let prompt_id = create_prompt(
&env,
&client,
&creator,
"Purchase Event Prompt",
price,
&context.xlm,
);

fund_buyer(&xlm_client, &buyer, &context.contract, price);

let before = env.events().all().len();
client.buy_prompt(&buyer, &prompt_id, &None::<Bytes>, &price, &None::<Bytes>);
let after = env.events().all().len();

assert!(
after > before,
"expected PromptPurchased event, got {} delta",
after - before
);

let last = env.events().all().get(after - 1).unwrap();
assert_eq!(last.topic, String::from_str(&env, "PromptPurchased"));
}

#[test]
fn test_price_history_is_capped() {
let env: Env = Default::default();
Expand Down
Empty file.
2 changes: 2 additions & 0 deletions server/src/controllers/webhookControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import { z } from "zod";
* "listing sales, transfers, disputes, and version updates").
*/
const ALLOWED_EVENTS = [
"PromptCreated", // new listing created
"PromptPurchased", // listing sales
"PromptPriceUpdated", // price changes
"LicenseTransferred", // transfers
"DisputeOpened", // disputes
"DisputeResolved", // disputes
Expand Down
46 changes: 45 additions & 1 deletion server/src/services/indexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { IndexerState } from "../models/IndexerState";
import { scanForSimilarity } from "./similarityDetection";
import { recordMarketplaceTransaction } from "./transactionHistoryService";
import { invalidatePromptMetadata } from "./cacheService";
import { dispatchEvent } from "./webhookDispatcher";
import { notifyPromptPurchased } from "./emailNotifications";

const CONTRACT_ID = process.env.PUBLIC_PROMPT_HASH_CONTRACT_ID;
const rpc = new Server(process.env.PUBLIC_STELLAR_RPC_URL!, { timeout: 15_000 });
Expand Down Expand Up @@ -108,11 +110,19 @@ async function processEvent(event: any) {
);
}
await invalidatePromptMetadata(String(upserted?._id ?? prompt_id));

void dispatchEvent(creator, "PromptCreated", {
prompt_id,
creator,
price_stroops,
}).catch((err) =>
console.error("[indexer] PromptCreated webhook dispatch failed:", err),
);
break;
}

case "PromptPurchased": {
const { prompt_id, buyer } = data;
const { prompt_id, buyer, creator } = data;
const updatedPrompt = await Prompt.findOneAndUpdate(
{ onChainId: prompt_id.toString() },
{ $inc: { salesCount: 1 } },
Expand All @@ -134,6 +144,29 @@ async function processEvent(event: any) {
);
}
await invalidatePromptMetadata(String(updatedPrompt?._id ?? prompt_id));

if (buyer && creator) {
void dispatchEvent(creator, "PromptPurchased", {
prompt_id,
buyer,
creator,
txHash: event.txHash,
}).catch((err) =>
console.error("[indexer] PromptPurchased webhook dispatch failed:", err),
);

const prompt = await Prompt.findOne({
onChainId: prompt_id.toString(),
}).lean();
void notifyPromptPurchased(creator, {
buyerWallet: buyer,
promptTitle: prompt?.title ?? `Prompt #${prompt_id}`,
promptId: prompt_id.toString(),
txHash: event.txHash,
}).catch((err) =>
console.error("[indexer] PromptPurchased email notification failed:", err),
);
}
break;
}

Expand All @@ -145,6 +178,17 @@ async function processEvent(event: any) {
{ new: true },
);
await invalidatePromptMetadata(String(updatedPrompt?._id ?? prompt_id));

const prompt = await Prompt.findOne({
onChainId: prompt_id.toString(),
}).lean();
const creator = prompt?.owner?.walletAddress ?? "";
void dispatchEvent(creator, "PromptPriceUpdated", {
prompt_id,
price_stroops,
}).catch((err) =>
console.error("[indexer] PromptPriceUpdated webhook dispatch failed:", err),
);
break;
}

Expand Down