From 89a6b63148548b3bdd533ef512ea7a9278d93ece Mon Sep 17 00:00:00 2001 From: Kris Urbas <605420+krzysu@users.noreply.github.com> Date: Thu, 21 Mar 2024 17:20:31 +0100 Subject: [PATCH] client: add frames module --- .changeset/little-emus-lick.md | 5 + .../scripts/authentication/authenticate.ts | 2 +- .../authentication/authenticateWithWallet.ts | 2 +- .../authentication/validateIdentityToken.ts | 35 + .../scripts/frames/createFrameTypedData.ts | 26 + .../node/scripts/frames/signFrameAction.ts | 31 + .../scripts/frames/verifyFrameSignature.ts | 47 ++ packages/client/src/LensClient.ts | 8 + .../src/authentication/Authentication.ts | 34 +- .../src/authentication/IAuthentication.ts | 26 +- .../adapters/AuthenticationApi.ts | 21 +- .../adapters/Credentials.spec.ts | 14 +- .../authentication/adapters/Credentials.ts | 1 + .../adapters/CredentialsStorage.spec.ts | 3 +- .../adapters/CredentialsStorage.ts | 8 +- .../authentication/graphql/auth.generated.ts | 13 +- .../src/authentication/graphql/auth.graphql | 3 + packages/client/src/graphql/index.ts | 2 + .../client/src/graphql/types.generated.ts | 95 ++- .../client/src/submodules/frames/Frames.ts | 128 ++++ .../frames/graphql/frames.generated.ts | 632 ++++++++++++++++++ .../submodules/frames/graphql/frames.graphql | 45 ++ .../client/src/submodules/frames/index.ts | 6 + packages/client/src/submodules/index.ts | 1 + 24 files changed, 1160 insertions(+), 28 deletions(-) create mode 100644 .changeset/little-emus-lick.md create mode 100644 examples/node/scripts/authentication/validateIdentityToken.ts create mode 100644 examples/node/scripts/frames/createFrameTypedData.ts create mode 100644 examples/node/scripts/frames/signFrameAction.ts create mode 100644 examples/node/scripts/frames/verifyFrameSignature.ts create mode 100644 packages/client/src/submodules/frames/Frames.ts create mode 100644 packages/client/src/submodules/frames/graphql/frames.generated.ts create mode 100644 packages/client/src/submodules/frames/graphql/frames.graphql create mode 100644 packages/client/src/submodules/frames/index.ts diff --git a/.changeset/little-emus-lick.md b/.changeset/little-emus-lick.md new file mode 100644 index 0000000000..ab0c1a9d09 --- /dev/null +++ b/.changeset/little-emus-lick.md @@ -0,0 +1,5 @@ +--- +"@lens-protocol/client": minor +--- + +**feat:** added Frames module diff --git a/examples/node/scripts/authentication/authenticate.ts b/examples/node/scripts/authentication/authenticate.ts index 1d4f9bee29..d4d9bebfda 100644 --- a/examples/node/scripts/authentication/authenticate.ts +++ b/examples/node/scripts/authentication/authenticate.ts @@ -35,7 +35,7 @@ async function main() { console.log(`Is LensClient authenticated? `, await client.authentication.isAuthenticated()); console.log(`Authenticated profileId: `, profileId); console.log(`Access token: `, accessToken); - console.log(`Is access token valid? `, await client.authentication.verify(accessToken)); + console.log(`Is access token valid? `, await client.authentication.verify({ accessToken })); } main(); diff --git a/examples/node/scripts/authentication/authenticateWithWallet.ts b/examples/node/scripts/authentication/authenticateWithWallet.ts index 112c04316a..07b2eb32da 100644 --- a/examples/node/scripts/authentication/authenticateWithWallet.ts +++ b/examples/node/scripts/authentication/authenticateWithWallet.ts @@ -25,7 +25,7 @@ async function main() { console.log(`Is LensClient authenticated? `, await client.authentication.isAuthenticated()); console.log(`Authenticated wallet: `, walletAddress); console.log(`Access token: `, accessToken); - console.log(`Is access token valid? `, await client.authentication.verify(accessToken)); + console.log(`Is access token valid? `, await client.authentication.verify({ accessToken })); } main(); diff --git a/examples/node/scripts/authentication/validateIdentityToken.ts b/examples/node/scripts/authentication/validateIdentityToken.ts new file mode 100644 index 0000000000..88f2985fe2 --- /dev/null +++ b/examples/node/scripts/authentication/validateIdentityToken.ts @@ -0,0 +1,35 @@ +import { LensClient, development } from '@lens-protocol/client'; + +import { setupWallet } from '../shared/setupWallet'; + +async function main() { + const client = new LensClient({ + environment: development, + }); + + const wallet = setupWallet(); + const address = await wallet.getAddress(); + + const managedProfiles = await client.wallet.profilesManaged({ for: wallet.address }); + + if (managedProfiles.items.length === 0) { + throw new Error(`You don't manage any profiles, create one first`); + } + + const { id, text } = await client.authentication.generateChallenge({ + signedBy: address, + for: managedProfiles.items[0].id, + }); + + const signature = await wallet.signMessage(text); + + await client.authentication.authenticate({ id, signature }); + + const identityTokenResult = await client.authentication.getIdentityToken(); + const identityToken = identityTokenResult.unwrap(); + + console.log(`Identity token: `, identityToken); + console.log(`Is identity token valid? `, await client.authentication.verify({ identityToken })); +} + +main(); diff --git a/examples/node/scripts/frames/createFrameTypedData.ts b/examples/node/scripts/frames/createFrameTypedData.ts new file mode 100644 index 0000000000..578a406f0e --- /dev/null +++ b/examples/node/scripts/frames/createFrameTypedData.ts @@ -0,0 +1,26 @@ +import { FramesEip721TypedDataSpec, LensClient, development } from '@lens-protocol/client'; + +async function main() { + const client = new LensClient({ + environment: development, + }); + + const deadline = new Date(); + deadline.setMinutes(deadline.getMinutes() + 30); + + const result = await client.frames.createFrameTypedData({ + actionResponse: '0x0000000000000000000000000000000000000000', + buttonIndex: 2, + deadline: deadline.getTime(), + inputText: 'Hello, World!', + profileId: '0x01', + pubId: '0x01-0x01', + specVersion: FramesEip721TypedDataSpec.OnePointOnePointOne, + state: '{"counter":1,"idempotency_key":"431b8b38-eb4d-455b"}', + url: 'https://mylensframe.xyz', + }); + + console.log(`Result: `, result); +} + +main(); diff --git a/examples/node/scripts/frames/signFrameAction.ts b/examples/node/scripts/frames/signFrameAction.ts new file mode 100644 index 0000000000..3e89c4f700 --- /dev/null +++ b/examples/node/scripts/frames/signFrameAction.ts @@ -0,0 +1,31 @@ +import { FramesEip721TypedDataSpec } from '@lens-protocol/client'; + +import { getAuthenticatedClient } from '../shared/getAuthenticatedClient'; +import { setupWallet } from '../shared/setupWallet'; + +async function main() { + const wallet = setupWallet(); + const client = await getAuthenticatedClient(wallet); + + const result = await client.frames.signFrameAction({ + actionResponse: '0x0000000000000000000000000000000000000000', + buttonIndex: 2, + inputText: 'Hello, World!', + profileId: '0x01', + pubId: '0x01-0x01', + specVersion: FramesEip721TypedDataSpec.OnePointOnePointOne, + state: '{"counter":1,"idempotency_key":"431b8b38-eb4d-455b"}', + url: 'https://mylensframe.xyz', + }); + + if (result.isFailure()) { + console.error(result.error); // CredentialsExpiredError or NotAuthenticatedError + process.exit(1); + } + + const data = result.value; + + console.log(`Result: `, data); +} + +main(); diff --git a/examples/node/scripts/frames/verifyFrameSignature.ts b/examples/node/scripts/frames/verifyFrameSignature.ts new file mode 100644 index 0000000000..11f37f046f --- /dev/null +++ b/examples/node/scripts/frames/verifyFrameSignature.ts @@ -0,0 +1,47 @@ +import { FrameVerifySignatureResult, FramesEip721TypedDataSpec } from '@lens-protocol/client'; + +import { getAuthenticatedClient } from '../shared/getAuthenticatedClient'; +import { setupWallet } from '../shared/setupWallet'; + +async function main() { + const wallet = setupWallet(); + const client = await getAuthenticatedClient(wallet); + const profileId = await client.authentication.getProfileId(); + + if (!profileId) { + throw new Error('Profile not authenticated'); + } + + const identityTokenResult = await client.authentication.getIdentityToken(); + const identityToken = identityTokenResult.unwrap(); + + // get signature + const result = await client.frames.signFrameAction({ + actionResponse: '0x0000000000000000000000000000000000000000', + buttonIndex: 2, + inputText: 'Hello, World!', + profileId: profileId, + pubId: '0x01-0x01', + specVersion: FramesEip721TypedDataSpec.OnePointOnePointOne, + state: '{"counter":1,"idempotency_key":"431b8b38-eb4d-455b"}', + url: 'https://mylensframe.xyz', + }); + + if (result.isFailure()) { + console.error(result.error); // CredentialsExpiredError or NotAuthenticatedError + process.exit(1); + } + + const data = result.value; + + // verify + const verifyResult = await client.frames.verifyFrameSignature({ + identityToken, + signature: data.signature, + signedTypedData: data.signedTypedData, + }); + + console.log(`Is signature valid? `, verifyResult === FrameVerifySignatureResult.Verified); +} + +main(); diff --git a/packages/client/src/LensClient.ts b/packages/client/src/LensClient.ts index 085bf43526..bddaf9b08b 100644 --- a/packages/client/src/LensClient.ts +++ b/packages/client/src/LensClient.ts @@ -7,6 +7,7 @@ import { QueryParams } from './queryParams'; import { Explore, Feed, + Frames, Handle, Invites, Modules, @@ -108,6 +109,13 @@ export class LensClient { return new Feed(this.context, this._authentication); } + /** + * The Frames module + */ + get frames(): Frames { + return new Frames(this.context, this._authentication); + } + /** * The Handle module */ diff --git a/packages/client/src/authentication/Authentication.ts b/packages/client/src/authentication/Authentication.ts index 4d7fe2eb41..5595cc563d 100644 --- a/packages/client/src/authentication/Authentication.ts +++ b/packages/client/src/authentication/Authentication.ts @@ -13,6 +13,7 @@ import type { ChallengeRequest, RevokeAuthenticationRequest, SignedAuthChallenge, + VerifyRequest, WalletAuthenticationToProfileAuthenticationRequest, } from '../graphql/types.generated'; import { buildAuthorizationHeader } from '../helpers/buildAuthorizationHeader'; @@ -39,7 +40,7 @@ export class Authentication implements IAuthentication { } async authenticateWith({ refreshToken }: { refreshToken: string }): Promise { - const credentials = new Credentials(undefined, refreshToken); + const credentials = new Credentials(undefined, undefined, refreshToken); await this.credentials.set(credentials); } @@ -59,8 +60,10 @@ export class Authentication implements IAuthentication { }); } - async verify(accessToken: string): Promise { - return this.api.verify(accessToken); + async verify(request: string | VerifyRequest): Promise { + const correctRequest = typeof request === 'string' ? { accessToken: request } : request; + + return this.api.verify(correctRequest); } async isAuthenticated(): Promise { @@ -109,6 +112,31 @@ export class Authentication implements IAuthentication { return failure(new CredentialsExpiredError()); } + async getIdentityToken(): PromiseResult { + const credentials = await this.credentials.get(); + + if (!credentials) { + return failure(new NotAuthenticatedError()); + } + + if (!credentials.shouldRefresh() && credentials.identityToken) { + return success(credentials.identityToken); + } + + if (credentials.canRefresh()) { + const newCredentials = await this.api.refresh(credentials.refreshToken); + await this.credentials.set(newCredentials); + + if (!newCredentials.identityToken) { + return failure(new CredentialsExpiredError()); + } + + return success(newCredentials.identityToken); + } + + return failure(new CredentialsExpiredError()); + } + async getProfileId(): Promise { const result = await this.getCredentials(); diff --git a/packages/client/src/authentication/IAuthentication.ts b/packages/client/src/authentication/IAuthentication.ts index 8f2d2a9a20..c171d18b6b 100644 --- a/packages/client/src/authentication/IAuthentication.ts +++ b/packages/client/src/authentication/IAuthentication.ts @@ -6,6 +6,7 @@ import type { ChallengeRequest, RevokeAuthenticationRequest, SignedAuthChallenge, + VerifyRequest, WalletAuthenticationToProfileAuthenticationRequest, } from '../graphql/types.generated'; import type { PaginatedResult } from '../helpers/buildPaginatedQueryResult'; @@ -55,12 +56,22 @@ export interface IAuthentication { ): PromiseResult; /** - * Verify that the access token is signed by the server and the user. + * Check the validity of the provided token. * - * @param accessToken - The access token to verify - * @returns Whether the access token is valid + * @deprecated Use with {@link VerifyRequest} instead. + * + * @param request - Access token as a string + * @returns Whether the provided token is valid + */ + verify(request: string): Promise; + + /** + * Check the validity of the provided token. + * + * @param request - Verify request + * @returns Whether the provided token is valid */ - verify(accessToken: string): Promise; + verify(request: VerifyRequest): Promise; /** * Check if the user is authenticated. If the credentials are expired, try to refresh them. @@ -76,6 +87,13 @@ export interface IAuthentication { */ getAccessToken(): PromiseResult; + /** + * Get the identity token. If it expired, try to refresh it. + * + * @returns A Result with the identity token or possible error scenarios + */ + getIdentityToken(): PromiseResult; + /** * Get the authenticated profile id. * diff --git a/packages/client/src/authentication/adapters/AuthenticationApi.ts b/packages/client/src/authentication/adapters/AuthenticationApi.ts index 3e473a3833..ceed3f1b9e 100644 --- a/packages/client/src/authentication/adapters/AuthenticationApi.ts +++ b/packages/client/src/authentication/adapters/AuthenticationApi.ts @@ -5,6 +5,7 @@ import type { ChallengeRequest, RevokeAuthenticationRequest, SignedAuthChallenge, + VerifyRequest, WalletAuthenticationToProfileAuthenticationRequest, } from '../../graphql/types.generated'; import { @@ -33,27 +34,27 @@ export class AuthenticationApi { return result.data.result; } - async verify(accessToken: string): Promise { - const result = await this.sdk.AuthVerify({ request: { accessToken } }); + async verify(request: VerifyRequest): Promise { + const result = await this.sdk.AuthVerify({ request }); return result.data.result; } async authenticate(request: SignedAuthChallenge): Promise { const result = await this.sdk.AuthAuthenticate({ request }); - const { accessToken, refreshToken } = result.data.result; + const { accessToken, identityToken, refreshToken } = result.data.result; - const credentials = new Credentials(accessToken, refreshToken); + const credentials = new Credentials(accessToken, identityToken, refreshToken); credentials.checkClock(); return credentials; } - async refresh(refreshToken: string): Promise { - const result = await this.sdk.AuthRefresh({ request: { refreshToken } }); - const { accessToken: newAccessToken, refreshToken: newRefreshToken } = result.data.result; + async refresh(currentRefreshToken: string): Promise { + const result = await this.sdk.AuthRefresh({ request: { refreshToken: currentRefreshToken } }); + const { accessToken, identityToken, refreshToken } = result.data.result; - const credentials = new Credentials(newAccessToken, newRefreshToken); + const credentials = new Credentials(accessToken, identityToken, refreshToken); credentials.checkClock(); return credentials; @@ -64,9 +65,9 @@ export class AuthenticationApi { headers?: Record, ): Promise { const result = await this.sdk.WalletAuthenticationToProfileAuthentication({ request }, headers); - const { accessToken, refreshToken } = result.data.result; + const { accessToken, identityToken, refreshToken } = result.data.result; - const credentials = new Credentials(accessToken, refreshToken); + const credentials = new Credentials(accessToken, identityToken, refreshToken); return credentials; } diff --git a/packages/client/src/authentication/adapters/Credentials.spec.ts b/packages/client/src/authentication/adapters/Credentials.spec.ts index 7309941a46..5e7971571b 100644 --- a/packages/client/src/authentication/adapters/Credentials.spec.ts +++ b/packages/client/src/authentication/adapters/Credentials.spec.ts @@ -9,6 +9,7 @@ const profileSession = { refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjB4NTYiLCJldm1BZGRyZXNzIjoiMHgzZkM0N2NkRGNGZDU5ZGNlMjA2OTRiNTc1QUZjMUQ5NDE4Njc3NWIwIiwicm9sZSI6InByb2ZpbGVfcmVmcmVzaCIsImF1dGhvcml6YXRpb25JZCI6IjA0NWM2N2I2LWIzNTEtNDVjOS1hNWE1LWM0YWQ5ODg5ZDYyYyIsImlhdCI6MTcwMTM1MzA5NiwiZXhwIjoxNzAxOTU3ODk2fQ.i2kzT4I6VBTuZvjly0TEdGN_YsuBaTDopMQU4_398kA', refreshTokenExp: 1701957896000, + identityToken: '', }; const walletSession = { @@ -18,14 +19,23 @@ const walletSession = { refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjB4YTU2NTNlODhEOWMzNTIzODdkZURkQzc5YmNmOTlmMGFkYTYyZTljNiIsInJvbGUiOiJ3YWxsZXRfcmVmcmVzaCIsImF1dGhvcml6YXRpb25JZCI6IjMxMWVkNzNkLTZjYjMtNDRmZi05NzdmLTJjMmM1MjI4YWJiNCIsImlhdCI6MTcwMTM1NTA3NywiZXhwIjoxNzAxOTU5ODc3fQ.WTUpWsH-Fvv8U4WIwL_Sk6cpHvRSGY_vdBsy1IQrCmM', refreshTokenExp: 1701959877000, + identityToken: '', }; const buildProfileCredentials = () => { - return new Credentials(profileSession.accessToken, profileSession.refreshToken); + return new Credentials( + profileSession.accessToken, + profileSession.identityToken, + profileSession.refreshToken, + ); }; const buildWalletCredentials = () => { - return new Credentials(walletSession.accessToken, walletSession.refreshToken); + return new Credentials( + walletSession.accessToken, + walletSession.identityToken, + walletSession.refreshToken, + ); }; describe(`Given the ${Credentials.name} class`, () => { diff --git a/packages/client/src/authentication/adapters/Credentials.ts b/packages/client/src/authentication/adapters/Credentials.ts index 58695a787c..ff983ec1ca 100644 --- a/packages/client/src/authentication/adapters/Credentials.ts +++ b/packages/client/src/authentication/adapters/Credentials.ts @@ -44,6 +44,7 @@ function isProfileJwtContent(decodedJwt: DecodedJwt): decodedJwt is ProfileJwtPa export class Credentials { constructor( readonly accessToken: string | undefined, + readonly identityToken: string | undefined, readonly refreshToken: string, ) {} diff --git a/packages/client/src/authentication/adapters/CredentialsStorage.spec.ts b/packages/client/src/authentication/adapters/CredentialsStorage.spec.ts index 13aac31ecc..4b9f24a473 100644 --- a/packages/client/src/authentication/adapters/CredentialsStorage.spec.ts +++ b/packages/client/src/authentication/adapters/CredentialsStorage.spec.ts @@ -5,6 +5,7 @@ import { CredentialsStorage } from './CredentialsStorage'; const accessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjB4YjE5QzI4OTBjZjk0N0FEM2YwYjdkN0U1QTlmZkJjZTM2ZDNmOWJkMiIsInJvbGUiOiJub3JtYWwiLCJpYXQiOjE2Mzc3NTQ2ODEsImV4cCI6MTYzNzc1NDc0MX0.Be1eGBvVuFL4fj4pHHqc0yWDledsgS2GP3Jgonmy-xw'; +const identityToken = ''; const refreshToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjB4YjE5QzI4OTBjZjk0N0FEM2YwYjdkN0U1QTlmZkJjZTM2ZDNmOWJkMiIsInJvbGUiOiJyZWZyZXNoIiwiaWF0IjoxNjM3NzU0NjgxLCJleHAiOjE2Mzc3NTQ5ODF9.3SqgsVMyqFPBcem2W9Iog91SWC8cIAFixXBkDue73Rc'; @@ -13,7 +14,7 @@ describe(`Given the ${CredentialsStorage.name} class`, () => { it(`should make the credentials available with ${CredentialsStorage.prototype.get.name} method`, async () => { const storage = new CredentialsStorage(new InMemoryStorageProvider(), 'namespace'); - const creds = new Credentials(accessToken, refreshToken); + const creds = new Credentials(accessToken, identityToken, refreshToken); await storage.set(creds); const storedCreds = await storage.get(); diff --git a/packages/client/src/authentication/adapters/CredentialsStorage.ts b/packages/client/src/authentication/adapters/CredentialsStorage.ts index 631fa2d7dd..65069b662b 100644 --- a/packages/client/src/authentication/adapters/CredentialsStorage.ts +++ b/packages/client/src/authentication/adapters/CredentialsStorage.ts @@ -18,14 +18,16 @@ import { Credentials } from './Credentials'; export class CredentialsStorage implements IStorage { private refreshTokenStorage: IStorage; private accessToken: string | undefined; + private identityToken: string | undefined; constructor(storageProvider: IStorageProvider, namespace: string) { const authStorageSchema = new CredentialsStorageSchema(`lens.${namespace}.credentials`); this.refreshTokenStorage = Storage.createForSchema(authStorageSchema, storageProvider); } - async set({ accessToken, refreshToken }: Credentials): Promise { + async set({ accessToken, identityToken, refreshToken }: Credentials): Promise { this.accessToken = accessToken; + this.identityToken = identityToken; await this.refreshTokenStorage.set({ refreshToken }); } @@ -38,12 +40,14 @@ export class CredentialsStorage implements IStorage { } const accessToken = this.accessToken; + const identityToken = this.identityToken; - return new Credentials(accessToken, refreshToken); + return new Credentials(accessToken, identityToken, refreshToken); } async reset(): Promise { this.accessToken = undefined; + this.identityToken = undefined; await this.refreshTokenStorage.reset(); } diff --git a/packages/client/src/authentication/graphql/auth.generated.ts b/packages/client/src/authentication/graphql/auth.generated.ts index def259d397..ff1a8a00b5 100644 --- a/packages/client/src/authentication/graphql/auth.generated.ts +++ b/packages/client/src/authentication/graphql/auth.generated.ts @@ -48,20 +48,24 @@ export type AuthAuthenticateMutationVariables = Types.Exact<{ request: Types.SignedAuthChallenge; }>; -export type AuthAuthenticateMutation = { result: { accessToken: string; refreshToken: string } }; +export type AuthAuthenticateMutation = { + result: { accessToken: string; refreshToken: string; identityToken: string }; +}; export type AuthRefreshMutationVariables = Types.Exact<{ request: Types.RefreshRequest; }>; -export type AuthRefreshMutation = { result: { accessToken: string; refreshToken: string } }; +export type AuthRefreshMutation = { + result: { accessToken: string; refreshToken: string; identityToken: string }; +}; export type WalletAuthenticationToProfileAuthenticationMutationVariables = Types.Exact<{ request: Types.WalletAuthenticationToProfileAuthenticationRequest; }>; export type WalletAuthenticationToProfileAuthenticationMutation = { - result: { accessToken: string; refreshToken: string }; + result: { accessToken: string; refreshToken: string; identityToken: string }; }; export type RevokeAuthenticationMutationVariables = Types.Exact<{ @@ -388,6 +392,7 @@ export const AuthAuthenticateDocument = { selections: [ { kind: 'Field', name: { kind: 'Name', value: 'accessToken' } }, { kind: 'Field', name: { kind: 'Name', value: 'refreshToken' } }, + { kind: 'Field', name: { kind: 'Name', value: 'identityToken' } }, ], }, }, @@ -432,6 +437,7 @@ export const AuthRefreshDocument = { selections: [ { kind: 'Field', name: { kind: 'Name', value: 'accessToken' } }, { kind: 'Field', name: { kind: 'Name', value: 'refreshToken' } }, + { kind: 'Field', name: { kind: 'Name', value: 'identityToken' } }, ], }, }, @@ -479,6 +485,7 @@ export const WalletAuthenticationToProfileAuthenticationDocument = { selections: [ { kind: 'Field', name: { kind: 'Name', value: 'accessToken' } }, { kind: 'Field', name: { kind: 'Name', value: 'refreshToken' } }, + { kind: 'Field', name: { kind: 'Name', value: 'identityToken' } }, ], }, }, diff --git a/packages/client/src/authentication/graphql/auth.graphql b/packages/client/src/authentication/graphql/auth.graphql index 7349d52d04..7f237bc662 100644 --- a/packages/client/src/authentication/graphql/auth.graphql +++ b/packages/client/src/authentication/graphql/auth.graphql @@ -47,6 +47,7 @@ mutation AuthAuthenticate($request: SignedAuthChallenge!) { result: authenticate(request: $request) { accessToken refreshToken + identityToken } } @@ -54,6 +55,7 @@ mutation AuthRefresh($request: RefreshRequest!) { result: refresh(request: $request) { accessToken refreshToken + identityToken } } @@ -63,6 +65,7 @@ mutation WalletAuthenticationToProfileAuthentication( result: walletAuthenticationToProfileAuthentication(request: $request) { accessToken refreshToken + identityToken } } diff --git a/packages/client/src/graphql/index.ts b/packages/client/src/graphql/index.ts index ff13cc4f6d..babcd103a4 100644 --- a/packages/client/src/graphql/index.ts +++ b/packages/client/src/graphql/index.ts @@ -292,6 +292,8 @@ export { ExplorePublicationType, FeedEventItemType, FollowModuleType, + FramesEip721TypedDataSpec, + FrameVerifySignatureResult, HiddenCommentsType, LensProfileManagerRelayErrorReasonType, LensTransactionFailureType, diff --git a/packages/client/src/graphql/types.generated.ts b/packages/client/src/graphql/types.generated.ts index 83b741c83e..7754f03374 100644 --- a/packages/client/src/graphql/types.generated.ts +++ b/packages/client/src/graphql/types.generated.ts @@ -215,6 +215,31 @@ export enum ComparisonOperatorConditionType { NotEqual = 'NOT_EQUAL', } +export type CreateFrameEip712TypedDataInput = { + /** The typed data domain */ + domain: Eip712TypedDataDomainInput; + /** The types */ + types: CreateFrameEip712TypedDataTypesInput; + /** The values */ + value: CreateFrameEip712TypedDataValueInput; +}; + +export type CreateFrameEip712TypedDataTypesInput = { + FrameData: Array; +}; + +export type CreateFrameEip712TypedDataValueInput = { + actionResponse: Scalars['String']['input']; + buttonIndex: Scalars['Int']['input']; + deadline: Scalars['UnixTimestamp']['input']; + inputText: Scalars['String']['input']; + profileId: Scalars['ProfileId']['input']; + pubId: Scalars['PublicationId']['input']; + specVersion: FramesEip721TypedDataSpec; + state: Scalars['String']['input']; + url: Scalars['URI']['input']; +}; + export type CreateProfileRequest = { followModule?: InputMaybe; to: Scalars['EvmAddress']['input']; @@ -269,6 +294,24 @@ export type DismissRecommendedProfilesRequest = { dismiss: Array; }; +export type Eip712TypedDataDomainInput = { + /** The chainId */ + chainId: Scalars['ChainId']['input']; + /** The name of the typed data domain */ + name: Scalars['String']['input']; + /** The verifying contract */ + verifyingContract: Scalars['EvmAddress']['input']; + /** The version */ + version: Scalars['String']['input']; +}; + +export type Eip712TypedDataFieldInput = { + /** The name of the typed data field */ + name: Scalars['String']['input']; + /** The type of the typed data field */ + type: Scalars['String']['input']; +}; + /** Possible sort criteria for exploring profiles */ export enum ExploreProfilesOrderByType { CreatedOn = 'CREATED_ON', @@ -438,6 +481,50 @@ export type FollowingRequest = { orderBy?: InputMaybe; }; +export type FrameEip712Request = { + actionResponse: Scalars['String']['input']; + buttonIndex: Scalars['Int']['input']; + deadline: Scalars['UnixTimestamp']['input']; + inputText: Scalars['String']['input']; + profileId: Scalars['ProfileId']['input']; + pubId: Scalars['PublicationId']['input']; + specVersion: FramesEip721TypedDataSpec; + state: Scalars['String']['input']; + url: Scalars['URI']['input']; +}; + +export type FrameLensManagerEip712Request = { + actionResponse: Scalars['String']['input']; + buttonIndex: Scalars['Int']['input']; + inputText: Scalars['String']['input']; + profileId: Scalars['ProfileId']['input']; + pubId: Scalars['PublicationId']['input']; + specVersion: FramesEip721TypedDataSpec; + state: Scalars['String']['input']; + url: Scalars['URI']['input']; +}; + +export type FrameVerifySignature = { + /** The identity token */ + identityToken: Scalars['Jwt']['input']; + /** The signature */ + signature: Scalars['Signature']['input']; + /** The typed data signed */ + signedTypedData: CreateFrameEip712TypedDataInput; +}; + +export enum FrameVerifySignatureResult { + DeadlineExpired = 'DEADLINE_EXPIRED', + IdentityCannotUseProfile = 'IDENTITY_CANNOT_USE_PROFILE', + IdentityUnauthorized = 'IDENTITY_UNAUTHORIZED', + SignerAddressCannotUseProfile = 'SIGNER_ADDRESS_CANNOT_USE_PROFILE', + Verified = 'VERIFIED', +} + +export enum FramesEip721TypedDataSpec { + OnePointOnePointOne = 'OnePointOnePointOne', +} + export type FraudReasonInput = { reason: PublicationReportingReason; subreason: PublicationReportingFraudSubreason; @@ -633,6 +720,10 @@ export type LatestPaidActionsFilter = { openActionPublicationMetadataFilters?: InputMaybe; }; +export type LatestPaidActionsWhere = { + customFilters?: InputMaybe>; +}; + export type LegacyCollectRequest = { on: Scalars['PublicationId']['input']; referrer?: InputMaybe; @@ -1856,7 +1947,9 @@ export type ValidatePublicationMetadataRequest = { export type VerifyRequest = { /** The access token to verify */ - accessToken: Scalars['Jwt']['input']; + accessToken?: InputMaybe; + /** The identity token to verify */ + identityToken?: InputMaybe; }; export type WalletAuthenticationToProfileAuthenticationRequest = { diff --git a/packages/client/src/submodules/frames/Frames.ts b/packages/client/src/submodules/frames/Frames.ts new file mode 100644 index 0000000000..0c23e007a9 --- /dev/null +++ b/packages/client/src/submodules/frames/Frames.ts @@ -0,0 +1,128 @@ +import { PromiseResult } from '@lens-protocol/shared-kernel'; + +import type { Authentication } from '../../authentication'; +import { LensContext } from '../../context'; +import { CredentialsExpiredError, NotAuthenticatedError } from '../../errors'; +import { FetchGraphQLClient } from '../../graphql/FetchGraphQLClient'; +import { + FrameEip712Request, + FrameLensManagerEip712Request, + FrameVerifySignature, + FrameVerifySignatureResult, +} from '../../graphql/types.generated'; +import { requireAuthHeaders, sdkAuthHeaderWrapper } from '../../helpers'; +import { + CreateFrameEip712TypedDataFragment, + FrameLensManagerSignatureResultFragment, + Sdk, + getSdk, +} from './graphql/frames.generated'; + +/** + * Lens Frames + * + * @group LensClient Modules + */ +export class Frames { + private readonly sdk: Sdk; + + /** + * @internal + */ + constructor( + context: LensContext, + private readonly authentication: Authentication, + ) { + const client = new FetchGraphQLClient(context); + + this.sdk = getSdk(client, sdkAuthHeaderWrapper(authentication)); + } + + /** + * Create Frame TypedData to sign by user wallet + * + * @param request - The request object + * @returns Typed data for Frame request + * + * @example + * ```ts + * const result = await client.frames.createFrameTypedData({ + * actionResponse: '0x0000000000000000000000000000000000000000', + * buttonIndex: 2, + * deadline: 1711038973, + * inputText: 'Hello, World!', + * profileId: '0x01', + * pubId: '0x01-0x01', + * specVersion: FramesEip721TypedDataSpec.OnePointOnePointOne, + * state: '{"counter":1,"idempotency_key":"431b8b38-eb4d-455b"}', + * url: 'https://mylensframe.xyz', + * }); + * ``` + */ + async createFrameTypedData( + request: FrameEip712Request, + ): Promise { + const response = await this.sdk.CreateFrameTypedData({ request }); + return response.data.result; + } + + /** + * Sign Frame Action + * + * ⚠️ Requires authenticated LensClient. + * + * @param request - The request object + * @returns Signature result + * + * @example + * ```ts + * const result = await client.frames.signFrameAction({ + * actionResponse: '0x0000000000000000000000000000000000000000', + * buttonIndex: 2, + * inputText: 'Hello, World!', + * profileId: '0x01', + * pubId: '0x01-0x01', + * specVersion: FramesEip721TypedDataSpec.OnePointOnePointOne, + * state: '{"counter":1,"idempotency_key":"431b8b38-eb4d-455b"}', + * url: 'https://mylensframe.xyz', + * }); + * ``` + */ + async signFrameAction( + request: FrameLensManagerEip712Request, + ): PromiseResult< + FrameLensManagerSignatureResultFragment, + CredentialsExpiredError | NotAuthenticatedError + > { + return requireAuthHeaders(this.authentication, async (headers) => { + const result = await this.sdk.SignFrameAction( + { + request, + }, + headers, + ); + + return result.data.result; + }); + } + + /** + * Verify Frame signature + * + * @param request - The request object + * @returns Verification result + * + * @example + * ```ts + * const result = await client.frames.verifyFrameSignature({ + * identityToken: identityToken, + * signature: data.signature, + * signedTypedData: data.signedTypedData, + * }); + * ``` + */ + async verifyFrameSignature(request: FrameVerifySignature): Promise { + const response = await this.sdk.VerifyFrameSignature({ request }); + return response.data.result; + } +} diff --git a/packages/client/src/submodules/frames/graphql/frames.generated.ts b/packages/client/src/submodules/frames/graphql/frames.generated.ts new file mode 100644 index 0000000000..9548716f51 --- /dev/null +++ b/packages/client/src/submodules/frames/graphql/frames.generated.ts @@ -0,0 +1,632 @@ +// @ts-nocheck +import * as Types from '../../../graphql/types.generated'; + +import { + Eip712TypedDataDomainFragment, + Eip712TypedDataFieldFragment, + ProfileFragment, + PostFragment, + QuoteFragment, + PaginatedResultInfoFragment, + CommentFragment, + MirrorFragment, + OpenActionResult_KnownCollectOpenActionResult_Fragment, + OpenActionResult_UnknownOpenActionResult_Fragment, + OptimisticStatusResultFragment, + RelaySuccessFragment, + LensProfileManagerRelayErrorFragment, +} from '../../../graphql/fragments.generated'; +import { GraphQLClient } from 'graphql-request'; +import { GraphQLClientRequestHeaders } from 'graphql-request/build/cjs/types'; +import { print } from 'graphql'; +import { DocumentNode } from 'graphql'; +export type VerifyFrameSignatureQueryVariables = Types.Exact<{ + request: Types.FrameVerifySignature; +}>; + +export type VerifyFrameSignatureQuery = { result: Types.FrameVerifySignatureResult }; + +export type CreateFrameTypedDataQueryVariables = Types.Exact<{ + request: Types.FrameEip712Request; +}>; + +export type CreateFrameTypedDataQuery = { result: CreateFrameEip712TypedDataFragment }; + +export type CreateFrameEip712TypedDataFragment = { + types: { FrameData: Array<{ name: string; type: string }> }; + domain: Eip712TypedDataDomainFragment; + value: { + specVersion: Types.FramesEip721TypedDataSpec; + url: string; + buttonIndex: number; + profileId: string; + pubId: string; + inputText: string; + state: string; + actionResponse: string; + deadline: number; + }; +}; + +export type FrameLensManagerSignatureResultFragment = { + signature: string; + signedTypedData: CreateFrameEip712TypedDataFragment; +}; + +export type SignFrameActionMutationVariables = Types.Exact<{ + request: Types.FrameLensManagerEip712Request; +}>; + +export type SignFrameActionMutation = { result: FrameLensManagerSignatureResultFragment }; + +export const CreateFrameEip712TypedDataFragmentDoc = { + kind: 'Document', + definitions: [ + { + kind: 'FragmentDefinition', + name: { kind: 'Name', value: 'CreateFrameEIP712TypedData' }, + typeCondition: { + kind: 'NamedType', + name: { kind: 'Name', value: 'CreateFrameEIP712TypedData' }, + }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'types' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'FrameData' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'name' } }, + { kind: 'Field', name: { kind: 'Name', value: 'type' } }, + ], + }, + }, + ], + }, + }, + { + kind: 'Field', + name: { kind: 'Name', value: 'domain' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'FragmentSpread', name: { kind: 'Name', value: 'EIP712TypedDataDomain' } }, + ], + }, + }, + { + kind: 'Field', + name: { kind: 'Name', value: 'value' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'specVersion' } }, + { kind: 'Field', name: { kind: 'Name', value: 'url' } }, + { kind: 'Field', name: { kind: 'Name', value: 'buttonIndex' } }, + { kind: 'Field', name: { kind: 'Name', value: 'profileId' } }, + { kind: 'Field', name: { kind: 'Name', value: 'pubId' } }, + { kind: 'Field', name: { kind: 'Name', value: 'inputText' } }, + { kind: 'Field', name: { kind: 'Name', value: 'state' } }, + { kind: 'Field', name: { kind: 'Name', value: 'actionResponse' } }, + { kind: 'Field', name: { kind: 'Name', value: 'deadline' } }, + ], + }, + }, + ], + }, + }, + { + kind: 'FragmentDefinition', + name: { kind: 'Name', value: 'EIP712TypedDataDomain' }, + typeCondition: { kind: 'NamedType', name: { kind: 'Name', value: 'EIP712TypedDataDomain' } }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'name' } }, + { kind: 'Field', name: { kind: 'Name', value: 'chainId' } }, + { kind: 'Field', name: { kind: 'Name', value: 'version' } }, + { kind: 'Field', name: { kind: 'Name', value: 'verifyingContract' } }, + ], + }, + }, + ], +} as unknown as DocumentNode; +export const FrameLensManagerSignatureResultFragmentDoc = { + kind: 'Document', + definitions: [ + { + kind: 'FragmentDefinition', + name: { kind: 'Name', value: 'FrameLensManagerSignatureResult' }, + typeCondition: { + kind: 'NamedType', + name: { kind: 'Name', value: 'FrameLensManagerSignatureResult' }, + }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'signedTypedData' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'FragmentSpread', + name: { kind: 'Name', value: 'CreateFrameEIP712TypedData' }, + }, + ], + }, + }, + { kind: 'Field', name: { kind: 'Name', value: 'signature' } }, + ], + }, + }, + { + kind: 'FragmentDefinition', + name: { kind: 'Name', value: 'CreateFrameEIP712TypedData' }, + typeCondition: { + kind: 'NamedType', + name: { kind: 'Name', value: 'CreateFrameEIP712TypedData' }, + }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'types' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'FrameData' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'name' } }, + { kind: 'Field', name: { kind: 'Name', value: 'type' } }, + ], + }, + }, + ], + }, + }, + { + kind: 'Field', + name: { kind: 'Name', value: 'domain' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'FragmentSpread', name: { kind: 'Name', value: 'EIP712TypedDataDomain' } }, + ], + }, + }, + { + kind: 'Field', + name: { kind: 'Name', value: 'value' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'specVersion' } }, + { kind: 'Field', name: { kind: 'Name', value: 'url' } }, + { kind: 'Field', name: { kind: 'Name', value: 'buttonIndex' } }, + { kind: 'Field', name: { kind: 'Name', value: 'profileId' } }, + { kind: 'Field', name: { kind: 'Name', value: 'pubId' } }, + { kind: 'Field', name: { kind: 'Name', value: 'inputText' } }, + { kind: 'Field', name: { kind: 'Name', value: 'state' } }, + { kind: 'Field', name: { kind: 'Name', value: 'actionResponse' } }, + { kind: 'Field', name: { kind: 'Name', value: 'deadline' } }, + ], + }, + }, + ], + }, + }, + { + kind: 'FragmentDefinition', + name: { kind: 'Name', value: 'EIP712TypedDataDomain' }, + typeCondition: { kind: 'NamedType', name: { kind: 'Name', value: 'EIP712TypedDataDomain' } }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'name' } }, + { kind: 'Field', name: { kind: 'Name', value: 'chainId' } }, + { kind: 'Field', name: { kind: 'Name', value: 'version' } }, + { kind: 'Field', name: { kind: 'Name', value: 'verifyingContract' } }, + ], + }, + }, + ], +} as unknown as DocumentNode; +export const VerifyFrameSignatureDocument = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'query', + name: { kind: 'Name', value: 'VerifyFrameSignature' }, + variableDefinitions: [ + { + kind: 'VariableDefinition', + variable: { kind: 'Variable', name: { kind: 'Name', value: 'request' } }, + type: { + kind: 'NonNullType', + type: { kind: 'NamedType', name: { kind: 'Name', value: 'FrameVerifySignature' } }, + }, + }, + ], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + alias: { kind: 'Name', value: 'result' }, + name: { kind: 'Name', value: 'verifyFrameSignature' }, + arguments: [ + { + kind: 'Argument', + name: { kind: 'Name', value: 'request' }, + value: { kind: 'Variable', name: { kind: 'Name', value: 'request' } }, + }, + ], + }, + ], + }, + }, + ], +} as unknown as DocumentNode; +export const CreateFrameTypedDataDocument = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'query', + name: { kind: 'Name', value: 'CreateFrameTypedData' }, + variableDefinitions: [ + { + kind: 'VariableDefinition', + variable: { kind: 'Variable', name: { kind: 'Name', value: 'request' } }, + type: { + kind: 'NonNullType', + type: { kind: 'NamedType', name: { kind: 'Name', value: 'FrameEIP712Request' } }, + }, + }, + ], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + alias: { kind: 'Name', value: 'result' }, + name: { kind: 'Name', value: 'createFrameTypedData' }, + arguments: [ + { + kind: 'Argument', + name: { kind: 'Name', value: 'request' }, + value: { kind: 'Variable', name: { kind: 'Name', value: 'request' } }, + }, + ], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'FragmentSpread', + name: { kind: 'Name', value: 'CreateFrameEIP712TypedData' }, + }, + ], + }, + }, + ], + }, + }, + { + kind: 'FragmentDefinition', + name: { kind: 'Name', value: 'CreateFrameEIP712TypedData' }, + typeCondition: { + kind: 'NamedType', + name: { kind: 'Name', value: 'CreateFrameEIP712TypedData' }, + }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'types' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'FrameData' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'name' } }, + { kind: 'Field', name: { kind: 'Name', value: 'type' } }, + ], + }, + }, + ], + }, + }, + { + kind: 'Field', + name: { kind: 'Name', value: 'domain' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'FragmentSpread', name: { kind: 'Name', value: 'EIP712TypedDataDomain' } }, + ], + }, + }, + { + kind: 'Field', + name: { kind: 'Name', value: 'value' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'specVersion' } }, + { kind: 'Field', name: { kind: 'Name', value: 'url' } }, + { kind: 'Field', name: { kind: 'Name', value: 'buttonIndex' } }, + { kind: 'Field', name: { kind: 'Name', value: 'profileId' } }, + { kind: 'Field', name: { kind: 'Name', value: 'pubId' } }, + { kind: 'Field', name: { kind: 'Name', value: 'inputText' } }, + { kind: 'Field', name: { kind: 'Name', value: 'state' } }, + { kind: 'Field', name: { kind: 'Name', value: 'actionResponse' } }, + { kind: 'Field', name: { kind: 'Name', value: 'deadline' } }, + ], + }, + }, + ], + }, + }, + { + kind: 'FragmentDefinition', + name: { kind: 'Name', value: 'EIP712TypedDataDomain' }, + typeCondition: { kind: 'NamedType', name: { kind: 'Name', value: 'EIP712TypedDataDomain' } }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'name' } }, + { kind: 'Field', name: { kind: 'Name', value: 'chainId' } }, + { kind: 'Field', name: { kind: 'Name', value: 'version' } }, + { kind: 'Field', name: { kind: 'Name', value: 'verifyingContract' } }, + ], + }, + }, + ], +} as unknown as DocumentNode; +export const SignFrameActionDocument = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'mutation', + name: { kind: 'Name', value: 'SignFrameAction' }, + variableDefinitions: [ + { + kind: 'VariableDefinition', + variable: { kind: 'Variable', name: { kind: 'Name', value: 'request' } }, + type: { + kind: 'NonNullType', + type: { + kind: 'NamedType', + name: { kind: 'Name', value: 'FrameLensManagerEIP712Request' }, + }, + }, + }, + ], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + alias: { kind: 'Name', value: 'result' }, + name: { kind: 'Name', value: 'signFrameAction' }, + arguments: [ + { + kind: 'Argument', + name: { kind: 'Name', value: 'request' }, + value: { kind: 'Variable', name: { kind: 'Name', value: 'request' } }, + }, + ], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'FragmentSpread', + name: { kind: 'Name', value: 'FrameLensManagerSignatureResult' }, + }, + ], + }, + }, + ], + }, + }, + { + kind: 'FragmentDefinition', + name: { kind: 'Name', value: 'CreateFrameEIP712TypedData' }, + typeCondition: { + kind: 'NamedType', + name: { kind: 'Name', value: 'CreateFrameEIP712TypedData' }, + }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'types' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'FrameData' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'name' } }, + { kind: 'Field', name: { kind: 'Name', value: 'type' } }, + ], + }, + }, + ], + }, + }, + { + kind: 'Field', + name: { kind: 'Name', value: 'domain' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'FragmentSpread', name: { kind: 'Name', value: 'EIP712TypedDataDomain' } }, + ], + }, + }, + { + kind: 'Field', + name: { kind: 'Name', value: 'value' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'specVersion' } }, + { kind: 'Field', name: { kind: 'Name', value: 'url' } }, + { kind: 'Field', name: { kind: 'Name', value: 'buttonIndex' } }, + { kind: 'Field', name: { kind: 'Name', value: 'profileId' } }, + { kind: 'Field', name: { kind: 'Name', value: 'pubId' } }, + { kind: 'Field', name: { kind: 'Name', value: 'inputText' } }, + { kind: 'Field', name: { kind: 'Name', value: 'state' } }, + { kind: 'Field', name: { kind: 'Name', value: 'actionResponse' } }, + { kind: 'Field', name: { kind: 'Name', value: 'deadline' } }, + ], + }, + }, + ], + }, + }, + { + kind: 'FragmentDefinition', + name: { kind: 'Name', value: 'FrameLensManagerSignatureResult' }, + typeCondition: { + kind: 'NamedType', + name: { kind: 'Name', value: 'FrameLensManagerSignatureResult' }, + }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'signedTypedData' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'FragmentSpread', + name: { kind: 'Name', value: 'CreateFrameEIP712TypedData' }, + }, + ], + }, + }, + { kind: 'Field', name: { kind: 'Name', value: 'signature' } }, + ], + }, + }, + { + kind: 'FragmentDefinition', + name: { kind: 'Name', value: 'EIP712TypedDataDomain' }, + typeCondition: { kind: 'NamedType', name: { kind: 'Name', value: 'EIP712TypedDataDomain' } }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { kind: 'Field', name: { kind: 'Name', value: 'name' } }, + { kind: 'Field', name: { kind: 'Name', value: 'chainId' } }, + { kind: 'Field', name: { kind: 'Name', value: 'version' } }, + { kind: 'Field', name: { kind: 'Name', value: 'verifyingContract' } }, + ], + }, + }, + ], +} as unknown as DocumentNode; + +export type SdkFunctionWrapper = ( + action: (requestHeaders?: Record) => Promise, + operationName: string, + operationType?: string, +) => Promise; + +const defaultWrapper: SdkFunctionWrapper = (action, _operationName, _operationType) => action(); +const VerifyFrameSignatureDocumentString = print(VerifyFrameSignatureDocument); +const CreateFrameTypedDataDocumentString = print(CreateFrameTypedDataDocument); +const SignFrameActionDocumentString = print(SignFrameActionDocument); +export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) { + return { + VerifyFrameSignature( + variables: VerifyFrameSignatureQueryVariables, + requestHeaders?: GraphQLClientRequestHeaders, + ): Promise<{ + data: VerifyFrameSignatureQuery; + extensions?: any; + headers: Dom.Headers; + status: number; + }> { + return withWrapper( + (wrappedRequestHeaders) => + client.rawRequest( + VerifyFrameSignatureDocumentString, + variables, + { ...requestHeaders, ...wrappedRequestHeaders }, + ), + 'VerifyFrameSignature', + 'query', + ); + }, + CreateFrameTypedData( + variables: CreateFrameTypedDataQueryVariables, + requestHeaders?: GraphQLClientRequestHeaders, + ): Promise<{ + data: CreateFrameTypedDataQuery; + extensions?: any; + headers: Dom.Headers; + status: number; + }> { + return withWrapper( + (wrappedRequestHeaders) => + client.rawRequest( + CreateFrameTypedDataDocumentString, + variables, + { ...requestHeaders, ...wrappedRequestHeaders }, + ), + 'CreateFrameTypedData', + 'query', + ); + }, + SignFrameAction( + variables: SignFrameActionMutationVariables, + requestHeaders?: GraphQLClientRequestHeaders, + ): Promise<{ + data: SignFrameActionMutation; + extensions?: any; + headers: Dom.Headers; + status: number; + }> { + return withWrapper( + (wrappedRequestHeaders) => + client.rawRequest(SignFrameActionDocumentString, variables, { + ...requestHeaders, + ...wrappedRequestHeaders, + }), + 'SignFrameAction', + 'mutation', + ); + }, + }; +} +export type Sdk = ReturnType; diff --git a/packages/client/src/submodules/frames/graphql/frames.graphql b/packages/client/src/submodules/frames/graphql/frames.graphql new file mode 100644 index 0000000000..14e7115148 --- /dev/null +++ b/packages/client/src/submodules/frames/graphql/frames.graphql @@ -0,0 +1,45 @@ +query VerifyFrameSignature($request: FrameVerifySignature!) { + result: verifyFrameSignature(request: $request) +} + +query CreateFrameTypedData($request: FrameEIP712Request!) { + result: createFrameTypedData(request: $request) { + ...CreateFrameEIP712TypedData + } +} + +fragment CreateFrameEIP712TypedData on CreateFrameEIP712TypedData { + types { + FrameData { + name + type + } + } + domain { + ...EIP712TypedDataDomain + } + value { + specVersion + url + buttonIndex + profileId + pubId + inputText + state + actionResponse + deadline + } +} + +fragment FrameLensManagerSignatureResult on FrameLensManagerSignatureResult { + signedTypedData { + ...CreateFrameEIP712TypedData + } + signature +} + +mutation SignFrameAction($request: FrameLensManagerEIP712Request!) { + result: signFrameAction(request: $request) { + ...FrameLensManagerSignatureResult + } +} diff --git a/packages/client/src/submodules/frames/index.ts b/packages/client/src/submodules/frames/index.ts new file mode 100644 index 0000000000..b280a7b0b7 --- /dev/null +++ b/packages/client/src/submodules/frames/index.ts @@ -0,0 +1,6 @@ +export * from './Frames'; + +export type { + CreateFrameEip712TypedDataFragment, + FrameLensManagerSignatureResultFragment, +} from './graphql/frames.generated'; diff --git a/packages/client/src/submodules/index.ts b/packages/client/src/submodules/index.ts index f2bccd4c48..1f33dfbd0f 100644 --- a/packages/client/src/submodules/index.ts +++ b/packages/client/src/submodules/index.ts @@ -1,5 +1,6 @@ export * from './explore'; export * from './feed'; +export * from './frames'; export * from './handle'; export * from './invites'; export * from './modules';