Skip to content

Commit 26e0e93

Browse files
committed
added reward type nad sub-type to tranfers and made them program-level
1 parent 4be9425 commit 26e0e93

File tree

7 files changed

+95
-25
lines changed

7 files changed

+95
-25
lines changed

.env.production

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Production contract addresses (Base mainnet)
2-
NEXT_PUBLIC_REWARDS_PROGRAM_ADDRESS=0x1e8eC692169C15e4EAA68d2965B670798ffb5fFb
3-
NEXT_PUBLIC_STAKING_POOL_ADDRESS=0xd9016b8C79c54Bec77467614A80A6697BC404B84
2+
NEXT_PUBLIC_REWARDS_PROGRAM_ADDRESS=0xc4a9c629310D71de8B8Cd290F0455A81CC67399c
3+
NEXT_PUBLIC_STAKING_POOL_ADDRESS=0x91Ab1543a4E56d20cF6fEFEEE16DFd3ec9956189
44
NEXT_PUBLIC_FULA_TOKEN_ADDRESS=0x9e12735d77c72c5C3670636D428f2F3815d8A4cB
55

66
# WalletConnect

src/app/balance/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ function OwnerActions({ memberWallet, initialProgramId }: { memberWallet: string
164164
const [depositRewardType, setDepositRewardType] = useState(0);
165165
const [depositNote, setDepositNote] = useState("");
166166
const { deposit: depositTokens, isApproving, isDepositing, isPending: isDepPending, isSuccess: depositSuccess, error: depositError } = useDepositTokens();
167-
const { data: rewardTypesData } = useRewardTypes();
167+
const { data: rewardTypesData } = useRewardTypes(pid);
168168

169169
const [disclaimer, setDisclaimer] = useState(false);
170170
const [actionTab, setActionTab] = useState(0);
@@ -177,7 +177,7 @@ function OwnerActions({ memberWallet, initialProgramId }: { memberWallet: string
177177
<TextField label="Program ID" value={actionProgramId} onChange={(e) => setActionProgramId(e.target.value)}
178178
type="number" size="small" sx={{ width: 150, mb: 2 }} />
179179

180-
<Tabs value={actionTab} onChange={(_, v) => setActionTab(v)} sx={{ borderBottom: 1, borderColor: "divider" }}>
180+
<Tabs value={actionTab} onChange={(_, v) => setActionTab(v)} variant="scrollable" scrollButtons="auto" sx={{ borderBottom: 1, borderColor: "divider" }}>
181181
<Tab label="Deposit" />
182182
<Tab label="Transfer to Parent" />
183183
<Tab label="Withdraw" />

src/app/programs/page.tsx

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ function ProgramRow({ programId, filterMine, wallet, isAdmin }: { programId: num
7272

7373
/* -- Reward Types Management (inline) -- */
7474

75-
function RewardTypesSection({ isAdmin }: { isAdmin: boolean }) {
76-
const { data: rewardTypesData } = useRewardTypes();
75+
function RewardTypesSection({ programId, isAdmin }: { programId: number; isAdmin: boolean }) {
76+
const { data: rewardTypesData } = useRewardTypes(programId);
7777
const { addRewardType, isPending: isAddingRT, isConfirming: isConfirmingRT, isSuccess: addRTSuccess, error: addRTError } = useAddRewardType();
7878
const { removeRewardType, isPending: isRemovingRT, isConfirming: isConfirmingRemRT } = useRemoveRewardType();
7979
const [rtId, setRtId] = useState("");
@@ -107,7 +107,7 @@ function RewardTypesSection({ isAdmin }: { isAdmin: boolean }) {
107107
<TableCell>{fromBytes16(names[idx])}</TableCell>
108108
{isAdmin && (
109109
<TableCell>
110-
<IconButton size="small" color="error" onClick={() => removeRewardType(id)}
110+
<IconButton size="small" color="error" onClick={() => removeRewardType(programId, id)}
111111
disabled={isRemovingRT || isConfirmingRemRT}>
112112
<DeleteIcon fontSize="small" />
113113
</IconButton>
@@ -127,7 +127,7 @@ function RewardTypesSection({ isAdmin }: { isAdmin: boolean }) {
127127
<TextField label="Name" value={rtName} onChange={(e) => setRtName(e.target.value)}
128128
size="small" sx={{ flexGrow: 1, minWidth: 120 }} inputProps={{ maxLength: 16 }} />
129129
<Button variant="outlined" size="small"
130-
onClick={() => addRewardType(parseInt(rtId), rtName)}
130+
onClick={() => addRewardType(programId, parseInt(rtId), rtName)}
131131
disabled={isAddingRT || isConfirmingRT || !rtId || !rtName}>
132132
{isAddingRT || isConfirmingRT ? <CircularProgress size={16} /> : "Add"}
133133
</Button>
@@ -143,7 +143,7 @@ function RewardTypesSection({ isAdmin }: { isAdmin: boolean }) {
143143
/* -- Sub-Types Management (inline) -- */
144144

145145
function SubTypesSection({ programId, canManage }: { programId: number; canManage: boolean }) {
146-
const { data: rewardTypesData } = useRewardTypes();
146+
const { data: rewardTypesData } = useRewardTypes(programId);
147147
const [selectedRT, setSelectedRT] = useState(0);
148148
const { data: subTypesData } = useSubTypes(programId, selectedRT);
149149
const { addSubType, isPending: isAddingST, isConfirming: isConfirmingST, isSuccess: addSTSuccess, error: addSTError } = useAddSubType();
@@ -307,13 +307,17 @@ function ProgramDetail({ programId }: { programId: number }) {
307307
const [depNote, setDepNote] = useState("");
308308
const [depDisclaimer, setDepDisclaimer] = useState(false);
309309
const [depRewardType, setDepRewardType] = useState(0);
310-
const { data: rewardTypesForDep } = useRewardTypes();
310+
const { data: rewardTypesForDep } = useRewardTypes(programId);
311311
const [transMemberCode, setTransMemberCode] = useState("");
312312
const [transTo, setTransTo] = useState("");
313313
const [transAmount, setTransAmount] = useState("");
314314
const [transLocked, setTransLocked] = useState(true);
315315
const [transLockDays, setTransLockDays] = useState("0");
316316
const [transNote, setTransNote] = useState("");
317+
const [transRewardType, setTransRewardType] = useState(0);
318+
const [transSubType, setTransSubType] = useState(0);
319+
const { data: rewardTypesForTrans } = useRewardTypes(programId);
320+
const { data: subTypesForTrans } = useSubTypes(programId, transRewardType);
317321
const [transDisclaimer, setTransDisclaimer] = useState(false);
318322
const [parentTo, setParentTo] = useState("");
319323
const [parentAmount, setParentAmount] = useState("");
@@ -620,13 +624,39 @@ function ProgramDetail({ programId }: { programId: number }) {
620624
<InfoOutlinedIcon sx={{ fontSize: 16, color: "text.secondary", cursor: "help" }} />
621625
</Tooltip>
622626
</Box>
627+
{rewardTypesForTrans && (rewardTypesForTrans as [number[], `0x${string}`[]])[0]?.length > 0 && (
628+
<FormControl fullWidth size="small" sx={{ mt: 1 }}>
629+
<InputLabel>Reward Type</InputLabel>
630+
<Select value={transRewardType} onChange={(e) => { setTransRewardType(Number(e.target.value)); setTransSubType(0); }} label="Reward Type">
631+
<MenuItem value={0}>None</MenuItem>
632+
{(rewardTypesForTrans as [number[], `0x${string}`[]])[0].map((id: number, idx: number) => (
633+
<MenuItem key={id} value={id}>
634+
{fromBytes16((rewardTypesForTrans as [number[], `0x${string}`[]])[1][idx]) || `Type ${id}`}
635+
</MenuItem>
636+
))}
637+
</Select>
638+
</FormControl>
639+
)}
640+
{transRewardType > 0 && subTypesForTrans && (subTypesForTrans as [number[], `0x${string}`[]])[0]?.length > 0 && (
641+
<FormControl fullWidth size="small" sx={{ mt: 1 }}>
642+
<InputLabel>Sub-Type</InputLabel>
643+
<Select value={transSubType} onChange={(e) => setTransSubType(Number(e.target.value))} label="Sub-Type">
644+
<MenuItem value={0}>None</MenuItem>
645+
{(subTypesForTrans as [number[], `0x${string}`[]])[0].map((id: number, idx: number) => (
646+
<MenuItem key={id} value={id}>
647+
{fromBytes16((subTypesForTrans as [number[], `0x${string}`[]])[1][idx]) || `Sub-Type ${id}`}
648+
</MenuItem>
649+
))}
650+
</Select>
651+
</FormControl>
652+
)}
623653
<TextField label="Note (optional, max 128)" value={transNote}
624654
onChange={(e) => setTransNote(e.target.value.slice(0, 128))}
625655
fullWidth size="small" sx={{ mt: 1 }} inputProps={{ maxLength: 128 }}
626656
helperText={`${transNote.length}/128`} />
627657
<OnChainDisclaimer accepted={transDisclaimer} onChange={setTransDisclaimer} />
628658
<Button variant="contained" fullWidth sx={{ mt: 1 }}
629-
onClick={() => transfer(programId, transTarget as `0x${string}`, transAmount, transLocked, parseInt(transLockDays) || 0, transNote)}
659+
onClick={() => transfer(programId, transTarget as `0x${string}`, transAmount, transLocked, parseInt(transLockDays) || 0, transRewardType, transSubType, transNote)}
630660
disabled={isTransPending || isTransConf || !transTarget || !transAmount || (!transResolvedAddr && !!transTo && !isValidAddress(transTo)) || !transDisclaimer}>
631661
{isTransPending || isTransConf ? <CircularProgress size={16} /> : "Transfer"}
632662
</Button>
@@ -689,7 +719,7 @@ function ProgramDetail({ programId }: { programId: number }) {
689719
{(isAdmin || canManageSubTypes) && program.active && (
690720
<Paper sx={{ p: 2, mb: 3 }}>
691721
<Typography variant="h6" gutterBottom>Reward Configuration</Typography>
692-
<RewardTypesSection isAdmin={isAdmin || isPA} />
722+
<RewardTypesSection programId={programId} isAdmin={isAdmin || isPA} />
693723
<SubTypesSection programId={programId} canManage={canManageSubTypes} />
694724
</Paper>
695725
)}

src/app/reports/page.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ import { formatFula, shortenAddress, fromBytes16, toBytes12 } from "@/lib/utils"
1818
export default function ReportsPage() {
1919
const theme = useTheme();
2020
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
21-
const { data: rewardTypesData } = useRewardTypes();
22-
2321
const [filterProgramId, setFilterProgramId] = useState("");
22+
const { data: rewardTypesData } = useRewardTypes(Number(filterProgramId) || 0);
2423
const [timeRange, setTimeRange] = useState<TimeRange>("7d");
2524
const [trigger, setTrigger] = useState(0);
2625
const [page, setPage] = useState(0);

src/app/tokens/page.tsx

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { useAccount, useReadContract } from "wagmi";
1111
import { zeroAddress } from "viem";
1212
import {
1313
useDepositTokens, useTransferToSubMember, useTransferToParent,
14-
useWithdraw, useMemberBalance, useTokenBalance, useRewardTypes, useTransferLimit,
14+
useWithdraw, useMemberBalance, useTokenBalance, useRewardTypes, useSubTypes, useTransferLimit,
1515
} from "@/hooks/useRewardsProgram";
1616
import { formatFula, toBytes12, fromBytes12, isValidAddress, formatContractError, fromBytes16, shortenAddress } from "@/lib/utils";
1717
import { OnChainDisclaimer } from "@/components/common/OnChainDisclaimer";
@@ -33,7 +33,7 @@ export default function TokensPage() {
3333
const { data: walletBalance } = useTokenBalance(address);
3434

3535
// Reward types
36-
const { data: rewardTypesData } = useRewardTypes();
36+
const { data: rewardTypesData } = useRewardTypes(pid);
3737

3838
// Transfer limit
3939
const { data: transferLimitData } = useTransferLimit(pid);
@@ -52,8 +52,11 @@ export default function TokensPage() {
5252
const [transferLocked, setTransferLocked] = useState(true);
5353
const [transferLockDays, setTransferLockDays] = useState("0");
5454
const [transferNote, setTransferNote] = useState("");
55+
const [transferRewardType, setTransferRewardType] = useState(0);
56+
const [transferSubType, setTransferSubType] = useState(0);
5557
const [transferDisclaimer, setTransferDisclaimer] = useState(false);
5658
const [scanInfo, setScanInfo] = useState("");
59+
const { data: transferSubTypesData } = useSubTypes(pid, transferRewardType);
5760
const { transfer, isPending: isTransferring, isConfirming: isTransConfirming, isSuccess: transferSuccess, error: transferError } = useTransferToSubMember();
5861

5962
// Resolve member code → storage key
@@ -152,7 +155,7 @@ export default function TokensPage() {
152155
)}
153156

154157
<Paper sx={{ p: 3 }}>
155-
<Tabs value={tab} onChange={(_, v) => setTab(v)}>
158+
<Tabs value={tab} onChange={(_, v) => setTab(v)} variant="scrollable" scrollButtons="auto">
156159
<Tab label="Deposit" />
157160
<Tab label="Transfer to Sub-Member" />
158161
<Tab label="Transfer to Parent" />
@@ -245,13 +248,39 @@ export default function TokensPage() {
245248
</Box>
246249
</Grid>
247250
</Grid>
251+
{rewardTypesData && (rewardTypesData as [number[], `0x${string}`[]])[0]?.length > 0 && (
252+
<FormControl fullWidth margin="normal">
253+
<InputLabel>Reward Type</InputLabel>
254+
<Select value={transferRewardType} onChange={(e) => { setTransferRewardType(Number(e.target.value)); setTransferSubType(0); }} label="Reward Type">
255+
<MenuItem value={0}>None</MenuItem>
256+
{(rewardTypesData as [number[], `0x${string}`[]])[0].map((id: number, idx: number) => (
257+
<MenuItem key={id} value={id}>
258+
{fromBytes16((rewardTypesData as [number[], `0x${string}`[]])[1][idx]) || `Type ${id}`}
259+
</MenuItem>
260+
))}
261+
</Select>
262+
</FormControl>
263+
)}
264+
{transferRewardType > 0 && transferSubTypesData && (transferSubTypesData as [number[], `0x${string}`[]])[0]?.length > 0 && (
265+
<FormControl fullWidth margin="normal">
266+
<InputLabel>Sub-Type</InputLabel>
267+
<Select value={transferSubType} onChange={(e) => setTransferSubType(Number(e.target.value))} label="Sub-Type">
268+
<MenuItem value={0}>None</MenuItem>
269+
{(transferSubTypesData as [number[], `0x${string}`[]])[0].map((id: number, idx: number) => (
270+
<MenuItem key={id} value={id}>
271+
{fromBytes16((transferSubTypesData as [number[], `0x${string}`[]])[1][idx]) || `Sub-Type ${id}`}
272+
</MenuItem>
273+
))}
274+
</Select>
275+
</FormControl>
276+
)}
248277
<TextField label="Note (optional, max 128 chars)" value={transferNote}
249278
onChange={(e) => setTransferNote(e.target.value.slice(0, 128))}
250279
fullWidth margin="normal" inputProps={{ maxLength: 128 }}
251280
helperText={`${transferNote.length}/128`} />
252281
<OnChainDisclaimer accepted={transferDisclaimer} onChange={setTransferDisclaimer} />
253282
<Button variant="contained" sx={{ mt: 2 }}
254-
onClick={() => transfer(pid, transferTarget as `0x${string}`, transferAmount, transferLocked, parseInt(transferLockDays), transferNote)}
283+
onClick={() => transfer(pid, transferTarget as `0x${string}`, transferAmount, transferLocked, parseInt(transferLockDays), transferRewardType, transferSubType, transferNote)}
255284
disabled={isTransferring || isTransConfirming || !transferTarget || !transferAmount || !transferDisclaimer || (!resolvedAddr && !transferToValid)}>
256285
{isTransferring || isTransConfirming ? <CircularProgress size={20} /> : "Transfer"}
257286
</Button>

src/config/contracts.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,9 @@ export const REWARDS_PROGRAM_ABI = [
153153
name: "getRewardTypes",
154154
type: "function",
155155
stateMutability: "view",
156-
inputs: [],
156+
inputs: [
157+
{ name: "programId", type: "uint32" },
158+
],
157159
outputs: [
158160
{ name: "ids", type: "uint8[]" },
159161
{ name: "names", type: "bytes16[]" },
@@ -344,6 +346,8 @@ export const REWARDS_PROGRAM_ABI = [
344346
{ name: "amount", type: "uint256" },
345347
{ name: "locked", type: "bool" },
346348
{ name: "lockTimeDays", type: "uint32" },
349+
{ name: "rewardType", type: "uint8" },
350+
{ name: "subTypeId", type: "uint8" },
347351
{ name: "note", type: "string" },
348352
],
349353
outputs: [],
@@ -403,6 +407,7 @@ export const REWARDS_PROGRAM_ABI = [
403407
type: "function",
404408
stateMutability: "nonpayable",
405409
inputs: [
410+
{ name: "programId", type: "uint32" },
406411
{ name: "typeId", type: "uint8" },
407412
{ name: "name", type: "bytes16" },
408413
],
@@ -413,6 +418,7 @@ export const REWARDS_PROGRAM_ABI = [
413418
type: "function",
414419
stateMutability: "nonpayable",
415420
inputs: [
421+
{ name: "programId", type: "uint32" },
416422
{ name: "typeId", type: "uint8" },
417423
],
418424
outputs: [],
@@ -486,6 +492,8 @@ export const REWARDS_PROGRAM_ABI = [
486492
{ name: "amount", type: "uint256", indexed: false },
487493
{ name: "locked", type: "bool", indexed: false },
488494
{ name: "lockTimeDays", type: "uint32", indexed: false },
495+
{ name: "rewardType", type: "uint8", indexed: false },
496+
{ name: "subTypeId", type: "uint8", indexed: false },
489497
],
490498
},
491499
{

src/hooks/useRewardsProgram.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,13 @@ export function useProgramCodeToId(code: string) {
6060
});
6161
}
6262

63-
export function useRewardTypes() {
63+
export function useRewardTypes(programId: number) {
6464
return useReadContract({
6565
address: CONTRACTS.rewardsProgram,
6666
abi: REWARDS_PROGRAM_ABI,
6767
functionName: "getRewardTypes",
68+
args: [programId],
69+
query: { enabled: programId > 0 },
6870
});
6971
}
7072

@@ -213,6 +215,8 @@ export function useTransferToSubMember() {
213215
amount: string,
214216
locked: boolean,
215217
lockTimeDays: number,
218+
rewardType: number = 0,
219+
subTypeId: number = 0,
216220
note: string = ""
217221
) => {
218222
const parsed = safeParseAmount(amount);
@@ -221,7 +225,7 @@ export function useTransferToSubMember() {
221225
address: CONTRACTS.rewardsProgram,
222226
abi: REWARDS_PROGRAM_ABI,
223227
functionName: "transferToSubMember",
224-
args: [programId, to, parsed, locked, lockTimeDays, note],
228+
args: [programId, to, parsed, locked, lockTimeDays, rewardType, subTypeId, note],
225229
});
226230
};
227231

@@ -379,12 +383,12 @@ export function useAddRewardType() {
379383
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash });
380384
useRefetchOnSuccess(isSuccess);
381385

382-
const addRewardType = (typeId: number, name: string) => {
386+
const addRewardType = (programId: number, typeId: number, name: string) => {
383387
writeContract({
384388
address: CONTRACTS.rewardsProgram,
385389
abi: REWARDS_PROGRAM_ABI,
386390
functionName: "addRewardType",
387-
args: [typeId, toBytes16(name)],
391+
args: [programId, typeId, toBytes16(name)],
388392
});
389393
};
390394

@@ -491,12 +495,12 @@ export function useRemoveRewardType() {
491495
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash });
492496
useRefetchOnSuccess(isSuccess);
493497

494-
const removeRewardType = (typeId: number) => {
498+
const removeRewardType = (programId: number, typeId: number) => {
495499
writeContract({
496500
address: CONTRACTS.rewardsProgram,
497501
abi: REWARDS_PROGRAM_ABI,
498502
functionName: "removeRewardType",
499-
args: [typeId],
503+
args: [programId, typeId],
500504
});
501505
};
502506

0 commit comments

Comments
 (0)