Skip to content

Commit 3e74a17

Browse files
authored
Merge pull request #578 from PotLock/staging
Staging to prod
2 parents 4ce98bf + a7ae79f commit 3e74a17

File tree

6 files changed

+172
-12
lines changed

6 files changed

+172
-12
lines changed

src/common/api/indexer/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * as indexerClient from "./internal/client.generated";
22
export * as indexer from "./hooks";
33
export * from "./types";
4+
export { syncApi } from "./sync";

src/common/api/indexer/sync.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { INDEXER_API_ENDPOINT_URL } from "@/common/_config";
2+
3+
// Use same logic as hooks.ts - staging/production should hit dev.potlock.io
4+
// because that's where the sync endpoints are deployed
5+
const SYNC_API_BASE_URL =
6+
process.env.NEXT_PUBLIC_ENV === "test" ? INDEXER_API_ENDPOINT_URL : "https://dev.potlock.io";
7+
8+
export const syncApi = {
9+
/**
10+
* Sync a campaign after creation or update
11+
* @param campaignId - The on-chain campaign ID
12+
*/
13+
async campaign(campaignId: number | string): Promise<{ success: boolean; message?: string }> {
14+
try {
15+
const response = await fetch(`${SYNC_API_BASE_URL}/api/v1/campaigns/${campaignId}/sync`, {
16+
method: "POST",
17+
});
18+
19+
if (!response.ok) {
20+
const error = await response.json().catch(() => ({}));
21+
console.warn("Failed to sync campaign:", error);
22+
return { success: false, message: error?.error || "Sync failed" };
23+
}
24+
25+
const result = await response.json();
26+
console.log("Campaign synced:", result);
27+
return { success: true, message: result.message };
28+
} catch (error) {
29+
console.warn("Failed to sync campaign:", error);
30+
return { success: false, message: String(error) };
31+
}
32+
},
33+
34+
/**
35+
* Sync a campaign donation after a donation is made
36+
* @param campaignId - The on-chain campaign ID
37+
* @param txHash - Transaction hash from the donation
38+
* @param senderId - Account ID of the donor
39+
*/
40+
async campaignDonation(
41+
campaignId: number | string,
42+
txHash: string,
43+
senderId: string,
44+
): Promise<{ success: boolean; message?: string }> {
45+
try {
46+
const response = await fetch(
47+
`${SYNC_API_BASE_URL}/api/v1/campaigns/${campaignId}/donations/sync`,
48+
{
49+
method: "POST",
50+
headers: { "Content-Type": "application/json" },
51+
body: JSON.stringify({ tx_hash: txHash, sender_id: senderId }),
52+
},
53+
);
54+
55+
if (!response.ok) {
56+
const error = await response.json().catch(() => ({}));
57+
console.warn("Failed to sync campaign donation:", error);
58+
return { success: false, message: error?.error || "Sync failed" };
59+
}
60+
61+
const result = await response.json();
62+
return { success: true, message: result.message };
63+
} catch (error) {
64+
console.warn("Failed to sync campaign donation:", error);
65+
return { success: false, message: String(error) };
66+
}
67+
},
68+
};

src/common/contracts/core/campaigns/client.ts

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ export const create_campaign = ({ args }: CreateCampaignParams) => {
7878

7979
return contractApi.callMultiple(transactions);
8080
} else {
81-
console.log("create campaign");
8281
return contractApi.call<CreateCampaignParams["args"], Campaign>("create_campaign", {
8382
args,
8483
deposit: floatToYoctoNear(0.021),
@@ -117,13 +116,62 @@ export const delete_campaign = ({ args }: DeleteCampaignParams) =>
117116
gas: FULL_TGAS,
118117
});
119118

120-
export const donate = (args: CampaignDonationArgs, depositAmountYocto: IndivisibleUnits) =>
121-
contractApi.call<CampaignDonationArgs, CampaignDonation>("donate", {
119+
export type DonateResult = {
120+
donation: CampaignDonation;
121+
txHash: string | null;
122+
};
123+
124+
export const donate = async (
125+
args: CampaignDonationArgs,
126+
depositAmountYocto: IndivisibleUnits,
127+
): Promise<DonateResult> => {
128+
const { walletApi } = await import("@/common/blockchains/near-protocol/client");
129+
const wallet = await walletApi.ensureWallet();
130+
const signerId = walletApi.accountId;
131+
132+
if (!signerId) {
133+
throw new Error("Wallet is not signed in.");
134+
}
135+
136+
const { actionCreators } = await import("@near-js/transactions");
137+
const { providers } = await import("near-api-js");
138+
139+
const action = actionCreators.functionCall(
140+
"donate",
122141
args,
123-
deposit: depositAmountYocto,
124-
gas: FULL_TGAS,
125-
callbackUrl: window.location.href,
126-
});
142+
BigInt(FULL_TGAS),
143+
BigInt(depositAmountYocto),
144+
);
145+
146+
let outcome: any;
147+
const walletAny = wallet as any;
148+
149+
if ("signAndSendTransaction" in walletAny) {
150+
outcome = await walletAny.signAndSendTransaction({
151+
signerId,
152+
receiverId: CAMPAIGNS_CONTRACT_ACCOUNT_ID,
153+
actions: [action],
154+
});
155+
} else if ("signAndSendTransactions" in walletAny) {
156+
const results = await walletAny.signAndSendTransactions({
157+
transactions: [
158+
{
159+
receiverId: CAMPAIGNS_CONTRACT_ACCOUNT_ID,
160+
actions: [action],
161+
},
162+
],
163+
});
164+
165+
outcome = Array.isArray(results) ? results[0] : results;
166+
} else {
167+
throw new Error("Wallet does not support transaction signing");
168+
}
169+
170+
const txHash = outcome?.transaction?.hash || outcome?.transaction_outcome?.id || null;
171+
const donation = providers.getTransactionLastResult(outcome) as CampaignDonation;
172+
173+
return { donation, txHash };
174+
};
127175

128176
export const get_campaigns = () => contractApi.view<{}, Campaign[]>("get_campaigns");
129177

src/entities/campaign/hooks/forms.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useRouter } from "next/router";
55
import { SubmitHandler, useForm, useWatch } from "react-hook-form";
66
import { isDeepEqual } from "remeda";
77

8+
import { syncApi } from "@/common/api/indexer/sync";
89
import { NATIVE_TOKEN_DECIMALS, NATIVE_TOKEN_ID } from "@/common/constants";
910
import { campaignsContractClient } from "@/common/contracts/core/campaigns";
1011
import type { Campaign } from "@/common/contracts/core/campaigns/interfaces";
@@ -310,7 +311,10 @@ export const useCampaignForm = ({ campaignId, ftId, onUpdateSuccess }: CampaignF
310311
.update_campaign({
311312
args: { ...args, campaign_id: campaignId },
312313
})
313-
.then(() => {
314+
.then(async () => {
315+
// Sync campaign to database
316+
await syncApi.campaign(campaignId).catch(console.warn);
317+
314318
self.reset(values, { keepErrors: false });
315319

316320
toast({
@@ -347,6 +351,16 @@ export const useCampaignForm = ({ campaignId, ftId, onUpdateSuccess }: CampaignF
347351
.then(async (newCampaign) => {
348352
const startMs = values.start_ms ? timeToMilliseconds(values.start_ms) : undefined;
349353

354+
// Sync new campaign to database
355+
if (
356+
newCampaign &&
357+
typeof newCampaign === "object" &&
358+
"id" in newCampaign &&
359+
newCampaign.id
360+
) {
361+
await syncApi.campaign((newCampaign as Campaign).id).catch(console.warn);
362+
}
363+
350364
toast({
351365
title: `You’ve successfully created a campaign for ${values.name}.`,
352366
description: (() => {

src/features/donation/models/effects/campaign-ft-donation.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ import type { AccountId, CampaignId } from "@/common/types";
1616
import { DONATION_BASE_STORAGE_DEPOSIT_FLOAT } from "../../constants";
1717
import type { DonationSubmitParams } from "../schemas";
1818

19+
export type CampaignFtDonationResult = {
20+
donation: CampaignDonation;
21+
txHash: string | null;
22+
};
23+
1924
type CampaignFtDonationMulticallInputs = Pick<
2025
DonationSubmitParams,
2126
"amount" | "referrerAccountId" | "bypassProtocolFee" | "message" | "tokenId"
@@ -36,7 +41,7 @@ export const campaignFtDonationMulticall = async ({
3641
bypassCreatorFee,
3742
message,
3843
tokenId,
39-
}: CampaignFtDonationMulticallInputs): Promise<CampaignDonation> => {
44+
}: CampaignFtDonationMulticallInputs): Promise<CampaignFtDonationResult> => {
4045
const { protocol_fee_recipient_account: protocolFeeRecipientAccountId } =
4146
await campaignsContractClient.get_config();
4247

@@ -247,6 +252,13 @@ export const campaignFtDonationMulticall = async ({
247252
),
248253
)
249254
.then((finalExecutionOutcomes) => {
255+
const lastOutcome = finalExecutionOutcomes?.at(-1);
256+
257+
const txHash =
258+
(lastOutcome as any)?.transaction?.hash ||
259+
(lastOutcome as any)?.transaction_outcome?.id ||
260+
null;
261+
250262
const receipt: CampaignDonation | undefined = finalExecutionOutcomes
251263
?.at(-1)
252264
?.receipts_outcome.filter(
@@ -278,7 +290,7 @@ export const campaignFtDonationMulticall = async ({
278290
.at(0);
279291

280292
if (receipt !== undefined) {
281-
return receipt;
293+
return { donation: receipt, txHash };
282294
} else throw new Error("Unable to determine transaction execution status.");
283295
});
284296
};

src/features/donation/models/effects/index.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import axios from "axios";
22

3+
import { syncApi } from "@/common/api/indexer";
34
import { RPC_NODE_URL, walletApi } from "@/common/blockchains/near-protocol/client";
45
import { NATIVE_TOKEN_ID } from "@/common/constants";
56
import { type CampaignDonation, campaignsContractClient } from "@/common/contracts/core/campaigns";
@@ -154,7 +155,15 @@ export const effects = (dispatch: AppDispatcher) => ({
154155
message,
155156
tokenId,
156157
})
157-
.then(dispatch.donation.success)
158+
.then(async (result) => {
159+
if (result.txHash && result.donation) {
160+
await syncApi
161+
.campaignDonation(campaignId, result.txHash, result.donation.donor_id)
162+
.catch(() => {});
163+
}
164+
165+
dispatch.donation.success(result.donation);
166+
})
158167
.catch((error) => {
159168
onError(error);
160169
dispatch.donation.failure(error);
@@ -172,7 +181,15 @@ export const effects = (dispatch: AppDispatcher) => ({
172181

173182
floatToYoctoNear(amount),
174183
)
175-
.then(dispatch.donation.success)
184+
.then(async (result) => {
185+
if (result.txHash && result.donation) {
186+
await syncApi
187+
.campaignDonation(campaignId, result.txHash, result.donation.donor_id)
188+
.catch(() => {});
189+
}
190+
191+
dispatch.donation.success(result.donation);
192+
})
176193
.catch((error) => {
177194
onError(error);
178195
dispatch.donation.failure(error);

0 commit comments

Comments
 (0)