Skip to content

Commit afbe26a

Browse files
authored
Merge pull request #595 from PotLock/staging
Staging
2 parents a4f0df3 + 4a2d403 commit afbe26a

File tree

5 files changed

+299
-68
lines changed

5 files changed

+299
-68
lines changed

src/common/api/indexer/sync.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,111 @@ export const syncApi = {
6969
}
7070
},
7171

72+
/**
73+
* Sync a campaign deletion after the owner deletes it on-chain
74+
* @param campaignId - The on-chain campaign ID
75+
* @param txHash - Transaction hash from the delete transaction
76+
* @param senderId - Account ID of the campaign owner who deleted it
77+
*/
78+
async campaignDelete(
79+
campaignId: number | string,
80+
txHash: string,
81+
senderId: string,
82+
): Promise<{ success: boolean; message?: string }> {
83+
try {
84+
const response = await fetch(
85+
`${CAMPAIGNS_SYNC_API_BASE_URL}/api/v1/campaigns/${campaignId}/delete/sync`,
86+
{
87+
method: "POST",
88+
headers: { "Content-Type": "application/json" },
89+
body: JSON.stringify({ tx_hash: txHash, sender_id: senderId }),
90+
},
91+
);
92+
93+
if (!response.ok) {
94+
const error = await response.json().catch(() => ({}));
95+
console.warn("Failed to sync campaign deletion:", error);
96+
return { success: false, message: error?.error || "Sync failed" };
97+
}
98+
99+
const result = await response.json();
100+
return { success: true, message: result.message };
101+
} catch (error) {
102+
console.warn("Failed to sync campaign deletion:", error);
103+
return { success: false, message: String(error) };
104+
}
105+
},
106+
107+
/**
108+
* Sync campaign donation refunds after process_refunds_batch is executed
109+
* @param campaignId - The on-chain campaign ID
110+
* @param txHash - Transaction hash from the refund transaction
111+
* @param senderId - Account ID of the sender who triggered refunds
112+
*/
113+
async campaignRefund(
114+
campaignId: number | string,
115+
txHash: string,
116+
senderId: string,
117+
): Promise<{ success: boolean; message?: string }> {
118+
try {
119+
const response = await fetch(
120+
`${CAMPAIGNS_SYNC_API_BASE_URL}/api/v1/campaigns/${campaignId}/refunds/sync`,
121+
{
122+
method: "POST",
123+
headers: { "Content-Type": "application/json" },
124+
body: JSON.stringify({ tx_hash: txHash, sender_id: senderId }),
125+
},
126+
);
127+
128+
if (!response.ok) {
129+
const error = await response.json().catch(() => ({}));
130+
console.warn("Failed to sync campaign refunds:", error);
131+
return { success: false, message: error?.error || "Sync failed" };
132+
}
133+
134+
const result = await response.json();
135+
return { success: true, message: result.message };
136+
} catch (error) {
137+
console.warn("Failed to sync campaign refunds:", error);
138+
return { success: false, message: String(error) };
139+
}
140+
},
141+
142+
/**
143+
* Sync campaign donation unescrow after process_escrowed_donations_batch is executed
144+
* @param campaignId - The on-chain campaign ID
145+
* @param txHash - Transaction hash from the unescrow transaction
146+
* @param senderId - Account ID of the sender who triggered unescrow
147+
*/
148+
async campaignUnescrow(
149+
campaignId: number | string,
150+
txHash: string,
151+
senderId: string,
152+
): Promise<{ success: boolean; message?: string }> {
153+
try {
154+
const response = await fetch(
155+
`${CAMPAIGNS_SYNC_API_BASE_URL}/api/v1/campaigns/${campaignId}/unescrow/sync`,
156+
{
157+
method: "POST",
158+
headers: { "Content-Type": "application/json" },
159+
body: JSON.stringify({ tx_hash: txHash, sender_id: senderId }),
160+
},
161+
);
162+
163+
if (!response.ok) {
164+
const error = await response.json().catch(() => ({}));
165+
console.warn("Failed to sync campaign unescrow:", error);
166+
return { success: false, message: error?.error || "Sync failed" };
167+
}
168+
169+
const result = await response.json();
170+
return { success: true, message: result.message };
171+
} catch (error) {
172+
console.warn("Failed to sync campaign unescrow:", error);
173+
return { success: false, message: String(error) };
174+
}
175+
},
176+
72177
/**
73178
* Sync an account profile and recalculate donation stats
74179
* @param accountId - The NEAR account ID

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

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,71 @@ export const create_campaign = ({ args }: CreateCampaignParams) => {
8686
}
8787
};
8888

89-
export const process_escrowed_donations_batch = ({ args }: { args: { campaign_id: CampaignId } }) =>
90-
contractApi.call("process_escrowed_donations_batch", {
91-
args,
92-
gas: FULL_TGAS,
93-
});
89+
export type TxHashResult = {
90+
txHash: string | null;
91+
};
92+
93+
const callWithTxHash = async (
94+
method: string,
95+
args: Record<string, unknown>,
96+
deposit?: string,
97+
): Promise<TxHashResult> => {
98+
const { walletApi } = await import("@/common/blockchains/near-protocol/client");
99+
const wallet = await walletApi.ensureWallet();
100+
const signerId = walletApi.accountId;
94101

95-
export const process_refunds_batch = ({ args }: { args: { campaign_id: CampaignId } }) =>
96-
contractApi.call("process_refunds_batch", {
102+
if (!signerId) {
103+
throw new Error("Wallet is not signed in.");
104+
}
105+
106+
const { actionCreators } = await import("@near-js/transactions");
107+
108+
const action = actionCreators.functionCall(
109+
method,
97110
args,
98-
gas: FULL_TGAS,
99-
});
111+
BigInt(FULL_TGAS),
112+
BigInt(deposit ?? "0"),
113+
);
114+
115+
let outcome: any;
116+
const walletAny = wallet as any;
117+
118+
if ("signAndSendTransaction" in walletAny) {
119+
outcome = await walletAny.signAndSendTransaction({
120+
signerId,
121+
receiverId: CAMPAIGNS_CONTRACT_ACCOUNT_ID,
122+
actions: [action],
123+
});
124+
} else if ("signAndSendTransactions" in walletAny) {
125+
const results = await walletAny.signAndSendTransactions({
126+
transactions: [
127+
{
128+
receiverId: CAMPAIGNS_CONTRACT_ACCOUNT_ID,
129+
actions: [action],
130+
},
131+
],
132+
});
133+
134+
outcome = Array.isArray(results) ? results[0] : results;
135+
} else {
136+
throw new Error("Wallet does not support transaction signing");
137+
}
138+
139+
const txHash = outcome?.transaction?.hash || outcome?.transaction_outcome?.id || null;
140+
return { txHash };
141+
};
142+
143+
export const process_escrowed_donations_batch = ({
144+
args,
145+
}: {
146+
args: { campaign_id: CampaignId };
147+
}): Promise<TxHashResult> => callWithTxHash("process_escrowed_donations_batch", args);
148+
149+
export const process_refunds_batch = ({
150+
args,
151+
}: {
152+
args: { campaign_id: CampaignId };
153+
}): Promise<TxHashResult> => callWithTxHash("process_refunds_batch", args);
100154

101155
export type UpdateCampaignParams = { args: CampaignInputs & { campaign_id: CampaignId } };
102156

@@ -109,12 +163,8 @@ export const update_campaign = ({ args }: UpdateCampaignParams) =>
109163

110164
export type DeleteCampaignParams = { args: { campaign_id: CampaignId } };
111165

112-
export const delete_campaign = ({ args }: DeleteCampaignParams) =>
113-
contractApi.call<DeleteCampaignParams["args"], void>("delete_campaign", {
114-
args,
115-
deposit: floatToYoctoNear(0.021),
116-
gas: FULL_TGAS,
117-
});
166+
export const delete_campaign = ({ args }: DeleteCampaignParams): Promise<TxHashResult> =>
167+
callWithTxHash("delete_campaign", args, floatToYoctoNear(0.021));
118168

119169
export type DonateResult = {
120170
donation: CampaignDonation;

src/common/lib/datetime.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export const timestamp = preprocess(
9999
);
100100

101101
export const futureTimestamp = timestamp.refine(
102-
(value) => value > Temporal.Now.instant().epochMilliseconds,
102+
(value) => value >= Temporal.Now.instant().epochMilliseconds - 60_000,
103103
{ message: "Cannot be in the past" },
104104
);
105105

0 commit comments

Comments
 (0)