Skip to content

Commit 8f2fa9d

Browse files
committed
feat: smart account utilities
1 parent 03f1d51 commit 8f2fa9d

File tree

28 files changed

+3655
-94
lines changed

28 files changed

+3655
-94
lines changed

apps/connect/src/components/create-space.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export function CreateSpace() {
5454

5555
const message: Messages.RequestConnectCreateSpaceEvent = {
5656
type: 'connect-create-space-event',
57+
accountAddress,
5758
event: spaceEvent,
5859
spaceId: spaceEvent.transaction.id,
5960
keyBox: {

apps/connect/src/hooks/use-spaces.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getAppInfoByIds } from '@/lib/get-app-info-by-ids';
2+
import { Connect } from '@graphprotocol/hypergraph';
23
import { useIdentityToken } from '@privy-io/react-auth';
34
import { useQuery } from '@tanstack/react-query';
45

@@ -22,8 +23,10 @@ export const useSpaces = () => {
2223
queryKey: ['spaces'],
2324
queryFn: async () => {
2425
if (!identityToken) return [];
26+
const accountAddress = Connect.loadAccountAddress(localStorage);
27+
if (!accountAddress) return [];
2528
const response = await fetch(`${import.meta.env.VITE_HYPERGRAPH_SYNC_SERVER_ORIGIN}/connect/spaces`, {
26-
headers: { 'privy-id-token': identityToken },
29+
headers: { 'privy-id-token': identityToken, 'account-address': accountAddress },
2730
});
2831
const data = await response.json();
2932
const appIds = new Set<string>();

apps/connect/src/routes/authenticate.tsx

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import { CreateSpace } from '@/components/create-space';
22
import { Button } from '@/components/ui/button';
33
import { useSpaces } from '@/hooks/use-spaces';
44
import { Connect, type Identity, Key, type Messages, StoreConnect, Utils } from '@graphprotocol/hypergraph';
5+
import { GEOGENESIS, GEO_TESTNET } from '@graphprotocol/hypergraph/connect/smart-account';
56
import { useIdentityToken, usePrivy, useWallets } from '@privy-io/react-auth';
67
import { createFileRoute } from '@tanstack/react-router';
78
import { createStore } from '@xstate/store';
89
import { useSelector } from '@xstate/store/react';
910
import { Effect, Schema } from 'effect';
1011
import { useEffect } from 'react';
1112
import { createWalletClient, custom } from 'viem';
12-
import { mainnet } from 'viem/chains';
13+
14+
const CHAIN = import.meta.env.VITE_HYPERGRAPH_CHAIN === 'geogenesis' ? GEOGENESIS : GEO_TESTNET;
1315

1416
type AuthenticateSearch = {
1517
data: unknown;
@@ -150,6 +152,7 @@ function AuthenticateComponent() {
150152
{
151153
headers: {
152154
'privy-id-token': identityToken,
155+
'account-address': accountAddress,
153156
'Content-Type': 'application/json',
154157
},
155158
},
@@ -261,6 +264,7 @@ function AuthenticateComponent() {
261264
const message: Messages.RequestConnectAddAppIdentityToSpaces = {
262265
type: 'connect-add-app-identity-to-spaces',
263266
appIdentityAddress: appIdentity.address,
267+
accountAddress,
264268
spacesInput,
265269
};
266270

@@ -284,6 +288,7 @@ function AuthenticateComponent() {
284288
appIdentityAddress: appIdentity.address,
285289
appIdentityAddressPrivateKey: appIdentity.addressPrivateKey,
286290
accountAddress: accountAddress,
291+
permissionId: appIdentity.permissionId,
287292
encryptionPrivateKey: appIdentity.encryptionPrivateKey,
288293
signaturePrivateKey: appIdentity.signaturePrivateKey,
289294
signaturePublicKey: appIdentity.signaturePublicKey,
@@ -311,7 +316,7 @@ function AuthenticateComponent() {
311316
try {
312317
const privyProvider = await embeddedWallet.getEthereumProvider();
313318
const walletClient = createWalletClient({
314-
chain: mainnet,
319+
chain: CHAIN,
315320
transport: custom(privyProvider),
316321
});
317322

@@ -331,18 +336,33 @@ function AuthenticateComponent() {
331336
};
332337

333338
const newAppIdentity = Connect.createAppIdentity();
339+
// TODO: add spaces and additional actions
340+
const permissionId = await Connect.createSmartSession(
341+
walletClient,
342+
accountAddress,
343+
newAppIdentity.addressPrivateKey,
344+
CHAIN,
345+
import.meta.env.VITE_HYPERGRAPH_RPC_URL,
346+
{
347+
allowCreateSpace: true,
348+
spaces: [],
349+
additionalActions: [],
350+
},
351+
);
352+
334353
const { ciphertext, nonce } = await Connect.encryptAppIdentity(
335354
signer,
336-
accountAddress,
337355
newAppIdentity.address,
338356
newAppIdentity.addressPrivateKey,
357+
permissionId,
339358
keys,
340359
);
341360
const { accountProof, keyProof } = await Connect.proveIdentityOwnership(signer, accountAddress, keys);
342361

343362
const message: Messages.RequestConnectCreateAppIdentity = {
344363
appId: state.appInfo.appId,
345364
address: newAppIdentity.address,
365+
accountAddress,
346366
signaturePublicKey: newAppIdentity.signaturePublicKey,
347367
encryptionPublicKey: newAppIdentity.encryptionPublicKey,
348368
ciphertext,
@@ -372,6 +392,7 @@ function AuthenticateComponent() {
372392
signaturePublicKey: newAppIdentity.signaturePublicKey,
373393
sessionToken: appIdentityResponse.appIdentity.sessionToken,
374394
sessionTokenExpires: new Date(appIdentityResponse.appIdentity.sessionTokenExpires),
395+
permissionId,
375396
},
376397
appInfo: state.appInfo,
377398
});
@@ -392,7 +413,7 @@ function AuthenticateComponent() {
392413

393414
const privyProvider = await embeddedWallet.getEthereumProvider();
394415
const walletClient = createWalletClient({
395-
chain: mainnet,
416+
chain: CHAIN,
396417
transport: custom(privyProvider),
397418
});
398419

@@ -413,7 +434,6 @@ function AuthenticateComponent() {
413434

414435
const decryptedIdentity = await Connect.decryptAppIdentity(
415436
signer,
416-
state.appIdentityResponse.accountAddress,
417437
state.appIdentityResponse.ciphertext,
418438
state.appIdentityResponse.nonce,
419439
);
@@ -422,7 +442,8 @@ function AuthenticateComponent() {
422442
appIdentity: {
423443
address: decryptedIdentity.address,
424444
addressPrivateKey: decryptedIdentity.addressPrivateKey,
425-
accountAddress: decryptedIdentity.accountAddress,
445+
accountAddress: state.appIdentityResponse.accountAddress,
446+
permissionId: decryptedIdentity.permissionId,
426447
encryptionPrivateKey: decryptedIdentity.encryptionPrivateKey,
427448
signaturePrivateKey: decryptedIdentity.signaturePrivateKey,
428449
encryptionPublicKey: decryptedIdentity.encryptionPublicKey,

apps/connect/src/routes/login.lazy.tsx

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { Button } from '@/components/ui/button';
22
import { Connect, type Identity } from '@graphprotocol/hypergraph';
3-
import { useIdentityToken, usePrivy, useWallets } from '@privy-io/react-auth';
3+
import { GEOGENESIS, GEO_TESTNET } from '@graphprotocol/hypergraph/connect/smart-account';
4+
import { type ConnectedWallet, useIdentityToken, usePrivy, useWallets } from '@privy-io/react-auth';
45
import { createLazyFileRoute, useRouter } from '@tanstack/react-router';
56
import { useCallback, useEffect, useState } from 'react';
6-
import { createWalletClient, custom, getAddress } from 'viem';
7-
import { mainnet } from 'viem/chains';
7+
import { type WalletClient, createWalletClient, custom } from 'viem';
88

9+
const CHAIN = import.meta.env.VITE_HYPERGRAPH_CHAIN === 'geogenesis' ? GEOGENESIS : GEO_TESTNET;
910
const syncServerUri = import.meta.env.VITE_HYPERGRAPH_SYNC_SERVER_ORIGIN;
1011
const storage = localStorage;
1112

@@ -21,18 +22,35 @@ function Login() {
2122
const { identityToken } = useIdentityToken();
2223

2324
const hypergraphLogin = useCallback(
24-
async (signer: Identity.Signer) => {
25-
if (!signer || !identityToken) {
25+
async (walletClient: WalletClient, embeddedWallet: ConnectedWallet) => {
26+
if (!identityToken) {
2627
return;
2728
}
29+
const signer: Identity.Signer = {
30+
getAddress: async () => {
31+
const [address] = await walletClient.getAddresses();
32+
return address;
33+
},
34+
signMessage: async (message: string) => {
35+
if (embeddedWallet.walletClientType === 'privy') {
36+
const { signature } = await signMessage({ message });
37+
return signature;
38+
}
39+
const [address] = await walletClient.getAddresses();
40+
return await walletClient.signMessage({ account: address, message });
41+
},
42+
};
43+
2844
const address = await signer.getAddress();
2945
if (!address) {
3046
return;
3147
}
32-
const accountAddress = getAddress(address);
33-
await Connect.login(signer, accountAddress, syncServerUri, storage, identityToken);
48+
49+
const rpcUrl = import.meta.env.VITE_HYPERGRAPH_RPC_URL;
50+
51+
await Connect.login({ walletClient, signer, syncServerUri, storage, identityToken, rpcUrl, chain: CHAIN });
3452
},
35-
[identityToken],
53+
[identityToken, signMessage],
3654
);
3755

3856
useEffect(() => {
@@ -48,26 +66,11 @@ function Login() {
4866
const embeddedWallet = wallets.find((wallet) => wallet.walletClientType === 'privy') || wallets[0];
4967
const privyProvider = await embeddedWallet.getEthereumProvider();
5068
const walletClient = createWalletClient({
51-
chain: mainnet,
69+
chain: CHAIN,
5270
transport: custom(privyProvider),
5371
});
5472

55-
const signer: Identity.Signer = {
56-
getAddress: async () => {
57-
const [address] = await walletClient.getAddresses();
58-
return address;
59-
},
60-
signMessage: async (message: string) => {
61-
if (embeddedWallet.walletClientType === 'privy') {
62-
const { signature } = await signMessage({ message });
63-
return signature;
64-
}
65-
const [address] = await walletClient.getAddresses();
66-
return await walletClient.signMessage({ account: address, message });
67-
},
68-
};
69-
70-
await hypergraphLogin(signer);
73+
await hypergraphLogin(walletClient, embeddedWallet);
7174

7275
const redirect = localStorage.getItem('geo-connect-authenticate-redirect');
7376
if (redirect) {
@@ -83,7 +86,7 @@ function Login() {
8386
}
8487
})();
8588
}
86-
}, [privyAuthenticated, walletsReady, wallets, signMessage, hypergraphLogin, navigate, hypergraphLoginStarted]);
89+
}, [privyAuthenticated, walletsReady, wallets, hypergraphLogin, navigate, hypergraphLoginStarted]);
8790

8891
return (
8992
<div className="flex flex-1 justify-center items-center flex-col gap-4">

apps/events/src/routes/authenticate-success.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ function RouteComponent() {
4545
address: parsedAuthParams.appIdentityAddress,
4646
addressPrivateKey: parsedAuthParams.appIdentityAddressPrivateKey,
4747
accountAddress: parsedAuthParams.accountAddress,
48+
permissionId: parsedAuthParams.permissionId,
4849
signaturePublicKey: parsedAuthParams.signaturePublicKey,
4950
signaturePrivateKey: parsedAuthParams.signaturePrivateKey,
5051
encryptionPublicKey: parsedAuthParams.encryptionPublicKey,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
Warnings:
3+
4+
- Added the required column `connectSignerAddress` to the `Account` table without a default value. This is not possible if the table is not empty.
5+
6+
*/
7+
-- RedefineTables
8+
PRAGMA defer_foreign_keys=ON;
9+
PRAGMA foreign_keys=OFF;
10+
CREATE TABLE "new_Account" (
11+
"address" TEXT NOT NULL PRIMARY KEY,
12+
"connectAddress" TEXT NOT NULL,
13+
"connectCiphertext" TEXT NOT NULL,
14+
"connectNonce" TEXT NOT NULL,
15+
"connectSignaturePublicKey" TEXT NOT NULL,
16+
"connectEncryptionPublicKey" TEXT NOT NULL,
17+
"connectAccountProof" TEXT NOT NULL,
18+
"connectKeyProof" TEXT NOT NULL,
19+
"connectSignerAddress" TEXT NOT NULL
20+
);
21+
INSERT INTO "new_Account" ("address", "connectAccountProof", "connectAddress", "connectCiphertext", "connectEncryptionPublicKey", "connectKeyProof", "connectNonce", "connectSignaturePublicKey") SELECT "address", "connectAccountProof", "connectAddress", "connectCiphertext", "connectEncryptionPublicKey", "connectKeyProof", "connectNonce", "connectSignaturePublicKey" FROM "Account";
22+
DROP TABLE "Account";
23+
ALTER TABLE "new_Account" RENAME TO "Account";
24+
CREATE UNIQUE INDEX "Account_connectAddress_key" ON "Account"("connectAddress");
25+
PRAGMA foreign_keys=ON;
26+
PRAGMA defer_foreign_keys=OFF;

apps/server/prisma/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ model Account {
109109
connectKeyProof String
110110
infoAuthor Space[]
111111
spaceKeyBoxes SpaceKeyBox[]
112+
connectSignerAddress String
112113
}
113114

114115
model AppIdentity {

apps/server/src/handlers/createIdentity.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { prisma } from '../prisma.js';
22

33
type Params = {
4+
signerAddress: string;
45
accountAddress: string;
56
ciphertext: string;
67
nonce: string;
@@ -11,6 +12,7 @@ type Params = {
1112
};
1213

1314
export const createIdentity = async ({
15+
signerAddress,
1416
accountAddress,
1517
ciphertext,
1618
nonce,
@@ -32,6 +34,7 @@ export const createIdentity = async ({
3234
}
3335
return await prisma.account.create({
3436
data: {
37+
connectSignerAddress: signerAddress,
3538
address: accountAddress,
3639
connectAccountProof: accountProof,
3740
connectKeyProof: keyProof,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { prisma } from '../prisma';
2+
3+
export const isSignerForAccount = async (signerAddress: string, accountAddress: string) => {
4+
const account = await prisma.account.findUnique({
5+
where: {
6+
address: accountAddress,
7+
},
8+
});
9+
if (!account) {
10+
return false;
11+
}
12+
return account.connectSignerAddress === signerAddress;
13+
};

0 commit comments

Comments
 (0)