Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 2 additions & 2 deletions backend/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ const config: HardhatUserConfig = {
},
networks: {
sepolia: { // Chain ID 11155111
url: "https://sepolia.drpc.org",
url: "https://0xrpc.io/sep",
accounts: privateKeys
},
mainnet: { // Chain ID 1
url: "https://eth.drpc.org",
url: "https://0xrpc.io/eth",
accounts: privateKeys
}
},
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"SponsorshipQueueModule#SponsorshipQueue": "0xDd2A7CAF07E9ebf7560E524454607f0270348ecf",
"DistributionQueueModule#DistributionQueue": "0x3bd4EFE520FFE291B36C5bDE3c34028A0CFe9513",
"DistributionVerifierModule#DistributionVerifier": "0xf09f4cadbB3CFDFd92B1717853ccFBAe3B57B73B"
"SponsorshipQueueModule#SponsorshipQueue": "0x646A5550af30f5340AF218C9aE375A4C7Bb740BF",
"DistributionQueueModule#DistributionQueue": "0xD7744d3Cc62748fcDc8f53e202C804488D9e85A8",
"DistributionVerifierModule#DistributionVerifier": "0xC3313d6BB50C081AE07B6B097700eC2ED0568377",
"QueueHandlerModule#QueueHandler": "0x0B96769D277D8b7EaED38d2e269101615fB3e237"
}
38 changes: 22 additions & 16 deletions backend/ignition/deployments/chain-11155111/journal.jsonl

Large diffs are not rendered by default.

41 changes: 13 additions & 28 deletions frontend/src/components/DistributionSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import LoadingIndicator from "./LoadingIndicator";
import { Address, formatEther } from "viem";
import ErrorIndicator from "./ErrorIndicator";

export default function DistributionSummary({ queueIndex }: any) {
export default function DistributionSummary({ queueNumber }: any) {
console.debug("DistributionSummary");

console.debug("queueIndex:", queueIndex);
console.debug("queueNumber:", queueNumber);

const deploymentAddress: Address = deployed_addresses["DistributionQueueModule#DistributionQueue"] as `0x${string}`;
console.debug("deploymentAddress:", deploymentAddress);
const { isLoading, isError, error, data } = useReadContract({
abi,
address: deploymentAddress,
functionName: "queue",
args: [queueIndex]
args: [queueNumber]
});
console.debug("isLoading:", isLoading);
console.debug("isError:", isError);
Expand All @@ -32,39 +32,24 @@ export default function DistributionSummary({ queueIndex }: any) {
}

const distribution: any = data;
const timestamp = Number(distribution[0]);
const distributor = distribution[1];
const status = distribution[2];
const languageCode = distribution[0];
const androidId = distribution[1];
const timestamp = Number(distribution[2]);
const distributor = distribution[3];
return (
<>
Queue number: #{queueIndex + 1}
Queue number: #{queueNumber}
<div className="mt-2">
{new Date(timestamp * 1_000).toISOString().substring(0,10)} {new Date(timestamp * 1_000).toISOString().substring(11,16)}
</div>
<div className="mt-2">
Distributor: <code>{distributor}</code>
Language: {languageCode}
</div>
<div className="mt-2">
Android ID: {androidId}
</div>
<div className="mt-2">
{(status == 0) && (
<span className="px-3 py-1 text-sm text-stone-400 border-stone-400 bg-stone-800 border-2 rounded-2xl">
Delivered📦
</span>
)}
{(status == 1) && (
<span className="px-3 py-1 text-sm text-emerald-400 border-emerald-400 bg-emerald-800 border-2 rounded-2xl">
Approved✅
</span>
)}
{(status == 2) && (
<span className="px-3 py-1 text-sm text-orange-400 border-orange-400 bg-orange-800 border-2 rounded-2xl">
Rejected<span className="animate-pulse">🔶</span>
</span>
)}
{(status == 3) && (
<span className="px-3 py-1 text-sm text-purple-400 border-purple-400 bg-purple-800 border-2 rounded-2xl">
Paid💷
</span>
)}
Distributor: <code>{distributor}</code>
</div>
</>
)
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/Distributions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function Distributions() {
const { isLoading, isError, error, data } = useReadContract({
abi,
address: deploymentAddress,
functionName: "getQueueCount"
functionName: "getLength"
});
console.debug("isLoading:", isLoading);
console.debug("isError:", isError);
Expand All @@ -41,7 +41,7 @@ export default function Distributions() {
{Array(queueCount).fill(1).map((el, i) =>
<Link key={i} href={`/distributions/${i + 1}`}>
<div className="skew-y-3 p-4 text-2xl bg-indigo-200 dark:bg-indigo-950 rounded-lg border-indigo-400 border-r-4 border-b-4 hover:border-r-8 hover:border-b-8 hover:-translate-y-1">
<DistributionSummary queueIndex={i} />
<DistributionSummary queueNumber={i + 1} />
</div>
</Link>
)}
Expand Down
16 changes: 6 additions & 10 deletions frontend/src/components/SponsorshipSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import LoadingIndicator from "./LoadingIndicator";
import { Address, formatEther } from "viem";
import ErrorIndicator from "./ErrorIndicator";

export default function SponsorshipSummary({ queueIndex }: any) {
export default function SponsorshipSummary({ queueNumber }: any) {
console.debug("SponsorshipSummary");

console.debug("queueIndex:", queueIndex);
console.debug("queueNumber:", queueNumber);

const deploymentAddress: Address = deployed_addresses["SponsorshipQueueModule#SponsorshipQueue"] as `0x${string}`;
console.debug("deploymentAddress:", deploymentAddress);
const { isLoading, isError, error, data } = useReadContract({
abi,
address: deploymentAddress,
functionName: "queue",
args: [queueIndex]
args: [queueNumber]
});
Comment on lines +19 to 20
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix off‑by‑one: contract index vs. human display is inconsistent (likely wrong item fetched).

The page now passes a 1‑based queueNumber from the route, but:

  • You call the contract with args: [queueNumber] (1‑based),
  • Then display Queue number: #{queueNumber + 1} (effectively 2‑based).

This will read the wrong queue entry on-chain and display an incorrect number. Align to a single convention: treat props as 1‑based for UX, convert to 0‑based only for the contract call.

Apply:

-        functionName: "queue",
-        args: [queueNumber]
+        functionName: "queue",
+        args: [BigInt(queueNumber - 1)]
-            Queue number: #{queueNumber + 1}
+            Queue number: #{queueNumber}

Context: Getting this right is key for trust—elimu.ai's mission is to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.

Also applies to: 40-41

🤖 Prompt for AI Agents
In frontend/src/components/SponsorshipSummary.tsx around lines 19-20 (and also
apply the same fix at lines 40-41): the component is using a 1-based queueNumber
from the route but calling the contract with the raw 1-based value and then
displaying queueNumber + 1, causing an off-by-one error and wrong on-chain read;
convert the UX 1-based prop to 0-based only for the contract call (pass
queueNumber - 1 in args) and stop adding 1 when rendering the displayed "Queue
number" (render queueNumber directly), and make the same change for the other
occurrence on lines 40-41 so both contract calls and displays follow the single
1-based-for-UI / 0-based-for-contract convention.

console.debug("isLoading:", isLoading);
console.debug("isError:", isError);
Expand All @@ -33,19 +33,15 @@ export default function SponsorshipSummary({ queueIndex }: any) {

const sponsorship: any = data;
const estimatedCost = BigInt(sponsorship[0]);
const languageCode = String(sponsorship[1]);
const timestamp = Number(sponsorship[2]);
const sponsor = sponsorship[3];
const timestamp = Number(sponsorship[1]);
const sponsor = sponsorship[2];
return (
<>
Queue number: #{queueIndex + 1}
Queue number: #{queueNumber}
<div className="mt-2">
{new Date(timestamp * 1_000).toISOString().substring(0,10)} {new Date(timestamp * 1_000).toISOString().substring(11,16)}
</div>
Amount: {formatEther(estimatedCost)} ETH
<div className="mt-2">
Language: <code>{languageCode}</code>
</div>
<div className="mt-2">
Sponsor: <code>{sponsor}</code>
</div>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/Sponsorships.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function Sponsorships() {
const { isLoading, isError, error, data } = useReadContract({
abi,
address: deploymentAddress,
functionName: "getQueueCount"
functionName: "getLength"
});
console.debug("isLoading:", isLoading);
console.debug("isError:", isError);
Expand All @@ -41,7 +41,7 @@ export default function Sponsorships() {
{Array(queueCount).fill(1).map((el, i) =>
<Link key={i} href={`/sponsorships/${i + 1}`}>
<div className="skew-y-3 p-4 text-2xl bg-purple-200 dark:bg-purple-950 rounded-lg border-purple-400 border-r-4 border-b-4 hover:border-r-8 hover:border-b-8 hover:-translate-y-1">
<SponsorshipSummary queueIndex={i} />
<SponsorshipSummary queueNumber={i + 1} />
</div>
</Link>
)}
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/pages/distributions/[queueNumber].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ export default function DistributionDetails() {
const router = useRouter();
const queueNumber = Number(router.query.queueNumber);
console.debug("queueNumber:", queueNumber);
const queueIndex = queueNumber - 1;
console.debug("queueIndex:", queueIndex);

return (
<>
Expand All @@ -30,7 +28,7 @@ export default function DistributionDetails() {
</h1>

<div className="mt-8 p-4 text-2xl bg-zinc-50 dark:bg-zinc-900 rounded-lg">
<DistributionSummary queueIndex={queueIndex} />
<DistributionSummary queueNumber={queueNumber} />
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Guard rendering and validate route param before hitting the chain

Passing queueNumber straight through is fine only if the child converts 1‑based UI → 0‑based contract index. Also prevent NaN/invalid values during the initial render (Next.js router not ready) to avoid spurious contract reads.

Apply this minimal guard at the call site:

-          <DistributionSummary queueNumber={queueNumber} />
+          {Number.isInteger(queueNumber) && queueNumber >= 1 ? (
+            <DistributionSummary queueNumber={queueNumber} />
+          ) : null}

Optional follow‑up (helps our mission’s reliability): run this to ensure no stale queueIndex props remain:

Eliminating off‑by‑one pitfalls here keeps the Distributions UI trustworthy—important as elimu.ai's mission is to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.


🏁 Script executed:

#!/bin/bash
# Find any remaining queueIndex usages
rg -n --type=ts --type=tsx -C1 '\bqueueIndex\b'

Length of output: 27


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "1) locate files named [queueNumber].tsx"
find . -type f -name '[queueNumber].tsx' -print || true
echo "----"
echo "2) if found, print first 200 lines"
for f in $(find . -type f -name '[queueNumber].tsx' -print); do
  echo ">>> $f"
  sed -n '1,200p' "$f"
done
echo "----"
echo "3) search for queueIndex occurrences (no --type flags)"
rg -n -C1 '\bqueueIndex\b' || true
echo "----"
echo "4) search for DistributionSummary usages"
rg -n -C3 'DistributionSummary' || true
echo "----"
echo "5) list files in frontend/src/pages/distributions (if present)"
ls -la frontend/src/pages/distributions 2>/dev/null || true

Length of output: 3717


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== pages/distributions/[queueNumber].tsx ==="
sed -n '1,240p' frontend/src/pages/distributions/[queueNumber].tsx || true
echo
echo "=== components/DistributionSummary.tsx ==="
sed -n '1,240p' frontend/src/components/DistributionSummary.tsx || true
echo
echo "=== search for queueIndex occurrences ==="
rg -n '\bqueueIndex\b' -S || true
echo
echo "=== search for queueNumber occurrences ==="
rg -n '\bqueueNumber\b' -S || true
echo
echo "=== search for router.query references ==="
rg -n "router\.query" -S || true
echo
echo "=== search for parseInt or Number around queueNumber ==="
rg -n -C1 "parseInt|Number\(" -S || true

Length of output: 9672


Guard route param before calling DistributionSummary

router.query can be undefined on initial render; Number(router.query.queueNumber) can be NaN — validate before passing to DistributionSummary (it calls useReadContract with args: [queueNumber]).

-          <DistributionSummary queueNumber={queueNumber} />
+          {Number.isInteger(queueNumber) && queueNumber >= 1 ? (
+            <DistributionSummary queueNumber={queueNumber} />
+          ) : null}

Also apply the same guard in frontend/src/pages/sponsorships/[queueNumber].tsx.

elimu.ai's mission is to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.

📝 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
<DistributionSummary queueNumber={queueNumber} />
{Number.isInteger(queueNumber) && queueNumber >= 1 ? (
<DistributionSummary queueNumber={queueNumber} />
) : null}
🤖 Prompt for AI Agents
In frontend/src/pages/distributions/[queueNumber].tsx around line 31,
router.query.queueNumber may be undefined or non-numeric on initial render so
Number(router.query.queueNumber) can yield NaN; guard and validate the param
before passing it to <DistributionSummary /> (which ultimately calls
useReadContract). Fix by extracting queueNumber from router.query, check that it
exists and that Number(queueNumber) is a finite integer (or use parseInt +
isFinite) and only render <DistributionSummary queueNumber={...}/> when valid;
otherwise render a loading/null fallback or redirect. Apply the same guard and
pattern in frontend/src/pages/sponsorships/[queueNumber].tsx.

</div>
</main>
<MainFooter />
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/distributions/add.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export function SimulateContractButton({ languageCode, androidId}: any) {
return <ErrorIndicator description={error.name} />
}

return <WriteContractButton />
return <WriteContractButton languageCode={languageCode} androidId={androidId} />
}

export function WriteContractButton({ languageCode, androidId}: any) {
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/pages/sponsorships/[queueNumber].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ export default function SponsorshipDetails() {
const router = useRouter();
const queueNumber = Number(router.query.queueNumber);
console.debug("queueNumber:", queueNumber);
const queueIndex = queueNumber - 1;
console.debug("queueIndex:", queueIndex);

return (
<>
Expand All @@ -30,7 +28,7 @@ export default function SponsorshipDetails() {
</h1>

<div className="mt-8 p-4 text-2xl bg-zinc-50 dark:bg-zinc-900 rounded-lg">
<SponsorshipSummary queueIndex={queueIndex} />
<SponsorshipSummary queueNumber={queueNumber} />
</div>
</main>
<MainFooter />
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/pages/sponsorships/add.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export function SimulateContractButton({ estimatedCost }: any) {
abi,
address: deploymentAddress,
functionName: "addSponsorship",
value: parseEther(estimatedCost)
value: parseEther(String(estimatedCost))
})
console.debug("isPending:", isPending);
console.debug("isError:", isError);
Expand All @@ -95,7 +95,7 @@ export function SimulateContractButton({ estimatedCost }: any) {
return <ErrorIndicator description={error.name} />
}

return <WriteContractButton />
return <WriteContractButton estimatedCost={estimatedCost} />
}

export function WriteContractButton({ estimatedCost }: any) {
Expand All @@ -113,7 +113,7 @@ export function WriteContractButton({ estimatedCost }: any) {
abi,
address: deploymentAddress,
functionName: "addSponsorship",
value: parseEther(estimatedCost)
value: parseEther(String(estimatedCost))
})
}
>
Expand Down
Loading