Skip to content

Commit

Permalink
feat: farcaster auth methods (#15)
Browse files Browse the repository at this point in the history
* feat: farcaster auth methods

* refactor: removed blank line
  • Loading branch information
IgorShadurin authored May 10, 2024
1 parent 5685704 commit 7464cf6
Show file tree
Hide file tree
Showing 18 changed files with 978 additions and 90 deletions.
159 changes: 159 additions & 0 deletions src/farcaster-client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { INetworkConfig } from '../network-config'
import { HttpClient } from '../http-client/http-client'
import { DelegatedFs } from '../service/delegated-fs/delegated-fs'
import { ISigner } from '../service/delegated-fs/interfaces'
import { prepareEthAddress } from '../utils/eth'

export interface IUserInfo {
nonce: number
}

export interface IGeneralResponse {
status: string
message?: string
}

export interface ICreateResponse {
status: string
requestId: number
answer: number
message?: string
}

export interface IGetProofResponse {
/**
* Status of the response
*/
status: string
/**
* User main address in the form of hex without 0x prefix
*/
userMainAddress: string
/**
* Delegated address which created by 3rd party application for the user
*/
userDelegatedAddress: string
/**
* Application address in the form of hex without 0x prefix
*/
applicationAddress: string
/**
* Authentication service proof in the form of hex without 0x prefix
*/
authServiceProof: string
/**
* Proof signature from 3rd party service in the form of hex without 0x prefix
*/
serviceProof: string
}

export class FarcasterClient {
constructor(
public readonly config: INetworkConfig,
public readonly httpClient: HttpClient,
) {}

/**
* Returns the URL of the Farcaster Auth Frame
*/
public getAuthFrameUrl(): string {
return this.config.farcasterAuthApiUrl
}

/**
* Checks that FID authorized in the application
* @param fid FID of the user
* @param applicationAddress Address of the app signer
*/
public async isFidAuthorized(fid: number, applicationAddress: string): Promise<boolean> {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return (
await this.httpClient.postJson('v1/authorization/is-authorized', { fid, appSignerAddress: applicationAddress })
).isAuthorized
}

/**
* Creates an authorization request.
* @param messageBytesProof Proof of user's request to the app
* @param userDelegatedAddress Address of a signer created for a user. ETH address with 0x prefix.
* @param signer Service signer to sign the proof of the delegated address
*/
public async createAuthRequest(
messageBytesProof: string,
userDelegatedAddress: string,
signer: ISigner,
): Promise<ICreateResponse> {
const serviceSignature = await signer.signMessage(prepareEthAddress(userDelegatedAddress))

return this.httpClient.postJson<ICreateResponse>('v1/authorization/create', {
messageBytesProof,
userDelegatedAddress,
serviceSignature,
})
}

/**
* Gets the user info in the application.
* @param userAddress User address
* @param applicationAddress Application address
*/
public async getUserInfo(userAddress: string, applicationAddress: string): Promise<IUserInfo> {
return this.httpClient.getJson<IUserInfo>(
`v1/delegated-fs/get-user-info?userAddress=${userAddress}&applicationAddress=${applicationAddress}`,
)
}

/**
* Saves the user data in the application.
* @param userAddress User address
* @param applicationAddress Application address
* @param data Data to save
* @param authServiceProof Proof of the auth service to operate from delegated signer
* @param userDelegatedSigner Delegated signer of the user
*/
public async saveUserAppData(
userAddress: string,
applicationAddress: string,
data: string,
authServiceProof: string,
userDelegatedSigner: ISigner,
): Promise<IGeneralResponse> {
const { nonce } = await this.getUserInfo(userAddress, applicationAddress)
const dataNonce = nonce + 1
const applicationDelegateDataSignature = await DelegatedFs.getDataSignature(data, dataNonce, userDelegatedSigner)

return this.httpClient.postJson<IGeneralResponse>('v1/delegated-fs/save', {
data,
userAddress,
proof: {
nonce: dataNonce,
applicationAddress,
authServiceProof,
applicationDelegateDataSignature,
},
})
}

/**
* Gets the user data in the application.
* @param userAddress User address
* @param applicationAddress Application address
*/
public async getDataByAddress(userAddress: string, applicationAddress: string): Promise<string> {
return this.httpClient.getText(
`v1/delegated-fs/get-by-address?userAddress=${userAddress}&applicationAddress=${applicationAddress}`,
)
}

/**
* Gets the proof of the user's authorization.
* @param userAddress User address
* @param applicationAddress Application address
*/
public async getAuthProofByAddress(userAddress: string, applicationAddress: string): Promise<IGetProofResponse> {
return this.httpClient.getJson<IGetProofResponse>(
`v1/authorization/get-proof?userAddress=${userAddress}&applicationAddress=${applicationAddress}`,
)
}
}
12 changes: 6 additions & 6 deletions src/gateway/gateway-verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ export class GatewayVerification {
async getSmartAccountInfo(address: string): Promise<ISmartAccountInfoResponse> {
assert0xEthAddress(address)

return (await this.httpClient.getJson(
return this.httpClient.getJson<ISmartAccountInfoResponse>(
`${this.version}/${INFO_PATH}/${METHOD_GET_SMART_ACCOUNT_INFO}?address=${address}`,
)) as ISmartAccountInfoResponse
)
}

/**
Expand All @@ -36,20 +36,20 @@ export class GatewayVerification {
* @param smartAccountAddress Smart account address, can be empty
*/
async verifyGoogle(data: string, eoaSignature: string, smartAccountAddress?: string): Promise<IVerifyResponse> {
return (await this.httpClient.postJson(`${this.version}/${VERIFY_PATH}/${METHOD_VERIFY_GOOGLE}`, {
return this.httpClient.postJson<IVerifyResponse>(`${this.version}/${VERIFY_PATH}/${METHOD_VERIFY_GOOGLE}`, {
data,
eoaSignature,
smartAccountAddress,
})) as IVerifyResponse
})
}

/**
* Verifies Farcaster data
* @param clickData Click data
*/
async verifyFarcaster(clickData: string): Promise<IVerifyResponse> {
return (await this.httpClient.postJson(`${this.version}/${VERIFY_PATH}/${METHOD_VERIFY_FARCASTER}`, {
return this.httpClient.postJson<IVerifyResponse>(`${this.version}/${VERIFY_PATH}/${METHOD_VERIFY_FARCASTER}`, {
clickData,
})) as IVerifyResponse
})
}
}
20 changes: 14 additions & 6 deletions src/http-client/http-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,36 @@ export class HttpClient {
* Gets the JSON response for the given path
* @param path Path to append to the base URL
*/
async getJson(path: string): Promise<unknown> {
return (
async getJson<T>(path: string): Promise<T> {
return (await (
await fetch(this.getUrl(path), {
method: 'GET',
})
).json()
).json()) as T
}

/**
* Posts the given form data to the given path and returns the JSON response
* @param path Path to append to the base URL
* @param body Body to send in the request
*/
async postJson(path: string, body: unknown): Promise<unknown> {
return (
async postJson<T>(path: string, body: unknown): Promise<T> {
return (await (
await fetch(this.getUrl(path), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
).json()
).json()) as T
}

async getText(path: string): Promise<string> {
return await (
await fetch(this.getUrl(path), {
method: 'GET',
})
).text()
}
}
37 changes: 31 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,52 @@ import * as RpcHelperUtils from './rpc-helper/utils'
import { Wallet, HDNodeWallet } from 'ethers'
import { FilesystemChanges } from './filesystem-changes'
import { Verification } from './verification'
import { FarcasterClient } from './farcaster-client'
import { HttpClient } from './http-client/http-client'
import { convertHDNodeWalletToAccountSigner } from './rpc-helper/utils'

/**
* Export all things that should be available for the user of the library
*/
export { Account, Config, Gateway, Connections, Utils, SmartAccountSigner, RpcHelperUtils, Wallet, Verification }
export {
Account,
Config,
Gateway,
Connections,
Utils,
SmartAccountSigner,
RpcHelperUtils,
Wallet,
Verification,
FarcasterClient,
}

export class SDK {
public readonly signer: SmartAccountSigner

public readonly account: Account
public readonly gateway: Gateway
public readonly connections: Connections
public readonly filesystemChanges: FilesystemChanges
public readonly verification: Verification
public readonly farcasterClient: FarcasterClient

constructor(
public readonly networkConfig: INetworkConfig,
public readonly signer: SmartAccountSigner,
signerOrMnemonic: SmartAccountSigner | string,
) {
this.account = new Account(networkConfig, signer)
if (typeof signerOrMnemonic === 'string') {
this.signer = convertHDNodeWalletToAccountSigner(HDNodeWallet.fromPhrase(signerOrMnemonic))
} else {
this.signer = signerOrMnemonic
}

this.account = new Account(networkConfig, this.signer)
this.gateway = new Gateway(networkConfig.appAuthUrl, networkConfig.verificationRpcUrl)
this.connections = new Connections(networkConfig, this.account.rpcHelper, signer)
this.filesystemChanges = new FilesystemChanges(networkConfig, this.account.rpcHelper, signer)
this.verification = new Verification(networkConfig, this.account.rpcHelper, signer)
this.connections = new Connections(networkConfig, this.account.rpcHelper, this.signer)
this.filesystemChanges = new FilesystemChanges(networkConfig, this.account.rpcHelper, this.signer)
this.verification = new Verification(networkConfig, this.account.rpcHelper, this.signer)
this.farcasterClient = new FarcasterClient(networkConfig, new HttpClient(networkConfig.farcasterAuthApiUrl))
}
}

Expand All @@ -49,6 +73,7 @@ declare global {
Wallet: typeof Wallet
HDNodeWallet: typeof HDNodeWallet
Verification: typeof import('./verification').Verification
FarcasterClient: typeof import('./farcaster-client').FarcasterClient
}
}
}
8 changes: 8 additions & 0 deletions src/network-config/INetworkConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,12 @@ export interface INetworkConfig {
* DappyKit verification RPC URL
*/
verificationRpcUrl: string
/**
* Farcaster Auth Frame URL
*/
farcasterAuthFrameUrl: string
/**
* Farcaster Auth API URL
*/
farcasterAuthApiUrl: string
}
2 changes: 2 additions & 0 deletions src/network-config/optimism-mainnet-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ export const optimismMainnetConfig: INetworkConfig = {
userOperationsExplorerUrl: '',
simpleExplorerUrl: 'https://explorer.optimism.io',
verificationRpcUrl: 'https://verify.dappykit.org',
farcasterAuthFrameUrl: 'https://farcaster-auth-frame.dappykit.org',
farcasterAuthApiUrl: 'https://farcaster-auth-api.dappykit.org',
}
2 changes: 2 additions & 0 deletions src/network-config/optimism-sepolia-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ export const optimismSepoliaConfig: INetworkConfig = {
userOperationsExplorerUrl: '',
simpleExplorerUrl: 'https://sepolia-optimistic.etherscan.io',
verificationRpcUrl: 'https://verify-api-test.dappykit.org',
farcasterAuthApiUrl: '',
farcasterAuthFrameUrl: '',
}
Loading

0 comments on commit 7464cf6

Please sign in to comment.