Skip to content

Commit 3145bb6

Browse files
committed
more progress
1 parent fdeb7c6 commit 3145bb6

File tree

9 files changed

+85
-59
lines changed

9 files changed

+85
-59
lines changed

apps/connect/src/routes/authenticate.tsx

Lines changed: 23 additions & 4 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;
@@ -288,6 +290,7 @@ function AuthenticateComponent() {
288290
nonce: appInfo.appNonce,
289291
ephemeralPublicKey: appInfo.ephemeralEncryptionPublicKey,
290292
appIdentityAddress: appIdentity.address,
293+
permissionId: appIdentity.permissionId,
291294
encryptionPrivateKey: appIdentity.encryptionPrivateKey,
292295
signaturePrivateKey: appIdentity.signaturePrivateKey,
293296
signaturePublicKey: appIdentity.signaturePublicKey,
@@ -316,7 +319,7 @@ function AuthenticateComponent() {
316319
try {
317320
const privyProvider = await embeddedWallet.getEthereumProvider();
318321
const walletClient = createWalletClient({
319-
chain: mainnet,
322+
chain: CHAIN,
320323
transport: custom(privyProvider),
321324
});
322325

@@ -336,11 +339,26 @@ function AuthenticateComponent() {
336339
};
337340

338341
const newAppIdentity = Connect.createAppIdentity();
342+
// TODO: add spaces and additional actions
343+
const permissionId = await Connect.createSmartSession(
344+
walletClient,
345+
accountAddress,
346+
newAppIdentity.addressPrivateKey,
347+
CHAIN,
348+
import.meta.env.VITE_HYPERGRAPH_RPC_URL,
349+
{
350+
allowCreateSpace: true,
351+
spaces: [],
352+
additionalActions: [],
353+
},
354+
);
355+
339356
const { ciphertext, nonce } = await Connect.encryptAppIdentity(
340357
signer,
341358
accountAddress,
342359
newAppIdentity.address,
343360
newAppIdentity.addressPrivateKey,
361+
permissionId,
344362
keys,
345363
);
346364
const { accountProof, keyProof } = await Connect.proveIdentityOwnership(signer, accountAddress, keys);
@@ -377,6 +395,7 @@ function AuthenticateComponent() {
377395
signaturePublicKey: newAppIdentity.signaturePublicKey,
378396
sessionToken: appIdentityResponse.appIdentity.sessionToken,
379397
sessionTokenExpires: new Date(appIdentityResponse.appIdentity.sessionTokenExpires),
398+
permissionId,
380399
},
381400
appInfo: state.appInfo,
382401
});
@@ -397,7 +416,7 @@ function AuthenticateComponent() {
397416

398417
const privyProvider = await embeddedWallet.getEthereumProvider();
399418
const walletClient = createWalletClient({
400-
chain: mainnet,
419+
chain: CHAIN,
401420
transport: custom(privyProvider),
402421
});
403422

@@ -418,7 +437,6 @@ function AuthenticateComponent() {
418437

419438
const decryptedIdentity = await Connect.decryptAppIdentity(
420439
signer,
421-
state.appIdentityResponse.accountAddress,
422440
state.appIdentityResponse.ciphertext,
423441
state.appIdentityResponse.nonce,
424442
);
@@ -427,6 +445,7 @@ function AuthenticateComponent() {
427445
appIdentity: {
428446
address: decryptedIdentity.address,
429447
addressPrivateKey: decryptedIdentity.addressPrivateKey,
448+
permissionId: decryptedIdentity.permissionId,
430449
encryptionPrivateKey: decryptedIdentity.encryptionPrivateKey,
431450
signaturePrivateKey: decryptedIdentity.signaturePrivateKey,
432451
encryptionPublicKey: decryptedIdentity.encryptionPublicKey,

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { Button } from '@/components/ui/button';
2-
import { Connect, type Identity, StoreConnect } from '@graphprotocol/hypergraph';
2+
import { Connect, type Identity } from '@graphprotocol/hypergraph';
33
import { GEOGENESIS, GEO_TESTNET } from '@graphprotocol/hypergraph/connect/smart-account';
44
import { type ConnectedWallet, useIdentityToken, usePrivy, useWallets } from '@privy-io/react-auth';
55
import { createLazyFileRoute, useRouter } from '@tanstack/react-router';
6-
import { useSelector } from 'node_modules/@xstate/store/dist/declarations/src/react';
76
import { useCallback, useEffect, useState } from 'react';
87
import { type WalletClient, createWalletClient, custom } from 'viem';
98
import { mainnet } from 'viem/chains';
@@ -79,7 +78,7 @@ function Login() {
7978
}
8079
})();
8180
}
82-
}, [privyAuthenticated, walletsReady, wallets, signMessage, hypergraphLogin, navigate, hypergraphLoginStarted]);
81+
}, [privyAuthenticated, walletsReady, wallets, hypergraphLogin, navigate, hypergraphLoginStarted]);
8382

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

packages/hypergraph/src/connect/create-callback-params.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type CreateAuthUrlParams = {
66
expiry: number;
77
nonce: string;
88
appId: string;
9+
permissionId: string;
910
appIdentityAddress: string;
1011
appIdentityAddressPrivateKey: string;
1112
signaturePublicKey: string;

packages/hypergraph/src/connect/identity-encryption.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export const encryptAppIdentity = async (
138138
accountAddress: string,
139139
appIdentityAddress: string,
140140
appIdentityAddressPrivateKey: string,
141+
permissionId: string,
141142
keys: IdentityKeys,
142143
): Promise<{ ciphertext: string; nonce: string }> => {
143144
const nonce = randomBytes(32);
@@ -164,6 +165,7 @@ export const encryptAppIdentity = async (
164165
keys.signaturePrivateKey,
165166
appIdentityAddress,
166167
appIdentityAddressPrivateKey,
168+
permissionId,
167169
].join('\n');
168170
const keysMsg = new TextEncoder().encode(keysTxt);
169171
const ciphertext = encrypt(keysMsg, secretKey);
@@ -172,7 +174,6 @@ export const encryptAppIdentity = async (
172174

173175
export const decryptAppIdentity = async (
174176
signer: Signer,
175-
accountAddress: string,
176177
ciphertext: string,
177178
nonce: string,
178179
): Promise<Omit<PrivateAppIdentity, 'sessionToken' | 'sessionTokenExpires'>> => {
@@ -181,7 +182,7 @@ export const decryptAppIdentity = async (
181182

182183
// Check that the signature is valid
183184
const valid = await verifyMessage({
184-
address: accountAddress as Hex,
185+
address: (await signer.getAddress()) as Hex,
185186
message,
186187
signature,
187188
});
@@ -209,21 +210,21 @@ export const decryptAppIdentity = async (
209210
}
210211
const keysTxt = new TextDecoder().decode(keysMsg);
211212
const [
212-
accountAddress,
213213
encryptionPublicKey,
214214
encryptionPrivateKey,
215215
signaturePublicKey,
216216
signaturePrivateKey,
217217
appIdentityAddress,
218218
appIdentityAddressPrivateKey,
219+
permissionId,
219220
] = keysTxt.split('\n');
220221
return {
221-
accountAddress,
222222
encryptionPublicKey,
223223
encryptionPrivateKey,
224224
signaturePublicKey,
225225
signaturePrivateKey,
226226
address: appIdentityAddress,
227227
addressPrivateKey: appIdentityAddressPrivateKey,
228+
permissionId,
228229
};
229230
};

packages/hypergraph/src/connect/login.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { loadAccountAddress, storeAccountAddress, storeKeys } from './auth-stora
66
import { createIdentityKeys } from './create-identity-keys.js';
77
import { decryptIdentity, encryptIdentity } from './identity-encryption.js';
88
import { proveIdentityOwnership } from './prove-ownership.js';
9-
import { getSmartAccountWalletClient } from './smart-account.js';
9+
import { getSmartAccountWalletClient, smartAccountNeedsUpdate, updateLegacySmartAccount } from './smart-account.js';
1010
import type { IdentityKeys, Signer, Storage } from './types.js';
1111

1212
export async function identityExists(accountAddress: string, syncServerUri: string) {
@@ -115,6 +115,10 @@ export async function login({
115115
if (!smartAccountWalletClient.account) {
116116
throw new Error('Smart account wallet client not found');
117117
}
118+
// This will prompt the user to sign a user operation to update the smart account
119+
if (await smartAccountNeedsUpdate(smartAccountWalletClient, chain, rpcUrl)) {
120+
await updateLegacySmartAccount(smartAccountWalletClient, chain, rpcUrl);
121+
}
118122
const accountAddress = smartAccountWalletClient.account.address;
119123
if (accountAddressFromStorage === undefined) {
120124
storeAccountAddress(storage, accountAddress);

packages/hypergraph/src/connect/parse-callback-params.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export const parseCallbackParams = ({
5252
return Effect.succeed({
5353
appIdentityAddress: data.appIdentityAddress,
5454
appIdentityAddressPrivateKey: data.appIdentityAddressPrivateKey,
55+
permissionId: data.permissionId,
5556
signaturePublicKey: data.signaturePublicKey,
5657
signaturePrivateKey: data.signaturePrivateKey,
5758
encryptionPublicKey: data.encryptionPublicKey,

packages/hypergraph/src/connect/smart-account.ts

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -168,32 +168,6 @@ export type SmartSessionClient = {
168168
signMessage: ({ message }: { message: SignableMessage }) => Promise<Hex>;
169169
};
170170

171-
const getOwnerAccount = (owner: WalletClient | string): { account: Account | WalletClient; address: Address } => {
172-
if (typeof owner === 'string') {
173-
return {
174-
account: toAccount({
175-
address: owner as `0x${string}`,
176-
signMessage: async () => {
177-
throw new Error('This account cannot sign messages');
178-
},
179-
signTransaction: async () => {
180-
throw new Error('This account cannot sign transactions');
181-
},
182-
signTypedData: async () => {
183-
throw new Error('This account cannot sign typed data');
184-
},
185-
}),
186-
address: owner as `0x${string}`,
187-
};
188-
}
189-
if (!owner.account) {
190-
throw new Error('Wallet client must be an account');
191-
}
192-
return {
193-
account: owner,
194-
address: owner.account.address,
195-
};
196-
};
197171
// Gets the legacy Geo smart account wallet client. If the smart account returned
198172
// by this function is deployed, it means it might need to be updated to have the 7579 module installed
199173
const getLegacySmartAccountWalletClient = async ({
@@ -202,7 +176,7 @@ const getLegacySmartAccountWalletClient = async ({
202176
rpcUrl = DEFAULT_RPC_URL,
203177
apiKey = DEFAULT_API_KEY,
204178
}: {
205-
owner: WalletClient | string;
179+
owner: WalletClient | Account;
206180
chain?: Chain;
207181
rpcUrl?: string;
208182
apiKey?: string;
@@ -215,7 +189,7 @@ const getLegacySmartAccountWalletClient = async ({
215189

216190
const safeAccount = await toSafeSmartAccount({
217191
client: publicClient,
218-
owners: [getOwnerAccount(owner).account],
192+
owners: [owner],
219193
entryPoint: {
220194
// optional, defaults to 0.7
221195
address: entryPoint07Address,
@@ -257,29 +231,31 @@ const get7579SmartAccountWalletClient = async ({
257231
rpcUrl = DEFAULT_RPC_URL,
258232
apiKey = DEFAULT_API_KEY,
259233
}: {
260-
owner: WalletClient | string;
234+
owner: WalletClient | Account;
261235
address?: Hex;
262236
chain?: Chain;
263237
rpcUrl?: string;
264238
apiKey?: string;
265239
}): Promise<SmartAccountClient> => {
266-
const ownerAccount = getOwnerAccount(owner);
267-
268240
const transport = http(rpcUrl);
269241
const publicClient = createPublicClient({
270242
transport,
271243
chain,
272244
});
245+
const ownerAddress = 'account' in owner ? owner.account?.address : owner.address;
246+
if (!ownerAddress) {
247+
throw new Error('Owner address not found');
248+
}
273249

274250
const ownableValidator = getOwnableValidator({
275-
owners: [ownerAccount.address],
251+
owners: [ownerAddress],
276252
threshold: 1,
277253
});
278254
const smartSessionsValidator = getSmartSessionsValidator({});
279255

280256
const safeAccountParams: ToSafeSmartAccountParameters<'0.7', Hex> = {
281257
client: publicClient,
282-
owners: [ownerAccount.account],
258+
owners: [owner],
283259
version: '1.4.1' as const,
284260
entryPoint: {
285261
address: entryPoint07Address,
@@ -349,7 +325,7 @@ export const getSmartAccountWalletClient = async ({
349325
rpcUrl = DEFAULT_RPC_URL,
350326
apiKey = DEFAULT_API_KEY,
351327
}: {
352-
owner: WalletClient | string;
328+
owner: WalletClient | Account;
353329
address?: Hex;
354330
chain?: Chain;
355331
rpcUrl?: string;
@@ -559,7 +535,7 @@ const getSpaceActions = (space: { address: Hex; type: 'personal' | 'public' }) =
559535
// It will return the permissionId that can be used to create a smart session client.
560536
export const createSmartSession = async (
561537
walletClient: WalletClient,
562-
smartAccountClient: SmartAccountClient,
538+
accountAddress: Hex,
563539
sessionPrivateKey: Hex,
564540
chain: Chain,
565541
rpcUrl: string,
@@ -576,12 +552,21 @@ export const createSmartSession = async (
576552
additionalActions?: Action[];
577553
} = {},
578554
): Promise<Hex> => {
555+
const smartAccountClient = await getSmartAccountWalletClient({
556+
owner: walletClient,
557+
address: accountAddress,
558+
chain,
559+
rpcUrl,
560+
});
579561
if (!smartAccountClient.account) {
580-
throw new Error('Invalid smart account');
562+
throw new Error('Invalid wallet client');
581563
}
582564
if (!smartAccountClient.account.isDeployed()) {
583565
throw new Error('Smart account must be deployed');
584566
}
567+
if (await smartAccountNeedsUpdate(smartAccountClient, chain, rpcUrl)) {
568+
throw new Error('Smart account needs to be updated');
569+
}
585570
if (!smartAccountClient.chain) {
586571
throw new Error('Invalid smart account chain');
587572
}
@@ -746,16 +731,30 @@ export const createSmartSession = async (
746731
// This is the function that we use on the end user app to create a smart session client that can send transactions to the smart account.
747732
// The session must have previously been created by the createSmartSession function.
748733
// The client also includes a signMessage function that can be used to sign messages with the session key.
749-
export const getSmartSessionClient = (
750-
smartAccountClient: SmartAccountClient,
751-
sessionPrivateKey: Hex,
752-
permissionId: Hex,
753-
chain: Chain,
754-
rpcUrl: string,
755-
): SmartSessionClient => {
756-
if (!smartAccountClient.account) {
757-
throw new Error('Invalid smart account');
758-
}
734+
export const getSmartSessionClient = async ({
735+
accountAddress,
736+
chain = GEOGENESIS,
737+
rpcUrl = DEFAULT_RPC_URL,
738+
apiKey = DEFAULT_API_KEY,
739+
sessionPrivateKey,
740+
permissionId,
741+
}: {
742+
accountAddress: Hex;
743+
chain?: Chain;
744+
rpcUrl?: string;
745+
apiKey?: string;
746+
sessionPrivateKey: Hex;
747+
permissionId: Hex;
748+
}): Promise<SmartSessionClient> => {
749+
const sessionKeyAccount = privateKeyToAccount(sessionPrivateKey);
750+
const smartAccountClient = await getSmartAccountWalletClient({
751+
owner: sessionKeyAccount, // Won't really be used, but we need to pass in an account
752+
address: accountAddress,
753+
chain,
754+
rpcUrl,
755+
apiKey,
756+
});
757+
759758
const sessionDetails = {
760759
mode: SmartSessionMode.USE,
761760
permissionId,
@@ -768,7 +767,7 @@ export const getSmartSessionClient = (
768767
transport: http(rpcUrl),
769768
chain,
770769
});
771-
const sessionKeyAccount = privateKeyToAccount(sessionPrivateKey);
770+
772771
return {
773772
sendTransaction: async <const calls extends readonly unknown[]>({ calls }: { calls: calls }) => {
774773
if (!smartAccountClient.account) {

packages/hypergraph/src/connect/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export type PublicAppIdentity = {
5858
export type PrivateAppIdentity = IdentityKeys & {
5959
address: string;
6060
addressPrivateKey: string;
61+
permissionId: string;
6162
sessionToken: string;
6263
sessionTokenExpires: Date;
6364
};

0 commit comments

Comments
 (0)