Skip to content

Feature/campaign sync#576

Merged
Ebube111 merged 4 commits intostagingfrom
feature/campaign-sync
Feb 5, 2026
Merged

Feature/campaign sync#576
Ebube111 merged 4 commits intostagingfrom
feature/campaign-sync

Conversation

@aunali8812
Copy link
Collaborator

@aunali8812 aunali8812 commented Feb 5, 2026

Summary by CodeRabbit

  • New Features
    • Campaigns automatically synchronize with the indexer after creation or updates to keep listings up to date.
    • Donations now include transaction confirmation information and automatically synchronize with the indexer to ensure transaction records are consistent.

@aunali8812 aunali8812 requested a review from Ebube111 as a code owner February 5, 2026 09:44
@vercel
Copy link

vercel bot commented Feb 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
potlock-next-app Ready Ready Preview, Comment Feb 5, 2026 10:33am

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

Walkthrough

Adds a syncApi indexer wrapper and integrates it into campaign create/update and donation flows; refactors donate to sign/send transactions and return a txHash alongside donation data for downstream sync calls.

Changes

Cohort / File(s) Summary
Sync API Infrastructure
src/common/api/indexer/index.ts, src/common/api/indexer/sync.ts
New syncApi export and implementation exposing campaign() and campaignDonation() methods that POST to indexer endpoints and return { success, message? } with consistent error handling.
Donate Contract Refactoring
src/common/contracts/core/campaigns/client.ts
Refactored donate() to an async function that builds and signs transactions via wallet APIs, returns DonateResult ({ donation, txHash }), and removes prior console.log.
Campaign Create/Update Sync Integration
src/entities/campaign/hooks/forms.ts
After successful campaign create/update, calls syncApi.campaign(campaignId) as a side-effect (errors suppressed) before continuing UI flows.
Donation Flow Sync Integration
src/features/donation/models/effects/campaign-ft-donation.ts, src/features/donation/models/effects/index.ts
Donation flows now return { donation, txHash } and, when present, call syncApi.campaignDonation(campaignId, txHash, donor_id) before dispatching success; added CampaignFtDonationResult type.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as UI Layer
    participant WalletAPI as Wallet/Contract
    participant Blockchain as Blockchain
    participant EffectsHandler as Effects Handler
    participant IndexerAPI as Indexer API
    participant Backend as Backend

    User->>UI: Initiate donation
    UI->>WalletAPI: donate(args, depositAmount)
    WalletAPI->>WalletAPI: Build transaction
    WalletAPI->>Blockchain: signAndSendTransaction
    Blockchain-->>WalletAPI: txHash, outcome
    WalletAPI->>WalletAPI: Extract donation & txHash
    WalletAPI-->>UI: DonateResult {donation, txHash}
    
    UI->>EffectsHandler: Process donation result
    EffectsHandler->>EffectsHandler: Extract donation & txHash
    EffectsHandler->>IndexerAPI: syncApi.campaignDonation(campaignId, txHash, donor_id)
    IndexerAPI->>Backend: POST /campaigns/{id}/donations/sync
    Backend-->>IndexerAPI: {success: true}
    IndexerAPI-->>EffectsHandler: Sync result
    EffectsHandler->>EffectsHandler: Dispatch success (errors suppressed)
    EffectsHandler-->>UI: Donation complete
Loading
sequenceDiagram
    actor User
    participant UI as UI/Forms
    participant ContractAPI as Contract/API
    participant IndexerAPI as Indexer API
    participant Backend as Backend

    User->>UI: Create/Update campaign
    UI->>ContractAPI: Submit campaign
    ContractAPI-->>UI: Campaign response (with id)
    
    UI->>IndexerAPI: syncApi.campaign(campaignId)
    IndexerAPI->>Backend: POST /campaigns/{id}/sync
    Backend-->>IndexerAPI: {success: true}
    IndexerAPI-->>UI: Sync result
    
    UI->>UI: Reset form & show toast (errors suppressed)
    UI-->>User: Campaign synced
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Use Pinata for campaign banner uploads #408: Modifies the donate implementation and signature in src/common/contracts/core/campaigns/client.ts, affecting the same function refactor.
  • Finalize FT donation flow #366: Changes donation success handling in src/features/donation/models/effects/index.ts, overlapping with added sync calls.
  • Campaign Fixes  #357: Updates the donate contract method signature in src/common/contracts/core/campaigns/client.ts, related to returned types and behavior.

Suggested reviewers

  • Ebube111
  • carina-akaia

"🐇
I hopped through code and left a trace,
Tx hashes tucked in every space,
Campaigns and gifts now gently chime,
Indexer calls keep perfect time,
Hooray — syncs across the line! 🌱"

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Feature/campaign sync' is vague and uses generic phrasing that doesn't clearly convey what specific functionality was added or changed, making it difficult for reviewers scanning history to understand the main purpose. Use a more descriptive title that explains the core change, such as 'Add campaign and donation sync API integration' or 'Implement indexer sync for campaigns and donations'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/campaign-sync

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/common/contracts/core/campaigns/client.ts`:
- Around line 169-172: providers.getTransactionLastResult(outcome) may return
undefined so you must validate its return before casting to CampaignDonation; in
the code around the symbols outcome, providers.getTransactionLastResult and the
returned DonateResult, check the result and if it's undefined either throw a
clear error (e.g. throw new Error("missing transaction result for txHash: ..."))
or return a value that matches the DonateResult contract (do not leave donation
undefined). Ensure the code uses the computed txHash when building the
error/return so the failure can be traced.
🧹 Nitpick comments (12)
src/entities/campaign/hooks/forms.ts (4)

8-8: Consider using the barrel export for consistency.

The import uses a direct path, but syncApi is re-exported from the barrel at @/common/api/indexer. Other files in this PR (e.g., src/features/donation/models/effects/index.ts) import from the barrel.

♻️ Suggested change
-import { syncApi } from "@/common/api/indexer/sync";
+import { syncApi } from "@/common/api/indexer";

314-317: Consider fire-and-forget for better UX.

The await on syncApi.campaign() blocks the success toast and form reset until the sync completes. Since sync errors are already swallowed and the primary operation succeeded, making this non-blocking would improve perceived responsiveness.

♻️ Fire-and-forget approach
          .then(async () => {
            // Sync campaign to database
-           await syncApi.campaign(campaignId).catch(console.warn);
+           syncApi.campaign(campaignId).catch(console.warn);

            self.reset(values, { keepErrors: false });

354-362: Duplicated type guard logic.

The same condition (newCampaign && typeof newCampaign === "object" && "id" in newCampaign && newCampaign.id) appears twice. Consider consolidating into a single block or extracting a type guard function.

♻️ Consolidated approach
          .then(async (newCampaign) => {
            const startMs = values.start_ms ? timeToMilliseconds(values.start_ms) : undefined;
+           const campaignWithId = 
+             newCampaign && typeof newCampaign === "object" && "id" in newCampaign && newCampaign.id
+               ? (newCampaign as Campaign)
+               : null;

            // Sync new campaign to database
-           if (
-             newCampaign &&
-             typeof newCampaign === "object" &&
-             "id" in newCampaign &&
-             newCampaign.id
-           ) {
-             await syncApi.campaign((newCampaign as Campaign).id).catch(console.warn);
+           if (campaignWithId) {
+             syncApi.campaign(campaignWithId.id).catch(console.warn);
            }

            toast({
              title: `You've successfully created a campaign for ${values.name}.`,
              description: (() => {
                if (startMs && startMs > Date.now()) {
                  return `Campaign starts on ${formatFullDateTime(startMs)}.`;
                }

                return "Campaign is live.";
              })(),
            });

-           // Fix: Ensure newCampaign has an id before accessing it
-           console.log(newCampaign);
-
-           if (
-             newCampaign &&
-             typeof newCampaign === "object" &&
-             "id" in newCampaign &&
-             newCampaign.id
-           ) {
-             router.push(routeSelectors.CAMPAIGN_BY_ID((newCampaign as Campaign).id));
+           if (campaignWithId) {
+             router.push(routeSelectors.CAMPAIGN_BY_ID(campaignWithId.id));
            } else {
              router.push(`/campaigns`);
            }

Also applies to: 378-384


376-376: Remove debug console.log.

This console.log(newCampaign) appears to be leftover debug code.

🧹 Remove debug statement
-           // Fix: Ensure newCampaign has an id before accessing it
-           console.log(newCampaign);
src/features/donation/models/effects/index.ts (2)

158-165: Add blank line before dispatch statement per linting rules.

ESLint reports missing padding line before the dispatch.donation.success statement.

🔧 Fix lint error
              await syncApi
                .campaignDonation(campaignId, result.txHash, result.donation.donor_id)
                .catch(() => {});
            }
+
            dispatch.donation.success(result.donation);

183-190: Add blank line before dispatch statement per linting rules.

Same ESLint padding line issue as the FT donation branch.

🔧 Fix lint error
              await syncApi
                .campaignDonation(campaignId, result.txHash, result.donation.donor_id)
                .catch(() => {});
            }
+
            dispatch.donation.success(result.donation);
src/features/donation/models/effects/campaign-ft-donation.ts (2)

255-259: Add blank line before const declaration per linting rules.

ESLint reports missing padding line before the txHash declaration.

🔧 Fix lint error
    .then((finalExecutionOutcomes) => {
      const lastOutcome = finalExecutionOutcomes?.at(-1);
+
      const txHash =
        (lastOutcome as any)?.transaction?.hash ||
        (lastOutcome as any)?.transaction_outcome?.id ||
        null;

256-259: Consider extracting txHash extraction logic.

The same transaction?.hash || transaction_outcome?.id pattern appears in src/common/contracts/core/campaigns/client.ts (line 169). Consider extracting to a shared utility to reduce duplication and ensure consistent behavior across donation flows.

src/common/api/indexer/sync.ts (2)

21-22: Inconsistent success logging.

The campaign method logs success with console.log("Campaign synced:", result), but campaignDonation doesn't log on success. Consider making this consistent - either log both or neither.

♻️ Option A: Add logging to campaignDonation
      const result = await response.json();
+     console.log("Campaign donation synced:", result);
      return { success: true, message: result.message };
♻️ Option B: Remove logging from campaign
      const result = await response.json();
-     console.log("Campaign synced:", result);
      return { success: true, message: result.message };

Also applies to: 57-58


10-13: Consider adding request timeouts.

The fetch calls have no timeout configured. If the indexer API is slow or unresponsive, these requests could hang indefinitely, blocking the awaiting code in the donation/campaign flows.

♻️ Add AbortController timeout
  async campaign(campaignId: number | string): Promise<{ success: boolean; message?: string }> {
    try {
+     const controller = new AbortController();
+     const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout
+
      const response = await fetch(
        `${INDEXER_API_ENDPOINT_URL}/api/v1/campaigns/${campaignId}/sync`,
-       { method: "POST" },
+       { method: "POST", signal: controller.signal },
      );
+     clearTimeout(timeoutId);

Apply similar pattern to campaignDonation.

Also applies to: 42-49

src/common/contracts/core/campaigns/client.ts (2)

164-164: Add blank line before outcome assignment per linting rules.

ESLint reports missing padding line.

🔧 Fix lint error
        ],
      });
+
      outcome = Array.isArray(results) ? results[0] : results;

146-167: Heavy use of any types reduces type safety.

The wallet interaction uses multiple any casts (outcome: any, walletAny, (lastOutcome as any)). While this is understandable given wallet API variations, it removes TypeScript's ability to catch errors.

Consider defining a minimal interface for the expected wallet methods and outcome structure to restore some type safety without requiring full typing of all wallet implementations.

Comment on lines +169 to +172
const txHash = outcome?.transaction?.hash || outcome?.transaction_outcome?.id || null;
const donation = providers.getTransactionLastResult(outcome) as CampaignDonation;

return { donation, txHash };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing null check on donation result.

providers.getTransactionLastResult(outcome) can return undefined if the transaction outcome doesn't have a successful result. This would cause donation to be undefined, which doesn't match the DonateResult type where donation is required (non-nullable CampaignDonation).

🛡️ Add validation
  const txHash = outcome?.transaction?.hash || outcome?.transaction_outcome?.id || null;
  const donation = providers.getTransactionLastResult(outcome) as CampaignDonation;

+ if (!donation) {
+   throw new Error("Unable to retrieve donation result from transaction.");
+ }
+
  return { donation, txHash };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const txHash = outcome?.transaction?.hash || outcome?.transaction_outcome?.id || null;
const donation = providers.getTransactionLastResult(outcome) as CampaignDonation;
return { donation, txHash };
const txHash = outcome?.transaction?.hash || outcome?.transaction_outcome?.id || null;
const donation = providers.getTransactionLastResult(outcome) as CampaignDonation;
if (!donation) {
throw new Error("Unable to retrieve donation result from transaction.");
}
return { donation, txHash };
🤖 Prompt for AI Agents
In `@src/common/contracts/core/campaigns/client.ts` around lines 169 - 172,
providers.getTransactionLastResult(outcome) may return undefined so you must
validate its return before casting to CampaignDonation; in the code around the
symbols outcome, providers.getTransactionLastResult and the returned
DonateResult, check the result and if it's undefined either throw a clear error
(e.g. throw new Error("missing transaction result for txHash: ...")) or return a
value that matches the DonateResult contract (do not leave donation undefined).
Ensure the code uses the computed txHash when building the error/return so the
failure can be traced.

@Ebube111 Ebube111 merged commit 3103088 into staging Feb 5, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants