Skip to content

Commit 646ccf2

Browse files
authored
Move active campaign concerns to campaign service (#3794)
This moves campaign related logic out of the main service into a dedicated service. It also causes dismissing campaign banners to clear and stop triggering notifications about the same topic. Campaign state is kept within the service db and updates are pushed to state in redux. Latest build: [extension-builds-3794](https://github.com/tahowallet/extension/suites/35331061341/artifacts/2708368343) (as of Fri, 07 Mar 2025 03:08:56 GMT).
2 parents 4671a6e + 62a2801 commit 646ccf2

File tree

16 files changed

+596
-222
lines changed

16 files changed

+596
-222
lines changed

background/networks.ts

+4
Original file line numberDiff line numberDiff line change
@@ -422,3 +422,7 @@ export const isEnrichedEVMTransactionRequest = (
422422
transactionRequest: TransactionRequest,
423423
): transactionRequest is EnrichedEVMTransactionRequest =>
424424
"annotation" in transactionRequest
425+
426+
export const isTransactionWithReceipt = (
427+
transaction: AnyEVMTransaction,
428+
): transaction is ConfirmedEVMTransaction => "status" in transaction

background/redux-slices/selectors/uiSelectors.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const selectShowingActivityDetail = createSelector(
3737
)
3838

3939
export const selectActiveCampaigns = createSelector(
40-
(state: RootState) => state.ui.activeCampaigns,
40+
(state: RootState) => state.ui.campaigns,
4141
(campaigns) => campaigns,
4242
)
4343

background/redux-slices/ui.ts

+28-24
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import { createBackgroundAsyncThunk } from "./utils"
1212
import { UNIXTime } from "../types"
1313
import { DEFAULT_AUTOLOCK_INTERVAL } from "../services/preferences/defaults"
1414
import type { RootState } from "."
15+
import {
16+
CampaignIds,
17+
Campaigns,
18+
FilterCampaignsById,
19+
} from "../services/campaign/types"
1520

1621
export const defaultSettings = {
1722
hideDust: false,
@@ -24,15 +29,9 @@ export const defaultSettings = {
2429
hideBanners: false,
2530
useFlashbots: false,
2631
autoLockInterval: DEFAULT_AUTOLOCK_INTERVAL,
32+
campaigns: {},
2733
}
2834

29-
export type MezoClaimStatus =
30-
| "not-eligible"
31-
| "eligible"
32-
| "claimed-sats"
33-
| "borrowed"
34-
| "campaign-complete"
35-
3635
export type UIState = {
3736
selectedAccount: AddressOnNetwork
3837
showingActivityDetailID: string | null
@@ -55,12 +54,15 @@ export type UIState = {
5554
routeHistoryEntries?: Partial<Location>[]
5655
slippageTolerance: number
5756
accountSignerSettings: AccountSignerSettings[]
58-
activeCampaigns: {
59-
"mezo-claim"?: {
60-
dateFrom: string
61-
dateTo: string
62-
state: MezoClaimStatus
63-
}
57+
// Active user campaigns
58+
campaigns: {
59+
/**
60+
* Some hash used to invalidate cached data and update UI
61+
*/
62+
[campaignId in CampaignIds]?: FilterCampaignsById<
63+
Campaigns,
64+
campaignId
65+
>["data"]
6466
}
6567
}
6668

@@ -79,6 +81,7 @@ export type Events = {
7981
addCustomNetworkResponse: [string, boolean]
8082
updateAutoLockInterval: number
8183
toggleShowTestNetworks: boolean
84+
clearNotification: string
8285
}
8386

8487
export const emitter = new Emittery<Events>()
@@ -94,7 +97,7 @@ export const initialState: UIState = {
9497
snackbarMessage: "",
9598
slippageTolerance: 0.01,
9699
accountSignerSettings: [],
97-
activeCampaigns: {},
100+
campaigns: {},
98101
}
99102

100103
const uiSlice = createSlice({
@@ -239,21 +242,15 @@ const uiSlice = createSlice({
239242
...state,
240243
settings: { ...state.settings, autoLockInterval: payload },
241244
}),
242-
updateCampaignState: <T extends keyof UIState["activeCampaigns"]>(
245+
updateCampaignsState: (
243246
immerState: UIState,
244247
{
245248
payload,
246249
}: {
247-
payload: [T, Partial<UIState["activeCampaigns"][T]>]
250+
payload: UIState["campaigns"]
248251
},
249252
) => {
250-
const [campaignId, update] = payload
251-
252-
immerState.activeCampaigns ??= {}
253-
immerState.activeCampaigns[campaignId] = {
254-
...immerState.activeCampaigns[campaignId],
255-
...update,
256-
}
253+
immerState.campaigns = payload
257254
},
258255
},
259256
})
@@ -279,7 +276,7 @@ export const {
279276
setSlippageTolerance,
280277
setAccountsSignerSettings,
281278
setAutoLockInterval,
282-
updateCampaignState,
279+
updateCampaignsState,
283280
} = uiSlice.actions
284281

285282
export default uiSlice.reducer
@@ -307,6 +304,13 @@ export const deleteAnalyticsData = createBackgroundAsyncThunk(
307304
},
308305
)
309306

307+
export const clearNotification = createBackgroundAsyncThunk(
308+
"ui/clearNotification",
309+
async (id: string) => {
310+
await emitter.emit("clearNotification", id)
311+
},
312+
)
313+
310314
// Async thunk to bubble the setNewDefaultWalletValue action from store to emitter.
311315
export const setNewDefaultWalletValue = createBackgroundAsyncThunk(
312316
"ui/setNewDefaultWalletValue",

background/services/campaign/db.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import Dexie from "dexie"
2+
import { CampaignIds, Campaigns, FilterCampaignsById } from "./types"
3+
4+
export class CampaignDatabase extends Dexie {
5+
private campaigns!: Dexie.Table<Campaigns, CampaignIds>
6+
7+
constructor() {
8+
super("taho/campaigns")
9+
10+
this.version(1).stores({
11+
campaigns: "&id",
12+
})
13+
}
14+
15+
async getActiveCampaigns() {
16+
return this.campaigns
17+
.toCollection()
18+
.filter((campaign) => campaign.enabled)
19+
.toArray()
20+
}
21+
22+
async getCampaignData<K extends CampaignIds>(
23+
id: K,
24+
): Promise<FilterCampaignsById<Campaigns, K> | undefined> {
25+
return this.campaigns.get(id) as Promise<
26+
FilterCampaignsById<Campaigns, K> | undefined
27+
>
28+
}
29+
30+
async upsertCampaign(campaign: Campaigns): Promise<void> {
31+
await this.campaigns.put(campaign)
32+
}
33+
34+
async updateCampaignData<K extends CampaignIds>(
35+
id: K,
36+
data: Partial<FilterCampaignsById<Campaigns, K>["data"]>,
37+
): Promise<void> {
38+
await this.campaigns.toCollection().modify((campaign) => {
39+
if (campaign.id === id) {
40+
Object.assign(campaign, { data })
41+
}
42+
})
43+
}
44+
}
45+
46+
export async function getOrCreateDB(): Promise<CampaignDatabase> {
47+
return new CampaignDatabase()
48+
}

0 commit comments

Comments
 (0)