Skip to content

Commit b7cc2fa

Browse files
authored
create space (#540)
1 parent 55a6403 commit b7cc2fa

File tree

7 files changed

+174
-17
lines changed

7 files changed

+174
-17
lines changed

.changeset/plain-eggs-joke.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@graphprotocol/hypergraph-react": patch
3+
---
4+
5+
add privy auth based createSpace hooks
6+

apps/privy-login-example/src/routes/index.tsx

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { store } from '@graphprotocol/hypergraph';
2-
import { useHypergraphApp, useSpaces } from '@graphprotocol/hypergraph-react';
2+
import {
3+
useHypergraphApp,
4+
_usePrivyAuthCreatePrivateSpace as usePrivyAuthCreatePrivateSpace,
5+
_usePrivyAuthCreatePublicSpace as usePrivyAuthCreatePublicSpace,
6+
useSpaces,
7+
} from '@graphprotocol/hypergraph-react';
38
import { createFileRoute, Link } from '@tanstack/react-router';
49
import { useSelector } from '@xstate/store/react';
510
import { useEffect, useState } from 'react';
@@ -18,24 +23,20 @@ function Index() {
1823
const { data: publicSpaces } = useSpaces({ mode: 'public' });
1924
const { data: privateSpaces } = useSpaces({ mode: 'private' });
2025
const [spaceName, setSpaceName] = useState('');
26+
const { createPrivateSpace, isLoading: isCreatingPrivateSpace } = usePrivyAuthCreatePrivateSpace();
27+
const { createPublicSpace, isLoading: isCreatingPublicSpace } = usePrivyAuthCreatePublicSpace();
2128

2229
const accountInboxes = useSelector(store, (state) => state.context.accountInboxes);
23-
const {
24-
createSpace,
25-
listInvitations,
26-
invitations,
27-
acceptInvitation,
28-
createAccountInbox,
29-
getOwnAccountInboxes,
30-
isConnecting,
31-
} = useHypergraphApp();
30+
const { listInvitations, invitations, acceptInvitation, createAccountInbox, getOwnAccountInboxes, isConnecting } =
31+
useHypergraphApp();
3232

3333
useEffect(() => {
3434
if (!isConnecting) {
3535
listInvitations();
3636
getOwnAccountInboxes();
3737
}
3838
}, [isConnecting, listInvitations, getOwnAccountInboxes]);
39+
const [spaceType, setSpaceType] = useState<'private' | 'public'>('private');
3940

4041
if (isConnecting) {
4142
return <div className="flex justify-center items-center h-screen">Loading …</div>;
@@ -76,15 +77,23 @@ function Index() {
7677
</div>
7778
<div className="flex flex-row gap-2 justify-between items-center">
7879
<Input value={spaceName} onChange={(e) => setSpaceName(e.target.value)} />
80+
<select
81+
className="c-input shrink-0"
82+
value={spaceType}
83+
onChange={(e) => setSpaceType(e.target.value as 'private' | 'public')}
84+
>
85+
<option value="private">Private</option>
86+
<option value="public">Public</option>
87+
</select>
7988
<Button
80-
disabled={true} // disabled until we have delegation for creating a space
89+
disabled={isCreatingPrivateSpace || isCreatingPublicSpace}
8190
onClick={async (event) => {
8291
event.preventDefault();
83-
// const smartSessionClient = await getSmartSessionClient();
84-
// if (!smartSessionClient) {
85-
// throw new Error('Missing smartSessionClient');
86-
// }
87-
createSpace({ name: spaceName });
92+
if (spaceType === 'private') {
93+
await createPrivateSpace({ name: spaceName });
94+
} else {
95+
await createPublicSpace({ name: spaceName });
96+
}
8897
setSpaceName('');
8998
}}
9099
>

packages/hypergraph-react/src/HypergraphAppContext.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ export type HypergraphAppCtx = {
116116
redirectFn: (url: URL) => void;
117117
}): void;
118118
processConnectAuthSuccess(params: { storage: Identity.Storage; ciphertext: string; nonce: string }): void;
119+
syncServerUri: string;
119120
};
120121

121122
export const HypergraphAppContext = createContext<HypergraphAppCtx>({
@@ -200,6 +201,7 @@ export const HypergraphAppContext = createContext<HypergraphAppCtx>({
200201
processConnectAuthSuccess() {
201202
throw new Error('processConnectAuthSuccess is missing');
202203
},
204+
syncServerUri: '',
203205
});
204206

205207
export function useHypergraphApp() {
@@ -1553,6 +1555,7 @@ export function HypergraphAppProvider({
15531555
ensureSpaceInbox: ensureSpaceInboxForContext,
15541556
redirectToConnect: redirectToConnectForContext,
15551557
processConnectAuthSuccess: processConnectAuthSuccessForContext,
1558+
syncServerUri,
15561559
}}
15571560
>
15581561
<QueryClientProvider client={queryClient}>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { Key, type Messages, SpaceEvents, SpaceInfo, Utils } from '@graphprotocol/hypergraph';
2+
import * as Effect from 'effect/Effect';
3+
import { useState } from 'react';
4+
import { useHypergraphApp, useHypergraphAuth } from '../HypergraphAppContext.js';
5+
6+
type CreatePrivateSpaceParams = {
7+
name: string;
8+
};
9+
10+
export const usePrivyAuthCreatePrivateSpace = () => {
11+
const [isLoading, setIsLoading] = useState(false);
12+
const { privyIdentity } = useHypergraphAuth();
13+
const { syncServerUri, listSpaces } = useHypergraphApp();
14+
15+
const createPrivateSpace = async ({ name }: CreatePrivateSpaceParams) => {
16+
const accountAddress = privyIdentity?.accountAddress;
17+
if (!accountAddress) {
18+
setIsLoading(false);
19+
throw new Error('No account address found');
20+
}
21+
const encryptionPrivateKey = privyIdentity?.encryptionPrivateKey;
22+
const encryptionPublicKey = privyIdentity?.encryptionPublicKey;
23+
const signaturePrivateKey = privyIdentity?.signaturePrivateKey;
24+
const signaturePublicKey = privyIdentity?.signaturePublicKey;
25+
if (!encryptionPrivateKey || !encryptionPublicKey || !signaturePrivateKey || !signaturePublicKey) {
26+
setIsLoading(false);
27+
throw new Error('No keys found');
28+
}
29+
const privyIdentityToken = privyIdentity?.privyIdentityToken;
30+
if (!privyIdentityToken) {
31+
setIsLoading(false);
32+
throw new Error('No Privy identity token found');
33+
}
34+
setIsLoading(true);
35+
36+
try {
37+
const spaceId = Utils.generateId();
38+
39+
const spaceEvent = await Effect.runPromise(
40+
SpaceEvents.createSpace({
41+
author: {
42+
accountAddress,
43+
encryptionPublicKey,
44+
signaturePrivateKey,
45+
signaturePublicKey,
46+
},
47+
spaceId,
48+
}),
49+
);
50+
const result = Key.createKey({
51+
privateKey: Utils.hexToBytes(encryptionPrivateKey),
52+
publicKey: Utils.hexToBytes(encryptionPublicKey),
53+
});
54+
55+
const { infoContent, signature } = SpaceInfo.encryptAndSignSpaceInfo({
56+
accountAddress,
57+
name,
58+
secretKey: Utils.bytesToHex(result.key),
59+
signaturePrivateKey,
60+
spaceId,
61+
});
62+
63+
const message: Messages.RequestConnectCreateSpaceEvent = {
64+
type: 'connect-create-space-event',
65+
accountAddress,
66+
event: spaceEvent,
67+
spaceId: spaceEvent.transaction.id,
68+
keyBox: {
69+
accountAddress,
70+
ciphertext: Utils.bytesToHex(result.keyBoxCiphertext),
71+
nonce: Utils.bytesToHex(result.keyBoxNonce),
72+
authorPublicKey: encryptionPublicKey,
73+
id: Utils.generateId(),
74+
},
75+
infoContent: Utils.bytesToHex(infoContent),
76+
infoSignature: signature,
77+
name,
78+
};
79+
80+
const response = await fetch(`${syncServerUri}/connect/spaces`, {
81+
headers: {
82+
'privy-id-token': privyIdentityToken,
83+
'Content-Type': 'application/json',
84+
},
85+
method: 'POST',
86+
body: JSON.stringify(message),
87+
});
88+
const data = await response.json();
89+
if (data.space) {
90+
listSpaces(); // list spaces to update the list of private spaces
91+
} else {
92+
throw new Error('Failed to create space');
93+
}
94+
return data.space;
95+
} finally {
96+
setIsLoading(false);
97+
}
98+
};
99+
100+
return { createPrivateSpace, isLoading };
101+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Graph } from '@graphprotocol/grc-20';
2+
import { useQueryClient } from '@tanstack/react-query';
3+
import { useState } from 'react';
4+
import { useHypergraphAuth } from '../HypergraphAppContext.js';
5+
6+
type CreatePublicSpaceParams = {
7+
name: string;
8+
};
9+
10+
export const usePrivyAuthCreatePublicSpace = () => {
11+
const [isLoading, setIsLoading] = useState(false);
12+
const queryClient = useQueryClient();
13+
const { privyIdentity } = useHypergraphAuth();
14+
15+
const createPublicSpace = async ({ name }: CreatePublicSpaceParams) => {
16+
const accountAddress = privyIdentity?.accountAddress;
17+
if (!accountAddress) {
18+
throw new Error('No account address found');
19+
}
20+
try {
21+
setIsLoading(true);
22+
const { id } = await Graph.createSpace({
23+
editorAddress: accountAddress,
24+
name,
25+
network: 'TESTNET',
26+
});
27+
queryClient.invalidateQueries({ queryKey: ['hypergraph-public-spaces'] });
28+
setIsLoading(false);
29+
return id;
30+
} catch (error) {
31+
setIsLoading(false);
32+
throw error;
33+
}
34+
};
35+
return { createPublicSpace, isLoading };
36+
};

packages/hypergraph-react/src/hooks/use-spaces.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const useSpaces = (params: { mode: 'public' | 'private' }) => {
3535
const accountAddress = identityAccountAddress ? identityAccountAddress : privyIdentityAccountAddress;
3636

3737
const publicResult = useQuery({
38-
queryKey: ['hypergraph-spaces', params.mode],
38+
queryKey: ['hypergraph-public-spaces', params.mode],
3939
queryFn: async () => {
4040
const result = await request<PublicSpacesQueryResult>(
4141
`${Graph.TESTNET_API_ORIGIN}/graphql`,

packages/hypergraph-react/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export { useCreateEntity } from './hooks/use-create-entity.js';
1010
export { useDeleteEntity } from './hooks/use-delete-entity.js';
1111
export { useEntity } from './hooks/use-entity.js';
1212
export { useHardDeleteEntity } from './hooks/use-hard-delete-entity.js';
13+
export { usePrivyAuthCreatePrivateSpace as _usePrivyAuthCreatePrivateSpace } from './hooks/use-privy-auth-create-private-space.js';
14+
export { usePrivyAuthCreatePublicSpace as _usePrivyAuthCreatePublicSpace } from './hooks/use-privy-auth-create-public-space.js';
1315
export { useQuery } from './hooks/use-query.js';
1416
export { useRemoveRelation } from './hooks/use-remove-relation.js';
1517
export { useSpace } from './hooks/use-space.js';

0 commit comments

Comments
 (0)