Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/adding support for recurring token allowance permissions #750

Merged
merged 8 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion advanced/wallets/react-wallet-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"@nextui-org/react": "1.0.8-beta.5",
"@polkadot/keyring": "^10.1.2",
"@polkadot/types": "^9.3.3",
"@rhinestone/module-sdk": "0.1.18",
"@reown/appkit-experimental": "1.1.5",
"@rhinestone/module-sdk": "0.1.25",
"@solana/web3.js": "1.89.2",
"@taquito/signer": "^15.1.0",
"@taquito/taquito": "^15.1.0",
Expand Down
6 changes: 5 additions & 1 deletion advanced/wallets/react-wallet-v2/src/data/EIP5792Data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ export const supportedEIP5792CapabilitiesForSCA: GetCapabilitiesResult = {
permissions: {
supported: true,
signerTypes: ['keys'],
permissionTypes: ['contract-call'],
permissionTypes: [
'contract-call',
'native-token-recurring-allowance',
'erc20-recurring-allowance'
],
policyTypes: []
},
atomicBatch: {
Expand Down
85 changes: 0 additions & 85 deletions advanced/wallets/react-wallet-v2/src/data/EIP7715Data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,93 +4,8 @@
export const EIP7715_METHOD = {
WALLET_GRANT_PERMISSIONS: 'wallet_grantPermissions'
}

export type Signer = MultiKeySigner
export type KeyType = 'secp256k1' | 'secp256r1'
// The types of keys that are supported for the following `key` and `keys` signer types.
export enum SignerKeyType {
SECP256K1 = 0, // EOA - k1
SECP256R1 = 1 // Passkey - r1
}
/*
* A signer representing a multisig signer.
* Each element of `publicKeys` are all explicitly the same `KeyType`, and the public keys are hex-encoded.
*/
export type MultiKeySigner = {
type: 'keys'
data: {
keys: {
type: KeyType
publicKey: `0x${string}`
}[]
}
}

export type Policy = {
type: string
data: Record<string, unknown>
}
// Enum for parameter operators
enum ParamOperator {
EQUAL = 'EQUAL',
GREATER_THAN = 'GREATER_THAN',
LESS_THAN = 'LESS_THAN'
// Add other operators as needed
}

// Enum for operation types
enum Operation {
Call = 'Call',
DelegateCall = 'DelegateCall'
}

// Type for a single argument condition
type ArgumentCondition = {
operator: ParamOperator
value: any // You might want to be more specific based on your use case
}

// Type for a single function permission
type FunctionPermission = {
functionName: string // Function name
args: ArgumentCondition[] // An array of conditions, each corresponding to an argument for the function
valueLimit: bigint // Maximum value that can be transferred for this specific function call
operation?: Operation // (optional) whether this is a call or a delegatecall. Defaults to call
}
export type ContractCallPermission = {
type: 'contract-call'
data: {
address: `0x${string}`
abi: Record<string, unknown>[]
functions: FunctionPermission[]
}
}

// Union type for all possible permissions
export type Permission = ContractCallPermission

export type WalletGrantPermissionsRequest = {
chainId: `0x${string}`
address?: `0x${string}`
expiry: number
signer: Signer
permissions: Permission[]
policies: {
type: string
data: Record<string, unknown>
}[]
}

export type WalletGrantPermissionsResponse = WalletGrantPermissionsRequest & {
context: `0x${string}`
accountMeta?: {
factory: `0x${string}`
factoryData: `0x${string}`
}
signerMeta?: {
// 7679 userOp building
userOpBuilder?: `0x${string}`
// 7710 delegation
delegationManager?: `0x${string}`
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import { SmartAccount, signerToSafeSmartAccount } from 'permissionless/accounts'
import { EntryPoint } from 'permissionless/types/entrypoint'
import { Address, Hex, createWalletClient, http, toHex } from 'viem'
import { TRUSTED_SMART_SESSIONS_ATTERSTER_ADDRESS } from './builders/SmartSessionUtil'
import { WalletGrantPermissionsRequest, WalletGrantPermissionsResponse } from '@/data/EIP7715Data'
import {
SmartSessionGrantPermissionsRequest,
WalletGrantPermissionsResponse
} from '@reown/appkit-experimental/smart-session'
import { getContext } from './builders/ContextBuilderUtil'
import { Execution, Module } from '@rhinestone/module-sdk'

Expand Down Expand Up @@ -56,7 +59,7 @@ export class SafeSmartAccountLib extends SmartAccountLib {

/* 7715 method */
async grantPermissions(
grantPermissionsRequestParameters: WalletGrantPermissionsRequest
grantPermissionsRequestParameters: SmartSessionGrantPermissionsRequest
): Promise<WalletGrantPermissionsResponse> {
if (!this.client?.account) {
throw new Error('Client not initialized')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { MULTIKEY_SIGNER_ADDRESSES, TIME_FRAME_POLICY_ADDRESSES } from './SmartSessionUtil'
import type { Session, ChainSession, Account, ActionData } from '@rhinestone/module-sdk'
import {
MOCK_POLICY,
MULTIKEY_SIGNER_ADDRESSES,
TIME_FRAME_POLICY_ADDRESSES
} from './SmartSessionUtil'
import type { Session, ChainSession, Account, ActionData, PolicyData } from '@rhinestone/module-sdk'
const {
SMART_SESSIONS_ADDRESS,
SmartSessionMode,
Expand Down Expand Up @@ -28,10 +32,12 @@ import {
MultiKeySigner,
Permission,
Signer,
WalletGrantPermissionsRequest,
SmartSessionGrantPermissionsRequest,
ContractCallPermission,
SignerKeyType
} from '@/data/EIP7715Data'
NativeTokenRecurringAllowancePermission,
ERC20RecurringAllowancePermission
} from '@reown/appkit-experimental/smart-session'
import { SignerKeyType } from '@/data/EIP7715Data'

// Constants for error messages
const ERROR_MESSAGES = {
Expand Down Expand Up @@ -76,7 +82,7 @@ async function fetchSessionData(
account: Account,
session: Session
): Promise<{ sessionNonce: bigint; sessionDigest: Hex; permissionId: Hex }> {
const permissionId = (await getPermissionId({ client: publicClient, session })) as Hex
const permissionId = (await getPermissionId({ session })) as Hex
const sessionNonce = await getSessionNonce({ client: publicClient, account, permissionId })
const sessionDigest = await getSessionDigest({
client: publicClient,
Expand Down Expand Up @@ -104,14 +110,11 @@ export async function getContext(
{
account,
grantPermissionsRequest
}: { account: Account; grantPermissionsRequest: WalletGrantPermissionsRequest }
}: { account: Account; grantPermissionsRequest: SmartSessionGrantPermissionsRequest }
): Promise<Hex> {
if (!walletClient.account) throw new Error(ERROR_MESSAGES.ACCOUNT_UNDEFINED)

const { chainId: hexChainId } = grantPermissionsRequest
console.log('walletClient.chain:', walletClient.chain)
console.log('publicClient.chain:', publicClient.chain)
console.log('hexChainId:', hexChainId)
if (!walletClient.chain || !publicClient.chain || !hexChainId)
throw new Error(ERROR_MESSAGES.CHAIN_UNDEFINED)
if (toHex(walletClient.chain.id) !== hexChainId || toHex(publicClient.chain.id) !== hexChainId)
Expand Down Expand Up @@ -204,25 +207,25 @@ const adjustVInSignature = (
}

/**
* This method transforms the WalletGrantPermissionsRequest into a Session object
* This method transforms the SmartSessionGrantPermissionsRequest into a Session object
* The Session object includes permittied actions and policies.
* It also includes the Session Validator Address(MultiKeySigner module) and Init Data needed for setting up the module.
* @param WalletGrantPermissionsRequest
* @param SmartSessionGrantPermissionsRequest
* @returns
*/
function getSmartSession({
chainId,
expiry,
permissions,
signer
}: WalletGrantPermissionsRequest): Session {
}: SmartSessionGrantPermissionsRequest): Session {
const chainIdNumber = parseInt(chainId, 16)
const actions = getActionsFromPermissions(permissions, chainIdNumber, expiry)

const sessionValidator = getSessionValidatorAddress(signer, chainIdNumber)
const sessionValidatorInitData = getSessionValidatorInitData(signer)

return {
chainId: BigInt(chainId),
sessionValidator,
sessionValidatorInitData,
salt: SESSION_SALT,
Expand Down Expand Up @@ -309,13 +312,23 @@ function isContractCallPermission(permission: Permission): permission is Contrac
return permission.type === 'contract-call'
}

function isNativeTokenRecurringAllowancePermission(
permission: Permission
): permission is NativeTokenRecurringAllowancePermission {
return permission.type === 'native-token-recurring-allowance'
}

function isERC20RecurringAllowancePermission(
permission: Permission
): permission is ERC20RecurringAllowancePermission {
return permission.type === 'erc20-recurring-allowance'
}

/**
* This method processes the permissions array from the permissions request and returns the actions array
* Note - Currently only 'contract-call' permission type is supported
* - For each contract-call permission, it creates an action for each function in the permission
* Note - For each permission, it creates an action data
* - It also adds the TIME_FRAME_POLICY for each action as the actionPolicy
* - The expiry time indicated in the permissions request is used as the expiry time for the actions
* - Function Arguments are not supported in this version
* @param permissions - Permissions array from the permissions request
* @param chainId - Chain ID on which the actions are to be performed
* @param expiry - Expiry time for the actions
Expand All @@ -327,12 +340,21 @@ function getActionsFromPermissions(
expiry: number
): ActionData[] {
return permissions.reduce((actions: ActionData[], permission) => {
if (!isContractCallPermission(permission)) {
throw new Error(ERROR_MESSAGES.UNSUPPORTED_PERMISSION_TYPE(JSON.stringify(permission)))
}
switch (true) {
case isContractCallPermission(permission):
actions.push(
...createActionForContractCall(permission as ContractCallPermission, chainId, expiry)
)
break

case isNativeTokenRecurringAllowancePermission(permission):
case isERC20RecurringAllowancePermission(permission):
actions.push(...createFallbackActionData(permission, chainId, expiry))
break

const contractCallActions = createActionForContractCall(permission, chainId, expiry)
actions.push(...contractCallActions)
default:
throw new Error(ERROR_MESSAGES.UNSUPPORTED_PERMISSION_TYPE(JSON.stringify(permission)))
}

return actions
}, [])
Expand Down Expand Up @@ -372,3 +394,29 @@ function createActionForContractCall(
}
})
}

/**
* This is a fallback action data which will be used to skip the functionSelector and address check.
* */
function createFallbackActionData(
permission: Permission,
chainId: number,
expiry: number
): ActionData[] {
const fallbackActionSelector = '0x00000001'
const fallbackActionAddress = '0x0000000000000000000000000000000000000001'

return [
{
actionTarget: fallbackActionAddress,
actionTargetSelector: fallbackActionSelector,
// Need atleast 1 actionPolicy, so hardcoding the TIME_FRAME_POLICY for now
actionPolicies: [
{
policy: TIME_FRAME_POLICY_ADDRESSES[chainId],
initData: encodePacked(['uint128', 'uint128'], [BigInt(expiry), BigInt(0)])
}
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import axios, { Method, AxiosError } from 'axios'
import { UserOperationWithBigIntAsHex } from './UserOpBuilder'
import { bigIntReplacer } from '@/utils/HelperUtil'
import { COSIGNER_BASE_URL } from '@/utils/ConstantsUtil'
import { WalletGrantPermissionsRequest } from '@/data/EIP7715Data'
import { SmartSessionGrantPermissionsRequest } from '@reown/appkit-experimental/smart-session'

//--Cosigner Types----------------------------------------------------------------------- //
export type AddPermissionRequest = WalletGrantPermissionsRequest
export type AddPermissionRequest = SmartSessionGrantPermissionsRequest

export type AddPermissionResponse = {
pci: string
Expand Down Expand Up @@ -164,7 +164,7 @@ export class CosignerService {
getPermissionsContextRequest: GetPermissionsContextRequest
): Promise<GetPermissionsContextResponse> {
// need to change the method to use POST method and pass pci in the body with url as /{address}/getContext
const url = `${this.baseUrl}/${encodeURIComponent(address)}/getContext`
const url = `${this.baseUrl}/${encodeURIComponent(address)}/getcontext`
return await sendCoSignerRequest<
never,
GetPermissionsContextResponse,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import { Address } from 'viem'
import { baseSepolia, sepolia } from 'viem/chains'

export const TIME_FRAME_POLICY_ADDRESSES: Record<number, Address> = {
84532: '0x9A6c4974dcE237E01Ff35c602CA9555a3c0Fa5EF',
11155111: '0x6E1FCe0ec6feaD8dBD2D36a5b9eCf8e33A538479'
[baseSepolia.id]: '0x9A6c4974dcE237E01Ff35c602CA9555a3c0Fa5EF',
[sepolia.id]: '0x6E1FCe0ec6feaD8dBD2D36a5b9eCf8e33A538479'
}

export const MULTIKEY_SIGNER_ADDRESSES: Record<number, Address> = {
84532: '0x207b90941d9cff79A750C1E5c05dDaA17eA01B9F',
11155111: '0x3cA2D7D588FA66248a49c1C885997e5017aF9Dc7'
[baseSepolia.id]: '0xcaF0461410340F8F366f1F7F7716cF1D90b6bdA4',
[sepolia.id]: '0x3cA2D7D588FA66248a49c1C885997e5017aF9Dc7'
}

export const MOCK_VALIDATOR_ADDRESSES: Record<number, Address> = {
84532: '0x8F8842B9b7346529484F282902Af173217411076',
11155111: '0xaE15a31afb2770cE4c5C6131925564B03b597Fe3'
[baseSepolia.id]: '0x8F8842B9b7346529484F282902Af173217411076',
[sepolia.id]: '0xaE15a31afb2770cE4c5C6131925564B03b597Fe3'
}

// All on sepolia
export const SIMPLE_SIGNER = '0x6ff7E9992160bB25f5c67b0Ce389c28d8faD3Bfb' as Address
export const MOCK_POLICY = '0xCBdFFA1e3b0bebAD9ea917910322332B2cfaeC26' as Address
export const UNI_ACTION_POLICY = '0x237C7567Ac09D4DB7Dd48852a438F77a6bd65fc4' as Address
export const USAGE_LIMIT_POLICY = '0x1f265E3beDc6ce93e1A36Dc80E1B1c65844F9861' as Address
export const VALUE_LIMIT_POLICY = '0x6F0eC0c77cCAF4c25ff8FF7113D329caAA769688' as Address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'
import { SessionTypes, SignClientTypes } from '@walletconnect/types'
import { getSdkError } from '@walletconnect/utils'
import SettingsStore from '@/store/SettingsStore'
import { EIP7715_METHOD } from '@/data/EIP7715Data'
import {
EIP7715_METHOD,
WalletGrantPermissionsRequest,
SmartSessionGrantPermissionsRequest,
WalletGrantPermissionsResponse
} from '@/data/EIP7715Data'
} from '@reown/appkit-experimental/smart-session'
import { SafeSmartAccountLib } from '@/lib/smart-accounts/SafeSmartAccountLib'
import { walletkit } from './WalletConnectUtil'
import { smartAccountWallets } from './SmartAccountUtil'
Expand Down Expand Up @@ -49,7 +49,7 @@ export async function approveEIP7715Request(requestEvent: RequestEventArgs) {
switch (request.method) {
case EIP7715_METHOD.WALLET_GRANT_PERMISSIONS: {
const wallet = getSmartAccountLibFromSession(requestSession, chainId)
let grantPermissionsRequestParams: WalletGrantPermissionsRequest = request.params[0]
let grantPermissionsRequestParams: SmartSessionGrantPermissionsRequest = request.params[0]
if (
wallet instanceof SafeSmartAccountLib
//TODO:fix kernel grantPermissions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,11 @@ export async function isERC7579ModuleInstalled(
})
const erc7579Module: Module = {
module: moduleAddress,
type: moduleType
type: moduleType,
initData: '0x',
deInitData: '0x',
additionalContext: '0x',
address: moduleAddress
}
return await isModuleInstalled({
client: publicClient, // The client object of type PublicClient from viem
Expand Down
Loading