Skip to content

Commit 814c75e

Browse files
authored
feat: XMTP sdk v4 (#1638)
1 parent c3ddcb0 commit 814c75e

File tree

211 files changed

+6296
-8467
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

211 files changed

+6296
-8467
lines changed

.cursorrules

+1-1
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ export const getMessagesQueryOptions = (args: { id: string }) => {
498498
const enabled = !!args.id
499499

500500
return queryOptions({
501-
queryKey: messagesQueryKey(args.id),
501+
queryKey: ["get-message", arg.id],
502502
queryFn: enabled ? () => fetchMessages(args) : skipToken,
503503
enabled,
504504
})

components/group-avatar.tsx

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ConversationTopic, InboxId } from "@xmtp/react-native-sdk"
1+
import { IXmtpConversationTopic, IXmtpInboxId } from "@features/xmtp/xmtp.types"
22
import React, { memo, useMemo } from "react"
33
import { StyleProp, TextStyle, ViewStyle } from "react-native"
44
import { Center } from "@/design-system/Center"
@@ -49,7 +49,7 @@ type ExtraMembersIndicatorProps = {
4949
* Component to render a group avatar from a list of inbox IDs
5050
*/
5151
export const GroupAvatarInboxIds = memo(function GroupAvatarInboxIds(props: {
52-
inboxIds: InboxId[]
52+
inboxIds: IXmtpInboxId[]
5353
}) {
5454
const { inboxIds } = props
5555

@@ -81,7 +81,7 @@ export const GroupAvatarInboxIds = memo(function GroupAvatarInboxIds(props: {
8181
* Will render the group image if available, otherwise shows member avatars
8282
*/
8383
export const GroupAvatar = memo(function GroupAvatar(props: {
84-
groupTopic: ConversationTopic
84+
groupTopic: IXmtpConversationTopic
8585
size?: IGroupAvatarSize
8686
sizeNumber?: number
8787
}) {
@@ -91,14 +91,14 @@ export const GroupAvatar = memo(function GroupAvatar(props: {
9191

9292
// Fetch group data
9393
const { data: group } = useGroupQuery({
94-
account: currentSender.ethereumAddress,
94+
inboxId: currentSender.inboxId,
9595
topic: groupTopic,
9696
})
9797

9898
// Fetch group members
9999
const { data: members } = useGroupMembersQuery({
100100
caller: "GroupAvatar",
101-
account: currentSender.ethereumAddress,
101+
clientInboxId: currentSender.inboxId,
102102
topic: groupTopic,
103103
})
104104

@@ -108,7 +108,7 @@ export const GroupAvatar = memo(function GroupAvatar(props: {
108108
return []
109109
}
110110

111-
return members.ids.reduce<InboxId[]>((inboxIds, memberId) => {
111+
return members.ids.reduce<IXmtpInboxId[]>((inboxIds, memberId) => {
112112
const memberInboxId = members.byId[memberId].inboxId
113113

114114
if (memberInboxId) {
@@ -169,8 +169,8 @@ export const GroupAvatar = memo(function GroupAvatar(props: {
169169
}, [size, theme, sizeNumberProp])
170170

171171
// If group has an image, use it instead of member avatars
172-
if (group?.imageUrlSquare) {
173-
return <Avatar uri={group.imageUrlSquare} sizeNumber={sizeNumber} name={group.name} />
172+
if (group?.groupImageUrl) {
173+
return <Avatar uri={group.groupImageUrl} sizeNumber={sizeNumber} name={group.groupName} />
174174
}
175175

176176
return <GroupAvatarUI members={memberData} size={sizeNumber} />

config/config.types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,7 @@ export type IConfig = {
4343
evm: IEvmConfig
4444
appCheckDebugToken: string | undefined
4545
reactQueryEncryptionKey: string
46+
xmtp: {
47+
maxMsUntilLogError: number
48+
}
4649
}

config/shared.ts

+3
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,7 @@ export const shared = {
4949
Platform.OS === "android"
5050
? process.env.EXPO_PUBLIC_FIREBASE_APP_CHECK_DEBUG_TOKEN_ANDROID
5151
: process.env.EXPO_PUBLIC_FIREBASE_APP_CHECK_DEBUG_TOKEN_IOS,
52+
xmtp: {
53+
maxMsUntilLogError: 3000,
54+
},
5255
} as const satisfies Partial<IConfig>

custom-eslint-plugin/require-promise-error-handling.js

+64-8
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,81 @@ module.exports = {
1313
},
1414
},
1515
create(context) {
16+
// Get the file extension
17+
const filename = context.getFilename()
18+
const isTsxFile = filename.endsWith(".tsx")
19+
1620
// Track if we're inside a React component
1721
let insideComponent = false
1822
// Track if we're inside a try block
1923
let insideTryBlock = 0
2024

25+
// Helper to check if a node is a React component
26+
function isReactComponent(node) {
27+
// Check for JSX in the function body
28+
const hasJSX =
29+
node.body &&
30+
(node.body.type === "JSXElement" ||
31+
(node.body.type === "BlockStatement" &&
32+
node.body.body.some(
33+
(n) =>
34+
n.type === "ReturnStatement" &&
35+
n.argument &&
36+
(n.argument.type === "JSXElement" || n.argument.type === "JSXFragment"),
37+
)))
38+
39+
// Check for React hooks usage
40+
const hasHooks =
41+
node.body &&
42+
node.body.type === "BlockStatement" &&
43+
node.body.body.some(
44+
(n) =>
45+
n.type === "VariableDeclaration" &&
46+
n.declarations.some(
47+
(d) =>
48+
d.init &&
49+
d.init.type === "CallExpression" &&
50+
d.init.callee.name &&
51+
(d.init.callee.name.startsWith("use") || d.init.callee.name === "memo"),
52+
),
53+
)
54+
55+
return hasJSX || hasHooks
56+
}
57+
2158
return {
2259
// Detect React component function declarations
23-
"FunctionDeclaration[id.name=/[A-Z].*/]"(node) {
24-
insideComponent = true
60+
FunctionDeclaration(node) {
61+
if (isTsxFile && isReactComponent(node)) {
62+
insideComponent = true
63+
}
2564
},
2665
// Detect React component arrow functions
27-
"VariableDeclarator[id.name=/[A-Z].*/] > ArrowFunctionExpression"(node) {
28-
insideComponent = true
66+
ArrowFunctionExpression(node) {
67+
if (isTsxFile && isReactComponent(node)) {
68+
insideComponent = true
69+
}
70+
},
71+
// Detect memo wrapped components
72+
"CallExpression[callee.name='memo']"(node) {
73+
if (isTsxFile) {
74+
insideComponent = true
75+
}
2976
},
3077
"FunctionDeclaration:exit"(node) {
31-
insideComponent = false
78+
if (isTsxFile && isReactComponent(node)) {
79+
insideComponent = false
80+
}
3281
},
3382
"ArrowFunctionExpression:exit"(node) {
34-
insideComponent = false
83+
if (isTsxFile && isReactComponent(node)) {
84+
insideComponent = false
85+
}
86+
},
87+
"CallExpression[callee.name='memo']:exit"(node) {
88+
if (isTsxFile) {
89+
insideComponent = false
90+
}
3591
},
3692
// Track try blocks
3793
TryStatement(node) {
@@ -42,7 +98,7 @@ module.exports = {
4298
},
4399
// Check await expressions
44100
AwaitExpression(node) {
45-
if (!insideComponent) return
101+
if (!isTsxFile || !insideComponent) return
46102

47103
// Skip if we're inside a try block
48104
if (insideTryBlock > 0) return
@@ -61,7 +117,7 @@ module.exports = {
61117
},
62118
// Check Promise.then() calls
63119
"CallExpression[callee.property.name='then']"(node) {
64-
if (!insideComponent) return
120+
if (!isTsxFile || !insideComponent) return
65121

66122
// Skip if we're inside a try block
67123
if (insideTryBlock > 0) return

design-system/Header/HeaderAction.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@ export function HeaderAction(props: HeaderActionProps) {
3939

4040
if (ActionComponent) return ActionComponent
4141

42+
const disabledStyle: ViewStyle = disabled ? { opacity: 0.7 } : {}
43+
4244
if (content) {
4345
return (
4446
<TouchableOpacity
45-
style={[themed([$actionTextContainer, { backgroundColor }]), style]}
47+
style={[themed([$actionTextContainer, { backgroundColor }]), style, disabledStyle]}
4648
onPress={onPress}
4749
disabled={disabled || !onPress}
4850
activeOpacity={0.8}
@@ -59,7 +61,7 @@ export function HeaderAction(props: HeaderActionProps) {
5961
// {...debugBorder()}
6062
onPress={onPress}
6163
disabled={disabled || !onPress}
62-
style={[themed([$actionIconContainer, { backgroundColor }]), style]}
64+
style={[themed([$actionIconContainer, { backgroundColor }]), style, disabledStyle]}
6365
hitSlop={theme.spacing.sm}
6466
>
6567
<Icon size={theme.iconSize.lg} icon={icon} color={iconColor} />

features/GroupInvites/joinGroup/JoinGroup.client.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,8 @@
231231
// [getV3IdFromTopic(topic).toLowerCase()]: "allowed",
232232
// });
233233

234-
// const inboxIdsToAllow: InboxId[] = [];
235-
// const inboxIds: { [inboxId: string]: "allowed" } = {};
234+
// const inboxIdsToAllow: IXmtpInboxId[] = [];
235+
// const inboxIds: { [inboxId: IXmtpInboxId]: "allowed" } = {};
236236
// if (
237237
// options.includeAddedBy &&
238238
// conversation.version === ConversationVersion.GROUP &&
@@ -305,7 +305,7 @@
305305
// creatorInboxId: async () => "0xabc" as InboxId,
306306
// name: "Group Name",
307307
// addedByInboxId: "0x123" as InboxId,
308-
// imageUrlSquare: "https://www.google.com",
308+
// groupImageUrl: "https://www.google.com",
309309
// description: "Group Description",
310310
// } as const;
311311

@@ -321,7 +321,7 @@
321321
// creatorInboxId: async () => "0xabc" as InboxId,
322322
// name: "Group Name",
323323
// addedByInboxId: "0x123" as InboxId,
324-
// imageUrlSquare: "https://www.google.com",
324+
// groupImageUrl: "https://www.google.com",
325325
// description: "Group Description",
326326
// };
327327

@@ -398,7 +398,7 @@
398398
// creatorInboxId: async () => "0xabc" as InboxId,
399399
// name: "Group Name",
400400
// addedByInboxId: "0x123" as InboxId,
401-
// imageUrlSquare: "https://www.google.com",
401+
// groupImageUrl: "https://www.google.com",
402402
// description: "Group Description",
403403
// } as const;
404404

@@ -476,7 +476,7 @@
476476
// creatorInboxId: async () => "0xabc" as InboxId,
477477
// name: "Group Name",
478478
// addedByInboxId: "0x123" as InboxId,
479-
// imageUrlSquare: "https://www.google.com",
479+
// groupImageUrl: "https://www.google.com",
480480
// description: "Group Description",
481481
// } as const;
482482

features/auth-onboarding/auth-onboarding.constants.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { config } from "@/config"
22

33
export enum ONBOARDING_ENTERING_DELAY {
44
FIRST = 100,
5-
SECOND = 175,
6-
THIRD = 250,
7-
FOURTH = 850,
8-
FIFTH = 925,
9-
SIXTH = 1000,
5+
SECOND = 125,
6+
THIRD = 175,
7+
FOURTH = 250,
8+
FIFTH = 350,
9+
SIXTH = 500,
1010
}
1111

1212
export const RPID = config.websiteDomain

features/auth-onboarding/components/auth-onboarding-welcome/auth-onboarding-welcome.tsx

+15-12
Original file line numberDiff line numberDiff line change
@@ -109,18 +109,21 @@ function useHeaderWrapper() {
109109

110110
const isProcessingWeb3Stuff = useAuthOnboardingStore((s) => s.isProcessingWeb3Stuff)
111111

112-
return useHeader({
113-
safeAreaEdges: ["top"],
114-
RightActionComponent: (
115-
<AnimatedCenter
116-
entering={theme.animation
117-
.reanimatedFadeInSpringSlow()
118-
.delay(ONBOARDING_ENTERING_DELAY.SIXTH)}
119-
>
120-
<HeaderAction disabled={isProcessingWeb3Stuff} icon="person-badge-key" onPress={login} />
121-
</AnimatedCenter>
122-
),
123-
})
112+
return useHeader(
113+
{
114+
safeAreaEdges: ["top"],
115+
RightActionComponent: (
116+
<AnimatedCenter
117+
entering={theme.animation
118+
.reanimatedFadeInSpringSlow()
119+
.delay(ONBOARDING_ENTERING_DELAY.SIXTH)}
120+
>
121+
<HeaderAction disabled={isProcessingWeb3Stuff} icon="person-badge-key" onPress={login} />
122+
</AnimatedCenter>
123+
),
124+
},
125+
[isProcessingWeb3Stuff],
126+
)
124127
}
125128

126129
const $termsContainer: ThemedStyle<ViewStyle> = ({ spacing, borderRadius, colors }) => ({

features/auth-onboarding/contexts/auth-onboarding.context.tsx

+21-16
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ import { useLogout } from "@/features/authentication/use-logout"
1616
import { useSmartWalletClient } from "@/features/wallets/smart-wallet"
1717
import { createXmtpSignerFromSwc } from "@/features/wallets/utils/create-xmtp-signer-from-swc"
1818
import { createXmtpClient } from "@/features/xmtp/xmtp-client/xmtp-client.service"
19+
import { validateClientInstallation } from "@/features/xmtp/xmtp-installations/xmtp-installations"
20+
import { IXmtpInboxId } from "@/features/xmtp/xmtp.types"
1921
import { captureError, captureErrorWithToast } from "@/utils/capture-error"
22+
import { IEthereumAddress } from "@/utils/evm/address"
2023
import { authLogger } from "@/utils/logger"
2124
import { tryCatch } from "@/utils/try-catch"
2225

@@ -114,29 +117,31 @@ export const AuthOnboardingContextProvider = (props: IAuthOnboardingContextProps
114117
authLogger.debug(`Smart wallet created`)
115118

116119
// Step 3: XMTP Inbox client
117-
const signer = createXmtpSignerFromSwc(swcClient)
118-
const { data: xmtpClient, error: xmtpError } = await tryCatch(
119-
createXmtpClient({
120-
inboxSigner: signer,
121-
}),
122-
)
123-
124-
if (xmtpError) {
125-
throw xmtpError
126-
}
120+
const xmtpClient = await createXmtpClient({
121+
inboxSigner: createXmtpSignerFromSwc(swcClient),
122+
})
127123

128124
if (!xmtpClient) {
129125
throw new Error("XMTP client creation failed")
130126
}
131127

128+
const isValid = await validateClientInstallation({
129+
client: xmtpClient,
130+
})
131+
132+
if (!isValid) {
133+
throw new Error("Invalid client installation")
134+
}
135+
132136
// Step 4: Set the current sender
133137
useMultiInboxStore.getState().actions.setCurrentSender({
134-
ethereumAddress: swcClient.account.address,
135-
inboxId: xmtpClient.inboxId,
138+
ethereumAddress: swcClient.account.address as IEthereumAddress,
139+
inboxId: xmtpClient.inboxId as IXmtpInboxId,
136140
})
137141

138142
await hydrateAuth()
139143
} catch (error) {
144+
useAuthOnboardingStore.getState().actions.reset()
140145
captureErrorWithToast(error, {
141146
message: "Failed to login with passkey",
142147
})
@@ -194,10 +199,9 @@ export const AuthOnboardingContextProvider = (props: IAuthOnboardingContextProps
194199
authLogger.debug(`Smart wallet created`)
195200

196201
// Step 3: Create XMTP Inbox
197-
const signer = createXmtpSignerFromSwc(swcClient)
198202
const { data: xmtpClient, error: xmtpError } = await tryCatch(
199203
createXmtpClient({
200-
inboxSigner: signer,
204+
inboxSigner: createXmtpSignerFromSwc(swcClient),
201205
}),
202206
)
203207

@@ -211,10 +215,11 @@ export const AuthOnboardingContextProvider = (props: IAuthOnboardingContextProps
211215

212216
// Step 4: Set the current sender
213217
useMultiInboxStore.getState().actions.setCurrentSender({
214-
ethereumAddress: swcClient.account.address,
215-
inboxId: xmtpClient.inboxId,
218+
ethereumAddress: swcClient.account.address as IEthereumAddress,
219+
inboxId: xmtpClient.inboxId as IXmtpInboxId,
216220
})
217221
} catch (error) {
222+
useAuthOnboardingStore.getState().actions.reset()
218223
captureErrorWithToast(error, {
219224
message: "Failed to sign up with passkey",
220225
})

0 commit comments

Comments
 (0)