-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathuse-propose-remove-member.ts
More file actions
140 lines (118 loc) · 5.03 KB
/
Copy pathuse-propose-remove-member.ts
File metadata and controls
140 lines (118 loc) · 5.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
'use client';
import { useMutation } from '@tanstack/react-query';
import { useCallback } from 'react';
import { Effect, Either } from 'effect';
import { type Hex } from 'viem';
import { usePersonalSpaceId } from '~/core/hooks/use-personal-space-id';
import { useSmartAccount } from '~/core/hooks/use-smart-account';
import { useSmartAccountTransaction } from '~/core/hooks/use-smart-account-transaction';
import { useSpace } from '~/core/hooks/use-space';
import { geo } from '~/core/sdk/geo-client';
import { useStatusBar } from '~/core/state/status-bar-store';
import { runEffectEither } from '~/core/telemetry/effect-runtime';
import { SPACE_REGISTRY_ADDRESS } from '~/core/utils/contracts/space-registry';
import { validateSpaceId } from '~/core/utils/utils';
interface UseProposeRemoveMemberArgs {
/** The DAO space ID (bytes16 hex without 0x prefix) to propose removing a member from */
spaceId: string | null;
}
interface ProposeRemoveMemberParams {
/** The space ID (bytes16 hex without 0x prefix) of the member to remove */
targetMemberSpaceId: string;
/** Voting mode: 'fast' (threshold-based) or 'slow' (duration-based). Defaults to 'fast' since removeMember is fast-path-valid. */
votingMode?: 'fast' | 'slow';
}
export function useProposeRemoveMember({ spaceId }: UseProposeRemoveMemberArgs) {
const { dispatch } = useStatusBar();
const { smartAccount } = useSmartAccount();
const { personalSpaceId, isRegistered } = usePersonalSpaceId();
const { space } = useSpace(spaceId ?? undefined);
const tx = useSmartAccountTransaction({
address: SPACE_REGISTRY_ADDRESS,
});
const handleProposeRemoveMember = useCallback(
async ({ targetMemberSpaceId, votingMode = 'fast' }: ProposeRemoveMemberParams) => {
if (!smartAccount) {
const message = 'Please connect your wallet to propose removing a member';
console.error('No smart account available');
dispatch({ type: 'ERROR', payload: message });
throw new Error(message);
}
if (!personalSpaceId || !isRegistered) {
const message = 'You need a registered personal space to propose removing a member';
console.error('User does not have a registered personal space ID');
dispatch({ type: 'ERROR', payload: message });
throw new Error(message);
}
if (!validateSpaceId(spaceId)) {
const message = 'Invalid space ID format. Please try again.';
console.error('Invalid target space ID:', spaceId);
dispatch({ type: 'ERROR', payload: message });
throw new Error(message);
}
if (!validateSpaceId(targetMemberSpaceId)) {
const message = 'Invalid member ID format. Please try again.';
console.error('Invalid target member space ID:', targetMemberSpaceId);
dispatch({ type: 'ERROR', payload: message });
throw new Error(message);
}
// The proposal's removeMember action must call the DAO space contract directly.
if (!space?.address) {
const message = 'No space address found. Please try again.';
console.error('No space address found for space:', spaceId);
dispatch({ type: 'ERROR', payload: message });
throw new Error(message);
}
const normalizedVotingMode = votingMode === 'slow' ? 'SLOW' : 'FAST';
console.log('Proposing to remove member', {
authorSpaceId: personalSpaceId,
spaceId,
targetMemberSpaceId,
votingMode: normalizedVotingMode,
});
const { calldata: callData } = geo.daoSpaces.proposeRemoveMember({
authorSpaceId: personalSpaceId,
spaceId,
daoSpaceAddress: space.address as Hex,
memberToRemoveSpaceId: targetMemberSpaceId,
votingMode: normalizedVotingMode,
});
const writeTxEffect = tx(callData).pipe(
Effect.withSpan('web.write.createProposal.removeMember'),
Effect.annotateSpans({
'io.operation': 'create_proposal',
'space.type': 'DAO',
'governance.action': 'proposal_created',
'governance.proposal_action': 'remove_member',
'governance.voting_mode': normalizedVotingMode,
})
);
const result = await runEffectEither(writeTxEffect);
Either.match(result, {
onLeft: error => {
console.error(
'Failed to propose remove member',
{ spaceId, targetMemberSpaceId, votingMode, personalSpaceId },
error
);
dispatch({
type: 'ERROR',
payload: String(error),
retry: () => handleProposeRemoveMember({ targetMemberSpaceId, votingMode }),
});
// Necessary to propagate error status to useMutation
throw error;
},
onRight: hash => console.log('Successfully proposed to remove member. Transaction hash:', hash),
});
},
[dispatch, smartAccount, personalSpaceId, isRegistered, spaceId, space, tx]
);
const { mutate, status } = useMutation({
mutationFn: handleProposeRemoveMember,
});
return {
proposeRemoveMember: mutate,
status,
};
}