Skip to content

Commit

Permalink
chores:refactor code
Browse files Browse the repository at this point in the history
  • Loading branch information
KannuSingh committed Sep 20, 2024
1 parent cc32065 commit 4ce10d3
Show file tree
Hide file tree
Showing 9 changed files with 956 additions and 362 deletions.
55 changes: 43 additions & 12 deletions packages/experimental/smart-session/exports/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,56 @@
import { AppKitSmartSessionControllerClient } from '../src/client.js'
import { useState, useEffect, useCallback } from 'react'
import { SmartSessionController } from '../src/core/controller/SmartSessionController.js'
import type {
SmartSessionGrantPermissionsRequest,
SmartSessionGrantPermissionsResponse
} from '../src/core/utils/TypeUtils.js'
export {
SmartSessionController,
type SmartSessionControllerClient
} from '../src/core/controller/SmartSessionController.js'
export { SmartSessionController } from '../src/core/controller/SmartSessionController.js'
export * from '../src/core/utils/TypeUtils.js'
import { AppKitSmartSessionControllerClient } from '../src/client.js'

export type { AppKitSmartSessionControllerClient }

// -- Hooks -------------------------------------------------------------------
export function useSmartSession() {
async function grantPermissions(
request: SmartSessionGrantPermissionsRequest
): Promise<SmartSessionGrantPermissionsResponse> {
const appkitSmartSessionControllerClient = new AppKitSmartSessionControllerClient()
return await appkitSmartSessionControllerClient.grantPermissions(request)
}
export const useSmartSession = () => {
// Local state to store the latest smart session state
const [permissions, setPermissions] = useState(SmartSessionController.state.permissions)
const [permissionsContext, setPermissionsContext] = useState(
SmartSessionController.state.permissionsContext
)

// Grant permissions method
const grantPermissions = useCallback(
async (
smartSessionGrantPermissionsRequest: SmartSessionGrantPermissionsRequest
): Promise<SmartSessionGrantPermissionsResponse> => {
try {
const response = await SmartSessionController.grantPermissions(
smartSessionGrantPermissionsRequest
)
return response
} catch (error) {
console.error('Error granting permissions:', error)
throw error
}
},
[]
)

// Subscribe to the SmartSessionController state and update local state when it changes
useEffect(() => {
const unsubscribe = SmartSessionController.subscribe(newState => {
setPermissions(newState.permissions)
setPermissionsContext(newState.permissionsContext)
})

// Cleanup subscription on unmount
return () => unsubscribe()
}, [])

// Return values and methods to use in components
return {
permissions,
permissionsContext,
grantPermissions
}
}
8 changes: 6 additions & 2 deletions packages/experimental/smart-session/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@
"react-dom": ">=17"
},
"devDependencies": {
"@types/axios-mock-adapter": "^1.10.0",
"@types/react": "^18.2.0",
"axios-mock-adapter": "^2.0.0",
"react": "18.2.0",
"react-dom": "18.2.0"
"react-dom": "18.2.0",
"vitest": "2.0.5"
},
"peerDependenciesMeta": {
"react": {
Expand Down Expand Up @@ -57,4 +61,4 @@
"bugs": {
"url": "https://github.com/WalletConnect/web3modal/issues"
}
}
}
144 changes: 78 additions & 66 deletions packages/experimental/smart-session/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ChainController, ConnectionController, CoreHelperUtil } from '@reown/appkit-core'
import {
ChainController,
ConnectionController,
CoreHelperUtil,
OptionsController
} from '@reown/appkit-core'
import {
decodeDIDToPublicKey,
decodeUncompressedPublicKey,
Expand All @@ -7,91 +12,98 @@ import {
KeyTypes
} from './core/helper/index.js'
import type {
// SmartSessionClientMethods,
SmartSessionGrantPermissionsRequest,
SmartSessionGrantPermissionsResponse,
WalletGrantPermissionsResponse
} from './core/utils/TypeUtils.js'
import { WalletConnectCosigner } from './core/utils/WalletConnectCosignerUtils.js'
import { WalletConnectCosigner } from './core/utils/WalletConnectCosigner'

// -- Client -------------------------------------------------------------------- //
export class AppKitSmartSessionControllerClient {
async grantPermissions(
smartSessionGrantPermissionsRequest: SmartSessionGrantPermissionsRequest
): Promise<SmartSessionGrantPermissionsResponse> {
const { signer } = smartSessionGrantPermissionsRequest
const caipAddress = ChainController.state.activeCaipAddress
const address = caipAddress ? CoreHelperUtil.getPlainAddress(caipAddress) : ''
if (!address) {
throw new Error('An address is required create Smart Session.')
}
const caipNetwork = ChainController.state.activeCaipNetwork
try {
const { signer } = smartSessionGrantPermissionsRequest
const { activeCaipAddress, activeCaipNetwork } = ChainController.state

if (!caipNetwork?.id) {
throw new Error('A chainId is required to create Smart Session.')
}
// Validate address and network
const address = activeCaipAddress ? CoreHelperUtil.getPlainAddress(activeCaipAddress) : ''
if (!address) throw new Error('An address is required to create a Smart Session.')

if (signer.type !== 'key' && signer.type !== 'keys' && signer.data['id']) {
throw new Error('Invalid signer type')
}
if (!activeCaipNetwork?.id)
throw new Error('A chainId is required to create a Smart Session.')

const dAppKey = decodeDIDToPublicKey(signer.data['id'])
if (!dAppKey) {
throw new Error('Invalid dAppKey signer data')
}
const dAppKeyDID = encodePublicKeyToDID(dAppKey.key, dAppKey.keyType)
const caip10Address = `eip155:${caipNetwork.id}:${address}`
const walletConnectCosigner = new WalletConnectCosigner()
const addPermissionResponse = await walletConnectCosigner.addPermission(caip10Address, {
permissionType: 'donut-purchase',
data: '',
onChainValidated: false,
required: true
})
const cosignerPublicKey = decodeUncompressedPublicKey(addPermissionResponse.key)
const cosignerKeyDID = encodePublicKeyToDID(cosignerPublicKey, KeyTypes.secp256k1)
smartSessionGrantPermissionsRequest.signer = {
type: 'keys',
data: {
ids: [cosignerKeyDID, dAppKeyDID]
// Validate signer type
if (signer.type !== 'key' && signer.type !== 'keys' && signer.data['id']) {
throw new Error('Invalid signer type.')
}
}

const connectionControllerClient = ConnectionController._getClient('eip155')
// Decode dAppKey and generate DID
const dAppKey = decodeDIDToPublicKey(signer.data['id'])
if (!dAppKey) throw new Error('Invalid dAppKey signer data.')

const smartSessionGrantPermissionsResponse = (await connectionControllerClient.grantPermissions(
smartSessionGrantPermissionsRequest
)) as WalletGrantPermissionsResponse
const dAppKeyDID = encodePublicKeyToDID(dAppKey.key, dAppKey.keyType)
const caip10Address = `${activeCaipNetwork.id}:${address}`
const projectId = OptionsController.state.projectId
// Add permission using WalletConnect cosigner
const walletConnectCosigner = new WalletConnectCosigner(projectId)
const addPermissionResponse = await walletConnectCosigner.addPermission(caip10Address, {
permissionType: 'donut-purchase',
data: '',
onChainValidated: false,
required: true
})

if (!smartSessionGrantPermissionsResponse) {
throw new Error(
'AppKitSmartSessionControllerClient:grantPermissions - smartSessionGrantPermissionsResponse is undefined'
)
}
// Decode cosigner key and generate DID
const cosignerPublicKey = decodeUncompressedPublicKey(addPermissionResponse.key)
const cosignerKeyDID = encodePublicKeyToDID(cosignerPublicKey, KeyTypes.secp256k1)

//TODO: Cosigner activate Permissions call
await walletConnectCosigner.updatePermissionsContext(caip10Address, {
pci: addPermissionResponse.pci,
context: {
expiry: smartSessionGrantPermissionsResponse.expiry,
signer: {
type: 'donut-purchase',
data: {
ids: [addPermissionResponse.key, hexStringToBase64(dAppKey.key)]
}
},
signerData: {
userOpBuilder: smartSessionGrantPermissionsResponse.signerMeta?.userOpBuilder || ''
},
permissionsContext: smartSessionGrantPermissionsResponse.context,
factory: smartSessionGrantPermissionsResponse.accountMeta?.factory || '',
factoryData: smartSessionGrantPermissionsResponse.accountMeta?.factoryData || ''
// Update request with cosigner info
smartSessionGrantPermissionsRequest.signer = {
type: 'keys',
data: {
ids: [cosignerKeyDID, dAppKeyDID]
}
}
})

return {
permissions: smartSessionGrantPermissionsResponse.permissions,
context: smartSessionGrantPermissionsResponse.context
// Call the connection controller to process the grant permission
const connectionControllerClient = ConnectionController._getClient('eip155')
const smartSessionGrantPermissionsResponse =
(await connectionControllerClient.grantPermissions(
smartSessionGrantPermissionsRequest
)) as WalletGrantPermissionsResponse

if (!smartSessionGrantPermissionsResponse) {
throw new Error(
'AppKitSmartSessionControllerClient:grantPermissions - No response received from grantPermissions'
)
}

// Update the cosigner permissions context
await walletConnectCosigner.updatePermissionsContext(caip10Address, {
pci: addPermissionResponse.pci,
context: {
expiry: smartSessionGrantPermissionsResponse.expiry,
signer: {
type: 'donut-purchase',
data: {
ids: [addPermissionResponse.key, hexStringToBase64(dAppKey.key)]
}
},
signerData: {
userOpBuilder: smartSessionGrantPermissionsResponse.signerMeta?.userOpBuilder || ''
},
permissionsContext: smartSessionGrantPermissionsResponse.context,
factory: smartSessionGrantPermissionsResponse.accountMeta?.factory || '',
factoryData: smartSessionGrantPermissionsResponse.accountMeta?.factoryData || ''
}
})

return smartSessionGrantPermissionsResponse
} catch (error) {
console.error('Error during grantPermissions process:', error)
throw error
}
}
}
Original file line number Diff line number Diff line change
@@ -1,58 +1,49 @@
import type { ChainAdapter } from '@reown/appkit-core'
import { subscribeKey as subKey } from 'valtio/vanilla/utils'
import { proxy, ref, subscribe as sub } from 'valtio/vanilla'
import { proxy, subscribe as sub } from 'valtio/vanilla'
import type {
SmartSessionClientMethods,
SmartSessionGrantPermissionsRequest,
SmartSessionGrantPermissionsResponse
} from '../utils/TypeUtils'
import { AppKitSmartSessionControllerClient } from '../../client'

// -- Types --------------------------------------------- //
export interface SmartSessionControllerClient extends SmartSessionClientMethods {
chainAdapter: ChainAdapter
export interface SmartSessionControllerState {
permissionsContext?: SmartSessionGrantPermissionsResponse['context']
permissions?: SmartSessionGrantPermissionsResponse['permissions']
}

export interface SmartSessionControllerClientState {
_client?: SmartSessionControllerClient
}

type StateKey = keyof SmartSessionControllerClientState
export type StateKey = keyof SmartSessionControllerState

// -- State --------------------------------------------- //
const state = proxy<SmartSessionControllerClientState>({
// status: 'uninitialized'
// -- State --------------------------------------------------------- //
const state = proxy<SmartSessionControllerState>({
permissions: undefined,
permissionsContext: undefined
})

// -- Controller ---------------------------------------- //
export const SmartSessionController = {
state,

subscribeKey<K extends StateKey>(
key: K,
callback: (value: SmartSessionControllerClientState[K]) => void
) {
return subKey(state, key, callback)
},
async grantPermissions(
smartSessionGrantPermissionsRequest: SmartSessionGrantPermissionsRequest
): Promise<SmartSessionGrantPermissionsResponse> {
const service = new AppKitSmartSessionControllerClient()
const response = await service.grantPermissions(smartSessionGrantPermissionsRequest)

subscribe(callback: (newState: SmartSessionControllerClientState) => void) {
return sub(state, () => callback(state))
},
this.setPermissions(response.permissions)
this.setPermissionsContext(response.context)

_getClient() {
if (!state._client) {
throw new Error('SmartSessionControllerClientState client not set')
}
return response
},

return state._client
subscribe(callback: (newState: SmartSessionControllerState) => void) {
return sub(state, () => callback(state))
},

setSmartSessionControllerClient(client: SmartSessionControllerClient) {
state._client = ref(client)
setPermissions(permissions: SmartSessionGrantPermissionsResponse['permissions']) {
state.permissions = permissions
},

grantPermissions(
request: SmartSessionGrantPermissionsRequest
): Promise<SmartSessionGrantPermissionsResponse> {
return this._getClient().grantPermissions(request)
setPermissionsContext(context: SmartSessionGrantPermissionsResponse['context']) {
state.permissionsContext = context
}
}
Loading

0 comments on commit 4ce10d3

Please sign in to comment.