From fdefcc847938c15cd85c2df6e0c5654917f4af97 Mon Sep 17 00:00:00 2001 From: "goodbounties-nanoclaw-agent[bot]" <307944451+goodbounties-nanoclaw-agent[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 09:04:49 +0000 Subject: [PATCH 1/3] [gdpatchagent] Read minimumDays/minimumClaims from InvitesV2 via getMinimums Add getMinimums to the adapter's SDK plumbing and surface minimums on InviteState so consumers can source contract thresholds instead of hardcoding them. Co-Authored-By: Claude On-Behalf-Of: gdpatchagent[onecli] (yaskkeryodtdijpv) --- .../citizen-claim-widget/src/inviteAdapter.ts | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/citizen-claim-widget/src/inviteAdapter.ts b/packages/citizen-claim-widget/src/inviteAdapter.ts index 9b82e9b3..516479aa 100644 --- a/packages/citizen-claim-widget/src/inviteAdapter.ts +++ b/packages/citizen-claim-widget/src/inviteAdapter.ts @@ -33,6 +33,11 @@ export type InviteStatus = | 'collecting' | 'error' +export interface InviteMinimums { + minimumDays: number + minimumClaims: number +} + export interface InviteState { status: InviteStatus address: Address | null @@ -44,6 +49,8 @@ export interface InviteState { collectableInvitees: Address[] eligibility: Record selfEligibility: BountyEligibilityDetails | null + /** Global minimumDays/minimumClaims thresholds, read from InvitesV2 via InviteSDK.getMinimums(). */ + minimums: InviteMinimums | null error: string | null success: string | null } @@ -79,6 +86,7 @@ const initialInviteState: InviteState = { collectableInvitees: [], eligibility: {}, selfEligibility: null, + minimums: null, error: null, success: null, } @@ -130,6 +138,7 @@ export interface InviteSnapshotSdk { getPendingInvitees: InviteSDK['getPendingInvitees'] getCollectableInvitees: InviteSDK['getCollectableInvitees'] checkEligibilityDetails: InviteSDK['checkEligibilityDetails'] + getMinimums: InviteSDK['getMinimums'] } export interface InviteSnapshot { @@ -140,6 +149,7 @@ export interface InviteSnapshot { collectableInvitees: Address[] eligibility: Record selfEligibility: BountyEligibilityDetails | null + minimums: InviteMinimums } /** @@ -152,13 +162,15 @@ export async function loadInviteSnapshot( address: Address, ): Promise { const user = await sdk.getUser(address) - const [level, invitees, pendingInvitees, collectableInvitees, selfEligibility] = await Promise.all([ - sdk.getLevel(Number(user.level)), - sdk.getInvitees(address), - sdk.getPendingInvitees(address), - sdk.getCollectableInvitees(address), - sdk.checkEligibilityDetails(address), - ]) + const [level, invitees, pendingInvitees, collectableInvitees, selfEligibility, minimums] = + await Promise.all([ + sdk.getLevel(Number(user.level)), + sdk.getInvitees(address), + sdk.getPendingInvitees(address), + sdk.getCollectableInvitees(address), + sdk.checkEligibilityDetails(address), + sdk.getMinimums(), + ]) const eligibilityEntries = await Promise.all( pendingInvitees.map(async (invitee) => { const { details } = await sdk.checkEligibilityDetails(invitee) @@ -174,6 +186,7 @@ export async function loadInviteSnapshot( collectableInvitees, eligibility: Object.fromEntries(eligibilityEntries), selfEligibility: selfEligibility.details, + minimums, } } From 05d57bb152988f9a15663b8435413a9c53983d42 Mon Sep 17 00:00:00 2001 From: "goodbounties-nanoclaw-agent[bot]" <307944451+goodbounties-nanoclaw-agent[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 09:04:50 +0000 Subject: [PATCH 2/3] [gdpatchagent] Align how-it-works copy with GoodWallet, use contract thresholds Mirror GoodWallet's InviteView 4-step explainer in the how-it-works drawer, interpolating minimumClaims/minimumDays from state.minimums instead of showing generic hardcoded copy. Co-Authored-By: Claude On-Behalf-Of: gdpatchagent[onecli] (yaskkeryodtdijpv) --- .../src/InviteRewards.tsx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/citizen-claim-widget/src/InviteRewards.tsx b/packages/citizen-claim-widget/src/InviteRewards.tsx index be0151c9..1432edc4 100644 --- a/packages/citizen-claim-widget/src/InviteRewards.tsx +++ b/packages/citizen-claim-widget/src/InviteRewards.tsx @@ -20,11 +20,15 @@ import { decodeInviteCode, formatInviteBounty, useInviteRuntime } from './invite import { canAttachInviter, hasCollectableInvitees, isInviteeCollectable } from './inviteRules' /** - * "How it works" — mirrors GoodWallet's InviteView, which opens the explainer - * in a Drawer from an inline info-icon link rather than showing it inline. + * "How it works" — mirrors GoodWallet's InviteView steps. Copy is hardcoded here + * (English-only, kept simple for now); minimumDays/minimumClaims are the only + * parts that must stay accurate, so those come from InvitesV2 via + * `state.minimums` (InviteSDK.getMinimums()) rather than being hardcoded too. */ function HowItWorksDrawer() { + const { state } = useInviteRuntime() const [open, setOpen] = useState(false) + const { minimums } = state return ( <> @@ -37,12 +41,16 @@ function HowItWorksDrawer() { setOpen(false)}> How it works - 1. Share your code. - 2. Your friend joins and claims. + 1. Share your personal invite link with your friends. + + 2. Make sure they sign up and complete{' '} + {minimums ? minimums.minimumClaims : 'the required number of'} claims. + - 3. After identity, claim-day, and minimum-claim requirements are met, collect your - reward. + 3. After {minimums ? minimums.minimumDays : 'the required number of'} days, the + invite reward becomes available for them to claim. + 4. When they claim it, both of you receive the reward. From ac4896d66ff40a25c8284826e2419002a5fb25ad Mon Sep 17 00:00:00 2001 From: "goodbounties-nanoclaw-agent[bot]" <307944451+goodbounties-nanoclaw-agent[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 09:04:51 +0000 Subject: [PATCH 3/3] [gdpatchagent] Cover snapshot.minimums in inviteAdapter tests Extend the fake SDK double with getMinimums and assert the loaded snapshot carries the contract thresholds. Co-Authored-By: Claude On-Behalf-Of: gdpatchagent[onecli] (yaskkeryodtdijpv) --- tests/widgets/citizen-claim-widget/inviteAdapter.spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/widgets/citizen-claim-widget/inviteAdapter.spec.ts b/tests/widgets/citizen-claim-widget/inviteAdapter.spec.ts index c400acb1..0292e68c 100644 --- a/tests/widgets/citizen-claim-widget/inviteAdapter.spec.ts +++ b/tests/widgets/citizen-claim-widget/inviteAdapter.spec.ts @@ -48,6 +48,10 @@ function createFakeSdk(overrides: Partial = {}): InviteWriteSdk getPendingInvitees: async () => [invitee, collectableInvitee], getCollectableInvitees: async () => [collectableInvitee], checkEligibilityDetails: async () => ({ eligible: true, details: eligibility }), + getMinimums: async () => ({ + minimumClaims: eligibility.minimumClaims, + minimumDays: eligibility.minimumDays, + }), resolveCode: async () => zeroAddress, join: async () => '0xjoin' as `0x${string}`, collectAllBounties: async () => [], @@ -64,6 +68,7 @@ test('loadInviteSnapshot distinguishes pending from collectable invitees', async expect(snapshot.collectableInvitees).toEqual([collectableInvitee]) expect(isInviteeCollectable(invitee, snapshot.collectableInvitees)).toBe(false) expect(isInviteeCollectable(collectableInvitee, snapshot.collectableInvitees)).toBe(true) + expect(snapshot.minimums).toEqual({ minimumClaims: 5, minimumDays: 3 }) }) test('performJoin reuses the caller original code when attaching a deferred inviter', async () => {