From 02a8bf010f4ea90cf8783e65006f0ae2e40f3090 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Wed, 5 Feb 2025 17:54:21 -0500 Subject: [PATCH 01/23] WIP --- src/client/api.ts | 770 +++++++++++++++++++++++++++++++++++ src/client/configuration.ts | 8 +- src/coinbase/coinbase.ts | 2 + src/coinbase/signer.ts | 23 ++ src/coinbase/smart_wallet.ts | 141 +++++++ src/coinbase/types.ts | 72 ++++ 6 files changed, 1015 insertions(+), 1 deletion(-) create mode 100644 src/coinbase/signer.ts create mode 100644 src/coinbase/smart_wallet.ts diff --git a/src/client/api.ts b/src/client/api.ts index da0e706f..ede1d1c7 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -417,6 +417,19 @@ export interface BroadcastTransferRequest { */ 'signed_payload': string; } +/** + * + * @export + * @interface BroadcastUserOperationRequest + */ +export interface BroadcastUserOperationRequest { + /** + * The hex-encoded signature of the user operation. + * @type {string} + * @memberof BroadcastUserOperationRequest + */ + 'signature': string; +} /** * * @export @@ -454,6 +467,31 @@ export interface BuildStakingOperationRequest { */ 'options': { [key: string]: string; }; } +/** + * An action that will be bundled into a user operation. + * @export + * @interface Call + */ +export interface Call { + /** + * The optional address of the contract to call. + * @type {string} + * @memberof Call + */ + 'to'?: string; + /** + * The hex-encoded data to send with the call. + * @type {string} + * @memberof Call + */ + 'data': string; + /** + * The optional hex-encoded value to send with the call. + * @type {string} + * @memberof Call + */ + 'value'?: string; +} /** * * @export @@ -938,6 +976,19 @@ export interface CreateSmartContractRequest { } +/** + * + * @export + * @interface CreateSmartWalletRequest + */ +export interface CreateSmartWalletRequest { + /** + * The address of the owner of the smart wallet. + * @type {string} + * @memberof CreateSmartWalletRequest + */ + 'owner': string; +} /** * * @export @@ -1037,6 +1088,25 @@ export interface CreateTransferRequest { */ 'skip_batching'?: boolean; } +/** + * + * @export + * @interface CreateUserOperationRequest + */ +export interface CreateUserOperationRequest { + /** + * The network to create the user operation on. + * @type {string} + * @memberof CreateUserOperationRequest + */ + 'network': string; + /** + * The list of calls to make from the smart wallet. + * @type {Array} + * @memberof CreateUserOperationRequest + */ + 'calls': Array; +} /** * * @export @@ -3043,6 +3113,56 @@ export const SmartContractType = { export type SmartContractType = typeof SmartContractType[keyof typeof SmartContractType]; +/** + * + * @export + * @interface SmartWallet + */ +export interface SmartWallet { + /** + * The onchain address of the smart wallet. + * @type {string} + * @memberof SmartWallet + */ + 'address': string; + /** + * The list of owner addresses for the smart wallet. + * @type {Array} + * @memberof SmartWallet + */ + 'owners': Array; +} +/** + * Paginated list of smart wallets + * @export + * @interface SmartWalletList + */ +export interface SmartWalletList { + /** + * + * @type {Array} + * @memberof SmartWalletList + */ + 'data': Array; + /** + * True if this list has another page of items after this one that can be fetched. + * @type {boolean} + * @memberof SmartWalletList + */ + 'has_more': boolean; + /** + * The page token to be used to fetch the next page. + * @type {string} + * @memberof SmartWalletList + */ + 'next_page': string; + /** + * The total number of wallets + * @type {number} + * @memberof SmartWalletList + */ + 'total_count': number; +} /** * * @export @@ -3261,6 +3381,12 @@ export interface StakingContextContext { * @memberof StakingContextContext */ 'unstakeable_balance': Balance; + /** + * + * @type {Balance} + * @memberof StakingContextContext + */ + 'pending_claimable_balance': Balance; /** * * @type {Balance} @@ -3870,6 +3996,60 @@ export interface User { */ 'display_name'?: string; } +/** + * + * @export + * @interface UserOperation + */ +export interface UserOperation { + /** + * The ID of the user operation. + * @type {string} + * @memberof UserOperation + */ + 'id'?: string; + /** + * The network the user operation is being created on. + * @type {string} + * @memberof UserOperation + */ + 'network': string; + /** + * The list of calls to make from the smart wallet. + * @type {Array} + * @memberof UserOperation + */ + 'calls': Array; + /** + * The hex-encoded hash that must be signed by the user. + * @type {string} + * @memberof UserOperation + */ + 'unsigned_payload': string; + /** + * The hex-encoded signature of the user operation. + * @type {string} + * @memberof UserOperation + */ + 'signature'?: string; + /** + * The status of the user operation. + * @type {string} + * @memberof UserOperation + */ + 'status'?: UserOperationStatusEnum; +} + +export const UserOperationStatusEnum = { + Pending: 'pending', + Signed: 'signed', + Broadcast: 'broadcast', + Complete: 'complete', + Failed: 'failed' +} as const; + +export type UserOperationStatusEnum = typeof UserOperationStatusEnum[keyof typeof UserOperationStatusEnum]; + /** * A validator onchain. * @export @@ -9716,6 +9896,596 @@ export class SmartContractsApi extends BaseAPI implements SmartContractsApiInter +/** + * SmartWalletsApi - axios parameter creator + * @export + */ +export const SmartWalletsApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * Broadcast a user operation + * @summary Broadcast a user operation + * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} userOperationId The ID of the user operation to broadcast. + * @param {BroadcastUserOperationRequest} [broadcastUserOperationRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + broadcastUserOperation: async (contractAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'contractAddress' is not null or undefined + assertParamExists('broadcastUserOperation', 'contractAddress', contractAddress) + // verify required parameter 'userOperationId' is not null or undefined + assertParamExists('broadcastUserOperation', 'userOperationId', userOperationId) + const localVarPath = `/v1/smart_wallets/{contract_address}/user_operations/{user_operation_id}/broadcast` + .replace(`{${"contract_address"}}`, encodeURIComponent(String(contractAddress))) + .replace(`{${"user_operation_id"}}`, encodeURIComponent(String(userOperationId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication apiKey required + await setApiKeyToObject(localVarHeaderParameter, "Jwt", configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(broadcastUserOperationRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Create a new smart wallet, not scoped to a given network. + * @summary Create a new smart wallet + * @param {CreateSmartWalletRequest} [createSmartWalletRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createSmartWallet: async (createSmartWalletRequest?: CreateSmartWalletRequest, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/v1/smart_wallets`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication apiKey required + await setApiKeyToObject(localVarHeaderParameter, "Jwt", configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(createSmartWalletRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Create a new user operation on a smart wallet. + * @summary Create a new user operation + * @param {string} contractAddress The contract address of the smart wallet to create the user operation on. + * @param {CreateUserOperationRequest} [createUserOperationRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createUserOperation: async (contractAddress: string, createUserOperationRequest?: CreateUserOperationRequest, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'contractAddress' is not null or undefined + assertParamExists('createUserOperation', 'contractAddress', contractAddress) + const localVarPath = `/v1/smart_wallets/{contract_address}/user_operations` + .replace(`{${"contract_address"}}`, encodeURIComponent(String(contractAddress))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication apiKey required + await setApiKeyToObject(localVarHeaderParameter, "Jwt", configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(createUserOperationRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Get smart wallet + * @summary Get smart wallet by contract address + * @param {string} contractAddress The contract address of the smart wallet to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getSmartWallet: async (contractAddress: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'contractAddress' is not null or undefined + assertParamExists('getSmartWallet', 'contractAddress', contractAddress) + const localVarPath = `/v1/smart_wallets/{contract_address}` + .replace(`{${"contract_address"}}`, encodeURIComponent(String(contractAddress))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication apiKey required + await setApiKeyToObject(localVarHeaderParameter, "Jwt", configuration) + + // authentication session required + await setApiKeyToObject(localVarHeaderParameter, "Jwt", configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Get user operation + * @summary Get user operation + * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} userOperationId The ID of the user operation to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getUserOperation: async (contractAddress: string, userOperationId: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'contractAddress' is not null or undefined + assertParamExists('getUserOperation', 'contractAddress', contractAddress) + // verify required parameter 'userOperationId' is not null or undefined + assertParamExists('getUserOperation', 'userOperationId', userOperationId) + const localVarPath = `/v1/smart_wallets/{contract_address}/user_operations/{user_operation_id}` + .replace(`{${"contract_address"}}`, encodeURIComponent(String(contractAddress))) + .replace(`{${"user_operation_id"}}`, encodeURIComponent(String(userOperationId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication apiKey required + await setApiKeyToObject(localVarHeaderParameter, "Jwt", configuration) + + // authentication session required + await setApiKeyToObject(localVarHeaderParameter, "Jwt", configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * List smart wallets + * @summary List smart wallets + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listSmartWallets: async (limit?: number, page?: string, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/v1/smart_wallets`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication apiKey required + await setApiKeyToObject(localVarHeaderParameter, "Jwt", configuration) + + // authentication session required + await setApiKeyToObject(localVarHeaderParameter, "Jwt", configuration) + + if (limit !== undefined) { + localVarQueryParameter['limit'] = limit; + } + + if (page !== undefined) { + localVarQueryParameter['page'] = page; + } + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * SmartWalletsApi - functional programming interface + * @export + */ +export const SmartWalletsApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = SmartWalletsApiAxiosParamCreator(configuration) + return { + /** + * Broadcast a user operation + * @summary Broadcast a user operation + * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} userOperationId The ID of the user operation to broadcast. + * @param {BroadcastUserOperationRequest} [broadcastUserOperationRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async broadcastUserOperation(contractAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.broadcastUserOperation(contractAddress, userOperationId, broadcastUserOperationRequest, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['SmartWalletsApi.broadcastUserOperation']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Create a new smart wallet, not scoped to a given network. + * @summary Create a new smart wallet + * @param {CreateSmartWalletRequest} [createSmartWalletRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async createSmartWallet(createSmartWalletRequest?: CreateSmartWalletRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.createSmartWallet(createSmartWalletRequest, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['SmartWalletsApi.createSmartWallet']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Create a new user operation on a smart wallet. + * @summary Create a new user operation + * @param {string} contractAddress The contract address of the smart wallet to create the user operation on. + * @param {CreateUserOperationRequest} [createUserOperationRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async createUserOperation(contractAddress: string, createUserOperationRequest?: CreateUserOperationRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.createUserOperation(contractAddress, createUserOperationRequest, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['SmartWalletsApi.createUserOperation']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Get smart wallet + * @summary Get smart wallet by contract address + * @param {string} contractAddress The contract address of the smart wallet to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getSmartWallet(contractAddress: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getSmartWallet(contractAddress, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['SmartWalletsApi.getSmartWallet']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Get user operation + * @summary Get user operation + * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} userOperationId The ID of the user operation to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getUserOperation(contractAddress: string, userOperationId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getUserOperation(contractAddress, userOperationId, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['SmartWalletsApi.getUserOperation']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * List smart wallets + * @summary List smart wallets + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listSmartWallets(limit?: number, page?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listSmartWallets(limit, page, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['SmartWalletsApi.listSmartWallets']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + } +}; + +/** + * SmartWalletsApi - factory interface + * @export + */ +export const SmartWalletsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = SmartWalletsApiFp(configuration) + return { + /** + * Broadcast a user operation + * @summary Broadcast a user operation + * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} userOperationId The ID of the user operation to broadcast. + * @param {BroadcastUserOperationRequest} [broadcastUserOperationRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + broadcastUserOperation(contractAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.broadcastUserOperation(contractAddress, userOperationId, broadcastUserOperationRequest, options).then((request) => request(axios, basePath)); + }, + /** + * Create a new smart wallet, not scoped to a given network. + * @summary Create a new smart wallet + * @param {CreateSmartWalletRequest} [createSmartWalletRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createSmartWallet(createSmartWalletRequest?: CreateSmartWalletRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.createSmartWallet(createSmartWalletRequest, options).then((request) => request(axios, basePath)); + }, + /** + * Create a new user operation on a smart wallet. + * @summary Create a new user operation + * @param {string} contractAddress The contract address of the smart wallet to create the user operation on. + * @param {CreateUserOperationRequest} [createUserOperationRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createUserOperation(contractAddress: string, createUserOperationRequest?: CreateUserOperationRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.createUserOperation(contractAddress, createUserOperationRequest, options).then((request) => request(axios, basePath)); + }, + /** + * Get smart wallet + * @summary Get smart wallet by contract address + * @param {string} contractAddress The contract address of the smart wallet to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getSmartWallet(contractAddress: string, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.getSmartWallet(contractAddress, options).then((request) => request(axios, basePath)); + }, + /** + * Get user operation + * @summary Get user operation + * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} userOperationId The ID of the user operation to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getUserOperation(contractAddress: string, userOperationId: string, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.getUserOperation(contractAddress, userOperationId, options).then((request) => request(axios, basePath)); + }, + /** + * List smart wallets + * @summary List smart wallets + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listSmartWallets(limit?: number, page?: string, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.listSmartWallets(limit, page, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * SmartWalletsApi - interface + * @export + * @interface SmartWalletsApi + */ +export interface SmartWalletsApiInterface { + /** + * Broadcast a user operation + * @summary Broadcast a user operation + * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} userOperationId The ID of the user operation to broadcast. + * @param {BroadcastUserOperationRequest} [broadcastUserOperationRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SmartWalletsApiInterface + */ + broadcastUserOperation(contractAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options?: RawAxiosRequestConfig): AxiosPromise; + + /** + * Create a new smart wallet, not scoped to a given network. + * @summary Create a new smart wallet + * @param {CreateSmartWalletRequest} [createSmartWalletRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SmartWalletsApiInterface + */ + createSmartWallet(createSmartWalletRequest?: CreateSmartWalletRequest, options?: RawAxiosRequestConfig): AxiosPromise; + + /** + * Create a new user operation on a smart wallet. + * @summary Create a new user operation + * @param {string} contractAddress The contract address of the smart wallet to create the user operation on. + * @param {CreateUserOperationRequest} [createUserOperationRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SmartWalletsApiInterface + */ + createUserOperation(contractAddress: string, createUserOperationRequest?: CreateUserOperationRequest, options?: RawAxiosRequestConfig): AxiosPromise; + + /** + * Get smart wallet + * @summary Get smart wallet by contract address + * @param {string} contractAddress The contract address of the smart wallet to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SmartWalletsApiInterface + */ + getSmartWallet(contractAddress: string, options?: RawAxiosRequestConfig): AxiosPromise; + + /** + * Get user operation + * @summary Get user operation + * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} userOperationId The ID of the user operation to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SmartWalletsApiInterface + */ + getUserOperation(contractAddress: string, userOperationId: string, options?: RawAxiosRequestConfig): AxiosPromise; + + /** + * List smart wallets + * @summary List smart wallets + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SmartWalletsApiInterface + */ + listSmartWallets(limit?: number, page?: string, options?: RawAxiosRequestConfig): AxiosPromise; + +} + +/** + * SmartWalletsApi - object-oriented interface + * @export + * @class SmartWalletsApi + * @extends {BaseAPI} + */ +export class SmartWalletsApi extends BaseAPI implements SmartWalletsApiInterface { + /** + * Broadcast a user operation + * @summary Broadcast a user operation + * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} userOperationId The ID of the user operation to broadcast. + * @param {BroadcastUserOperationRequest} [broadcastUserOperationRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SmartWalletsApi + */ + public broadcastUserOperation(contractAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options?: RawAxiosRequestConfig) { + return SmartWalletsApiFp(this.configuration).broadcastUserOperation(contractAddress, userOperationId, broadcastUserOperationRequest, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Create a new smart wallet, not scoped to a given network. + * @summary Create a new smart wallet + * @param {CreateSmartWalletRequest} [createSmartWalletRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SmartWalletsApi + */ + public createSmartWallet(createSmartWalletRequest?: CreateSmartWalletRequest, options?: RawAxiosRequestConfig) { + return SmartWalletsApiFp(this.configuration).createSmartWallet(createSmartWalletRequest, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Create a new user operation on a smart wallet. + * @summary Create a new user operation + * @param {string} contractAddress The contract address of the smart wallet to create the user operation on. + * @param {CreateUserOperationRequest} [createUserOperationRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SmartWalletsApi + */ + public createUserOperation(contractAddress: string, createUserOperationRequest?: CreateUserOperationRequest, options?: RawAxiosRequestConfig) { + return SmartWalletsApiFp(this.configuration).createUserOperation(contractAddress, createUserOperationRequest, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Get smart wallet + * @summary Get smart wallet by contract address + * @param {string} contractAddress The contract address of the smart wallet to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SmartWalletsApi + */ + public getSmartWallet(contractAddress: string, options?: RawAxiosRequestConfig) { + return SmartWalletsApiFp(this.configuration).getSmartWallet(contractAddress, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Get user operation + * @summary Get user operation + * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} userOperationId The ID of the user operation to fetch. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SmartWalletsApi + */ + public getUserOperation(contractAddress: string, userOperationId: string, options?: RawAxiosRequestConfig) { + return SmartWalletsApiFp(this.configuration).getUserOperation(contractAddress, userOperationId, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * List smart wallets + * @summary List smart wallets + * @param {number} [limit] A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10. + * @param {string} [page] A cursor for pagination across multiple pages of results. Don\'t include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SmartWalletsApi + */ + public listSmartWallets(limit?: number, page?: string, options?: RawAxiosRequestConfig) { + return SmartWalletsApiFp(this.configuration).listSmartWallets(limit, page, options).then((request) => request(this.axios, this.basePath)); + } +} + + + /** * StakeApi - axios parameter creator * @export diff --git a/src/client/configuration.ts b/src/client/configuration.ts index 1fc8a134..a1a0c93e 100644 --- a/src/client/configuration.ts +++ b/src/client/configuration.ts @@ -89,7 +89,13 @@ export class Configuration { this.accessToken = param.accessToken; this.basePath = param.basePath; this.serverIndex = param.serverIndex; - this.baseOptions = param.baseOptions; + this.baseOptions = { + headers: { + ...param.baseOptions?.headers, + 'User-Agent': "OpenAPI-Generator/typescript-axios" + }, + ...param.baseOptions + }; this.formDataCtor = param.formDataCtor; } diff --git a/src/coinbase/coinbase.ts b/src/coinbase/coinbase.ts index 6b9fd818..03165cc2 100644 --- a/src/coinbase/coinbase.ts +++ b/src/coinbase/coinbase.ts @@ -20,6 +20,7 @@ import { MPCWalletStakeApiFactory, FundApiFactory, ReputationApiFactory, + SmartWalletsApiFactory, } from "../client"; import { BASE_PATH } from "./../client/base"; import { Configuration } from "./../client/configuration"; @@ -144,6 +145,7 @@ export class Coinbase { ); Coinbase.apiClients.wallet = WalletsApiFactory(config, basePath, axiosInstance); + Coinbase.apiClients.smartWallet = SmartWalletsApiFactory(config, basePath, axiosInstance); Coinbase.apiClients.address = AddressesApiFactory(config, basePath, axiosInstance); Coinbase.apiClients.transfer = TransfersApiFactory(config, basePath, axiosInstance); Coinbase.apiClients.trade = TradesApiFactory(config, basePath, axiosInstance); diff --git a/src/coinbase/signer.ts b/src/coinbase/signer.ts new file mode 100644 index 00000000..a6048748 --- /dev/null +++ b/src/coinbase/signer.ts @@ -0,0 +1,23 @@ +import type { + LocalAccount, + Address, + Hash, + Transaction, + TypedData + } from 'viem' + +interface WalletSigner { + // Core viem account properties + address: Address; + type: 'local'; + source: 'custom'; + + // Standard signing methods from viem + sign(hash: Hash): Promise; + signMessage(message: string | Bytes): Promise; + signTransaction(transaction: Transaction): Promise; + signTypedData(typedData: TypedData): Promise; + + // Optional: Add any smart wallet specific methods + signUserOperation?(userOp: UserOperation): Promise; +} \ No newline at end of file diff --git a/src/coinbase/smart_wallet.ts b/src/coinbase/smart_wallet.ts new file mode 100644 index 00000000..88e36861 --- /dev/null +++ b/src/coinbase/smart_wallet.ts @@ -0,0 +1,141 @@ +import { NetworkIdentifier, SmartWallet as SmartWalletModel } from "../client"; + +import { Coinbase } from "./coinbase"; + +import type { Address, Narrow } from 'abitype' +import type { Client, Hex } from 'viem' +import type { Chain } from 'viem/chains' +import type { + UserOperation, + UserOperationRequest, + PrepareUserOperationRequest, + PrepareUserOperationReturnType, + UserOperationCalls, + SendUserOperationParameters, + SendUserOperationReturnType +} from 'viem/account-abstraction' + +import { encodeAbiParameters, encodeFunctionData, encodePacked, LocalAccount, parseSignature, size } from "viem"; + +// TODO - figure out the network interface to connect to a specific network +export class SmartWallet { + private model: SmartWalletModel; + private account: LocalAccount; + + public constructor(model: SmartWalletModel, account: LocalAccount) { + this.model = model; + this.account = account; + } + + public static async create(account: LocalAccount): Promise { + const result = await Coinbase.apiClients.smartWallet!.createSmartWallet({ + owner: account.address, + }); + + const smartWallet = new SmartWallet(result.data!, account); + return smartWallet; + } + + public static async connect(smartWalletAddress: string, account: LocalAccount): Promise { + const result = await Coinbase.apiClients.smartWallet!.getSmartWallet(smartWalletAddress); + + if (!result.data?.owners.some(owner => owner.toLowerCase() === account.address.toLowerCase())) { + throw new Error('Account is not an owner of the smart wallet'); + } + + const smartWallet = new SmartWallet(result.data!, account); + return smartWallet; + } + + + + private wrapSignature(parameters: { + ownerIndex?: number | undefined + signature: Hex + }) { + + const { ownerIndex = 0 } = parameters + const signatureData = (() => { + if (size(parameters.signature) !== 65) return parameters.signature + const signature = parseSignature(parameters.signature) + return encodePacked( + ['bytes32', 'bytes32', 'uint8'], + [signature.r, signature.s, signature.yParity === 0 ? 27 : 28], + ) + })() + return encodeAbiParameters( + [ + { + components: [ + { + name: 'ownerIndex', + type: 'uint8', + }, + { + name: 'signatureData', + type: 'bytes', + }, + ], + type: 'tuple', + }, + ], + [ + { + ownerIndex, + signatureData, + }, + ], + ) + } + + public async sendUserOperation( + params: { calls: UserOperationCalls } + ): Promise { + // Encode the calls + const encodedCalls = params.calls.map((call) => { + if ('abi' in call) { + return { + data: encodeFunctionData({ + abi: call.abi, + functionName: call.functionName, + args: call.args + }), + to: call.to, + value: call.value || '0x0' + } + } + return { + data: call.data, + to: call.to, + value: call.value || '0x0' + } + }) + + const createOpResponse = await Coinbase.apiClients.smartWallet!.createUserOperation({ + network: NetworkIdentifier.BaseSepolia, // TODO - make this a property of the smart wallet? + calls: encodedCalls + }) + + if (!createOpResponse.data) { + throw new Error('Failed to create user operation') + } + + if (!this.account.sign) { + throw new Error('Account does not support signing') + } + + const signature = await this.account.sign({ hash: createOpResponse.data.unsigned_payload as `0x${string}` }) + const wrappedSignature = this.wrapSignature({ ownerIndex: 0, signature }) + + const broadcastResponse = await Coinbase.apiClients.smartWallet!.broadcastUserOperation({ + signature: wrappedSignature + }) + + if (!broadcastResponse.data) { + throw new Error('Failed to broadcast user operation') + } + + return broadcastResponse.data.id as `0x${string}` + } + +} diff --git a/src/coinbase/types.ts b/src/coinbase/types.ts index 8aef4db2..708a5eae 100644 --- a/src/coinbase/types.ts +++ b/src/coinbase/types.ts @@ -12,6 +12,7 @@ import { CreateTransferRequest, TransferList, Wallet as WalletModel, + SmartWallet as SmartWalletModel, Transfer as TransferModel, Trade as TradeModel, Asset as AssetModel, @@ -68,6 +69,10 @@ import { CompiledSmartContract, BroadcastExternalTransactionRequest, BroadcastExternalTransaction200Response, + CreateSmartWalletRequest, + CreateUserOperationRequest, + UserOperation as UserOperationModel, + BroadcastUserOperationRequest, } from "./../client/api"; import { Address } from "./address"; import { Wallet } from "./wallet"; @@ -244,6 +249,72 @@ export type WalletAPIClient = { ): AxiosPromise; }; +/** + * SmartWalletAPI client type definition. + */ +export type SmartWalletAPIClient = { + /** + * Create a new smart wallet scoped to the user. + * + * @class + * @param createdSmartWalletRequest - The smart wallet creation request. + * @param options - Axios request options. + * @throws {APIError} If the request fails. + */ + createSmartWallet: ( + createSmartWalletRequest?: CreateSmartWalletRequest, + options?: RawAxiosRequestConfig, + ) => AxiosPromise; + + /* + Get the smart wallet by address + + @param smartWalletAddress - The address of the smart wallet to fetch. + @param options - Override http request option. + @throws {APIError} If the request fails. + */ + getSmartWallet: ( + smartWalletAddress: string, + options?: RawAxiosRequestConfig, + ) => AxiosPromise; + + /* + Create a user operation + + @param createUserOperationRequest - The user operation creation request. + @param options - Override http request option. + @throws {APIError} If the request fails. + */ + createUserOperation: ( + createUserOperationRequest: CreateUserOperationRequest, + options?: RawAxiosRequestConfig, + ) => AxiosPromise; + + /* + Broadcast a user operation + + @param broadcastUserOperationRequest - The user operation broadcast request. + @param options - Override http request option. + @throws {APIError} If the request fails. + */ + broadcastUserOperation: ( + broadcastUserOperationRequest: BroadcastUserOperationRequest, + options?: RawAxiosRequestConfig, + ) => AxiosPromise; + + /* + Get a user operation by ID + + @param userOperationId - The ID of the user operation to fetch. + @param options - Override http request option. + @throws {APIError} If the request fails. + */ + getUserOperation: ( + userOperationId: string, + options?: RawAxiosRequestConfig, + ) => AxiosPromise; +}; + /** * AddressAPI client type definition. */ @@ -751,6 +822,7 @@ export type ApiClients = { smartContract?: SmartContractAPIClient; fund?: FundOperationApiClient; addressReputation?: AddressReputationApiClient; + smartWallet?: SmartWalletAPIClient; }; /** From c8d290ea52d334bce413fd8013f1447b8b7381f1 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Wed, 5 Feb 2025 17:55:38 -0500 Subject: [PATCH 02/23] changes so far --- src/coinbase/signer.ts | 23 ----------------------- src/index.ts | 1 + 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 src/coinbase/signer.ts diff --git a/src/coinbase/signer.ts b/src/coinbase/signer.ts deleted file mode 100644 index a6048748..00000000 --- a/src/coinbase/signer.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { - LocalAccount, - Address, - Hash, - Transaction, - TypedData - } from 'viem' - -interface WalletSigner { - // Core viem account properties - address: Address; - type: 'local'; - source: 'custom'; - - // Standard signing methods from viem - sign(hash: Hash): Promise; - signMessage(message: string | Bytes): Promise; - signTransaction(transaction: Transaction): Promise; - signTypedData(typedData: TypedData): Promise; - - // Optional: Add any smart wallet specific methods - signUserOperation?(userOp: UserOperation): Promise; -} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index d5412e89..6a3f5f1f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,3 +32,4 @@ export * from "./coinbase/crypto_amount"; export * from "./coinbase/fiat_amount"; export * from "./coinbase/fund_operation"; export * from "./coinbase/fund_quote"; +export * from "./coinbase/smart_wallet"; \ No newline at end of file From c0ebe943a5da8d1a0c22f1f58e9c3107569d4810 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Wed, 5 Feb 2025 17:58:23 -0500 Subject: [PATCH 03/23] remove --- src/coinbase/smart_wallet.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/coinbase/smart_wallet.ts b/src/coinbase/smart_wallet.ts index 88e36861..a8fd1c27 100644 --- a/src/coinbase/smart_wallet.ts +++ b/src/coinbase/smart_wallet.ts @@ -2,18 +2,10 @@ import { NetworkIdentifier, SmartWallet as SmartWalletModel } from "../client"; import { Coinbase } from "./coinbase"; -import type { Address, Narrow } from 'abitype' -import type { Client, Hex } from 'viem' -import type { Chain } from 'viem/chains' -import type { - UserOperation, - UserOperationRequest, - PrepareUserOperationRequest, - PrepareUserOperationReturnType, - UserOperationCalls, - SendUserOperationParameters, - SendUserOperationReturnType -} from 'viem/account-abstraction' +import type { Hex } from 'viem'; +import type { + UserOperationCalls, SendUserOperationReturnType +} from 'viem/account-abstraction'; import { encodeAbiParameters, encodeFunctionData, encodePacked, LocalAccount, parseSignature, size } from "viem"; From b2442a0fcbaa6bb3703f3419880b968fd1b1d076 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Wed, 5 Feb 2025 19:02:27 -0500 Subject: [PATCH 04/23] network --- src/coinbase/smart_wallet.ts | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/coinbase/smart_wallet.ts b/src/coinbase/smart_wallet.ts index a8fd1c27..1f4a751f 100644 --- a/src/coinbase/smart_wallet.ts +++ b/src/coinbase/smart_wallet.ts @@ -9,17 +9,18 @@ import type { import { encodeAbiParameters, encodeFunctionData, encodePacked, LocalAccount, parseSignature, size } from "viem"; -// TODO - figure out the network interface to connect to a specific network export class SmartWallet { private model: SmartWalletModel; private account: LocalAccount; + private network?: NetworkIdentifier; - public constructor(model: SmartWalletModel, account: LocalAccount) { + public constructor(model: SmartWalletModel, account: LocalAccount, network?: NetworkIdentifier) { this.model = model; this.account = account; + this.network = network; } - public static async create(account: LocalAccount): Promise { + public static async create({account}: {account: LocalAccount}): Promise { const result = await Coinbase.apiClients.smartWallet!.createSmartWallet({ owner: account.address, }); @@ -28,7 +29,7 @@ export class SmartWallet { return smartWallet; } - public static async connect(smartWalletAddress: string, account: LocalAccount): Promise { + public static async connect({smartWalletAddress, account}: {smartWalletAddress: string, account: LocalAccount}): Promise { const result = await Coinbase.apiClients.smartWallet!.getSmartWallet(smartWalletAddress); if (!result.data?.owners.some(owner => owner.toLowerCase() === account.address.toLowerCase())) { @@ -39,7 +40,17 @@ export class SmartWallet { return smartWallet; } + public async use({network}: {network: NetworkIdentifier}) { + this.network = network; + } + public getSmartWalletAddress() { + return this.model.address; + } + + private isNetworkSet() { + return this.network !== undefined; + } private wrapSignature(parameters: { ownerIndex?: number | undefined @@ -83,7 +94,10 @@ export class SmartWallet { public async sendUserOperation( params: { calls: UserOperationCalls } ): Promise { - // Encode the calls + if (!this.network) { + throw new Error('Network not set - call use({network}) first'); + } + const encodedCalls = params.calls.map((call) => { if ('abi' in call) { return { @@ -104,7 +118,7 @@ export class SmartWallet { }) const createOpResponse = await Coinbase.apiClients.smartWallet!.createUserOperation({ - network: NetworkIdentifier.BaseSepolia, // TODO - make this a property of the smart wallet? + network: this.network, calls: encodedCalls }) From 9cdc74d87bce37b7fe5ac9c094321b01d725724d Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Wed, 5 Feb 2025 19:02:51 -0500 Subject: [PATCH 05/23] Cleanup --- src/coinbase/smart_wallet.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/coinbase/smart_wallet.ts b/src/coinbase/smart_wallet.ts index 1f4a751f..a3891589 100644 --- a/src/coinbase/smart_wallet.ts +++ b/src/coinbase/smart_wallet.ts @@ -44,14 +44,10 @@ export class SmartWallet { this.network = network; } - public getSmartWalletAddress() { + public getAddress() { return this.model.address; } - private isNetworkSet() { - return this.network !== undefined; - } - private wrapSignature(parameters: { ownerIndex?: number | undefined signature: Hex From bbf7d5a0bfb463621d1025649839be7801cb19ab Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Wed, 5 Feb 2025 19:53:58 -0500 Subject: [PATCH 06/23] Initial user operation class --- src/coinbase/user_operation.ts | 118 +++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 src/coinbase/user_operation.ts diff --git a/src/coinbase/user_operation.ts b/src/coinbase/user_operation.ts new file mode 100644 index 00000000..60b67772 --- /dev/null +++ b/src/coinbase/user_operation.ts @@ -0,0 +1,118 @@ +import { Coinbase } from "./coinbase"; +import { UserOperation as UserOperationModel, UserOperationStatusEnum } from "../client/api"; +import { delay } from "./utils"; +import { TimeoutError } from "./errors"; + +/** + * A representation of a UserOperation, which calls a smart contract method + * onchain. + */ +export class UserOperation { + private model: UserOperationModel; + + /** + * Private constructor to prevent direct instantiation outside of the factory methods. + * + * @ignore + * @param userOperationModel - The UserOperation model. + * @hideconstructor + */ + public constructor(userOperationModel: UserOperationModel) { + this.model = userOperationModel; + } + + /** + * Returns the ID of the UserOperation. + * + * @returns The UserOperation ID. + */ + public getId(): string { + return this.model.id!; // TODO remove the ! + } + + /** + * Returns the Network ID of the UserOperation. + * + * @returns The Network ID. + */ + public getNetworkId(): string { + return this.model.network; + } + + + /** + * TODO + * + * Returns the Transaction Hash of the ContractInvocation. + * + * @returns The Transaction Hash as a Hex string, or undefined if not yet available. + */ + // public getTransactionHash(): string | undefined { + // return this.getTransaction().getTransactionHash(); + // } + + /** + * Returns the Status of the ContractInvocation. + * + * @returns The Status of the ContractInvocation. + */ + public getStatus(): UserOperationStatusEnum | undefined { + return this.model.status; + } + + /** + * TODO + * + * Returns the link to the Transaction on the blockchain explorer. + * + * @returns The link to the Transaction on the blockchain explorer. + */ + // public getTransactionLink(): string { + // return this.getTransaction().getTransactionLink(); + // } + + /** + * Waits for the ContractInvocation to be confirmed on the Network or fail on chain. + * Waits until the ContractInvocation is completed or failed on-chain by polling at the given interval. + * Raises an error if the ContractInvocation takes longer than the given timeout. + * + * @param options - The options to configure the wait function. + * @param options.intervalSeconds - The interval to check the status of the ContractInvocation. + * @param options.timeoutSeconds - The maximum time to wait for the ContractInvocation to be confirmed. + * + * @returns The ContractInvocation object in a terminal state. + * @throws {Error} if the ContractInvocation times out. + */ + public async wait({ + intervalSeconds = 0.2, + timeoutSeconds = 10, + } = {}): Promise { + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutSeconds * 1000) { + await this.reload(); + + // If the UserOperation is in a terminal state, return the UserOperation. + const status = this.getStatus(); + if (status === UserOperationStatusEnum.Complete || status === UserOperationStatusEnum.Failed) { + return this; + } + + await delay(intervalSeconds); + } + + throw new TimeoutError("UserOperation timed out"); + } + + /** + * Reloads the ContractInvocation model with the latest data from the server. + * + * @throws {APIError} if the API request to get a ContractInvocation fails. + */ + public async reload(): Promise { + const result = await Coinbase.apiClients.smartWallet!.getUserOperation( + this.model.id!, + ); + this.model = result?.data; + } +} From 3a23b1c822ba7d096d3773b72aa41496ac10835e Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Wed, 5 Feb 2025 19:54:30 -0500 Subject: [PATCH 07/23] Fix --- src/coinbase/smart_wallet.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coinbase/smart_wallet.ts b/src/coinbase/smart_wallet.ts index a3891589..dbbc1ae0 100644 --- a/src/coinbase/smart_wallet.ts +++ b/src/coinbase/smart_wallet.ts @@ -8,6 +8,7 @@ import type { } from 'viem/account-abstraction'; import { encodeAbiParameters, encodeFunctionData, encodePacked, LocalAccount, parseSignature, size } from "viem"; +import { UserOperation } from "./user_operation"; export class SmartWallet { private model: SmartWalletModel; @@ -89,7 +90,7 @@ export class SmartWallet { public async sendUserOperation( params: { calls: UserOperationCalls } - ): Promise { + ): Promise { if (!this.network) { throw new Error('Network not set - call use({network}) first'); } @@ -137,7 +138,7 @@ export class SmartWallet { throw new Error('Failed to broadcast user operation') } - return broadcastResponse.data.id as `0x${string}` + return new UserOperation(broadcastResponse.data); } } From ea1ee75ab5190674666564add6805d8298ef950d Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Wed, 5 Feb 2025 19:54:46 -0500 Subject: [PATCH 08/23] index --- src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 6a3f5f1f..3be357e2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,4 +32,5 @@ export * from "./coinbase/crypto_amount"; export * from "./coinbase/fiat_amount"; export * from "./coinbase/fund_operation"; export * from "./coinbase/fund_quote"; -export * from "./coinbase/smart_wallet"; \ No newline at end of file +export * from "./coinbase/smart_wallet"; +export * from "./coinbase/user_operation"; \ No newline at end of file From 8fdefaa6aa5e6368e399680eb52927a6b4c2d342 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Thu, 6 Feb 2025 15:58:51 -0500 Subject: [PATCH 09/23] Update code to compile --- src/client/api.ts | 174 +++++++++++++++++---------------- src/coinbase/smart_wallet.ts | 22 +++-- src/coinbase/types.ts | 2 + src/coinbase/user_operation.ts | 4 +- 4 files changed, 104 insertions(+), 98 deletions(-) diff --git a/src/client/api.ts b/src/client/api.ts index ede1d1c7..8b24f319 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -474,11 +474,11 @@ export interface BuildStakingOperationRequest { */ export interface Call { /** - * The optional address of the contract to call. + * The address the call is interacting with. * @type {string} * @memberof Call */ - 'to'?: string; + 'to': string; /** * The hex-encoded data to send with the call. * @type {string} @@ -486,11 +486,11 @@ export interface Call { */ 'data': string; /** - * The optional hex-encoded value to send with the call. + * The string-encoded integer value to send with the call. * @type {string} * @memberof Call */ - 'value'?: string; + 'value': string; } /** * @@ -1094,12 +1094,6 @@ export interface CreateTransferRequest { * @interface CreateUserOperationRequest */ export interface CreateUserOperationRequest { - /** - * The network to create the user operation on. - * @type {string} - * @memberof CreateUserOperationRequest - */ - 'network': string; /** * The list of calls to make from the smart wallet. * @type {Array} @@ -4007,13 +4001,13 @@ export interface UserOperation { * @type {string} * @memberof UserOperation */ - 'id'?: string; + 'id': string; /** - * The network the user operation is being created on. + * The ID of the network the user operation is being created on. * @type {string} * @memberof UserOperation */ - 'network': string; + 'network_id': string; /** * The list of calls to make from the smart wallet. * @type {Array} @@ -9905,19 +9899,19 @@ export const SmartWalletsApiAxiosParamCreator = function (configuration?: Config /** * Broadcast a user operation * @summary Broadcast a user operation - * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} smartWalletAddress The address of the smart wallet to broadcast the user operation from. * @param {string} userOperationId The ID of the user operation to broadcast. * @param {BroadcastUserOperationRequest} [broadcastUserOperationRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - broadcastUserOperation: async (contractAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options: RawAxiosRequestConfig = {}): Promise => { - // verify required parameter 'contractAddress' is not null or undefined - assertParamExists('broadcastUserOperation', 'contractAddress', contractAddress) + broadcastUserOperation: async (smartWalletAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'smartWalletAddress' is not null or undefined + assertParamExists('broadcastUserOperation', 'smartWalletAddress', smartWalletAddress) // verify required parameter 'userOperationId' is not null or undefined assertParamExists('broadcastUserOperation', 'userOperationId', userOperationId) - const localVarPath = `/v1/smart_wallets/{contract_address}/user_operations/{user_operation_id}/broadcast` - .replace(`{${"contract_address"}}`, encodeURIComponent(String(contractAddress))) + const localVarPath = `/v1/smart_wallets/{smart_wallet_address}/user_operations/{user_operation_id}/broadcast` + .replace(`{${"smart_wallet_address"}}`, encodeURIComponent(String(smartWalletAddress))) .replace(`{${"user_operation_id"}}`, encodeURIComponent(String(userOperationId))); // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -9987,16 +9981,20 @@ export const SmartWalletsApiAxiosParamCreator = function (configuration?: Config /** * Create a new user operation on a smart wallet. * @summary Create a new user operation - * @param {string} contractAddress The contract address of the smart wallet to create the user operation on. + * @param {string} smartWalletAddress The address of the smart wallet to create the user operation on. + * @param {string} networkId The ID of the network to create the user operation on. * @param {CreateUserOperationRequest} [createUserOperationRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - createUserOperation: async (contractAddress: string, createUserOperationRequest?: CreateUserOperationRequest, options: RawAxiosRequestConfig = {}): Promise => { - // verify required parameter 'contractAddress' is not null or undefined - assertParamExists('createUserOperation', 'contractAddress', contractAddress) - const localVarPath = `/v1/smart_wallets/{contract_address}/user_operations` - .replace(`{${"contract_address"}}`, encodeURIComponent(String(contractAddress))); + createUserOperation: async (smartWalletAddress: string, networkId: string, createUserOperationRequest?: CreateUserOperationRequest, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'smartWalletAddress' is not null or undefined + assertParamExists('createUserOperation', 'smartWalletAddress', smartWalletAddress) + // verify required parameter 'networkId' is not null or undefined + assertParamExists('createUserOperation', 'networkId', networkId) + const localVarPath = `/v1/smart_wallets/{smart_wallet_address}/networks/{network_id}/user_operations` + .replace(`{${"smart_wallet_address"}}`, encodeURIComponent(String(smartWalletAddress))) + .replace(`{${"network_id"}}`, encodeURIComponent(String(networkId))); // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -10027,16 +10025,16 @@ export const SmartWalletsApiAxiosParamCreator = function (configuration?: Config }, /** * Get smart wallet - * @summary Get smart wallet by contract address - * @param {string} contractAddress The contract address of the smart wallet to fetch. + * @summary Get smart wallet by address + * @param {string} smartWalletAddress The address of that smart wallet to fetch. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getSmartWallet: async (contractAddress: string, options: RawAxiosRequestConfig = {}): Promise => { - // verify required parameter 'contractAddress' is not null or undefined - assertParamExists('getSmartWallet', 'contractAddress', contractAddress) - const localVarPath = `/v1/smart_wallets/{contract_address}` - .replace(`{${"contract_address"}}`, encodeURIComponent(String(contractAddress))); + getSmartWallet: async (smartWalletAddress: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'smartWalletAddress' is not null or undefined + assertParamExists('getSmartWallet', 'smartWalletAddress', smartWalletAddress) + const localVarPath = `/v1/smart_wallets/{smart_wallet_address}` + .replace(`{${"smart_wallet_address"}}`, encodeURIComponent(String(smartWalletAddress))); // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -10068,18 +10066,18 @@ export const SmartWalletsApiAxiosParamCreator = function (configuration?: Config /** * Get user operation * @summary Get user operation - * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} smartWalletAddress The address of the smart wallet the user operation belongs to. * @param {string} userOperationId The ID of the user operation to fetch. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getUserOperation: async (contractAddress: string, userOperationId: string, options: RawAxiosRequestConfig = {}): Promise => { - // verify required parameter 'contractAddress' is not null or undefined - assertParamExists('getUserOperation', 'contractAddress', contractAddress) + getUserOperation: async (smartWalletAddress: string, userOperationId: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'smartWalletAddress' is not null or undefined + assertParamExists('getUserOperation', 'smartWalletAddress', smartWalletAddress) // verify required parameter 'userOperationId' is not null or undefined assertParamExists('getUserOperation', 'userOperationId', userOperationId) - const localVarPath = `/v1/smart_wallets/{contract_address}/user_operations/{user_operation_id}` - .replace(`{${"contract_address"}}`, encodeURIComponent(String(contractAddress))) + const localVarPath = `/v1/smart_wallets/{smart_wallet_address}/user_operations/{user_operation_id}` + .replace(`{${"smart_wallet_address"}}`, encodeURIComponent(String(smartWalletAddress))) .replace(`{${"user_operation_id"}}`, encodeURIComponent(String(userOperationId))); // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -10168,14 +10166,14 @@ export const SmartWalletsApiFp = function(configuration?: Configuration) { /** * Broadcast a user operation * @summary Broadcast a user operation - * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} smartWalletAddress The address of the smart wallet to broadcast the user operation from. * @param {string} userOperationId The ID of the user operation to broadcast. * @param {BroadcastUserOperationRequest} [broadcastUserOperationRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async broadcastUserOperation(contractAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.broadcastUserOperation(contractAddress, userOperationId, broadcastUserOperationRequest, options); + async broadcastUserOperation(smartWalletAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.broadcastUserOperation(smartWalletAddress, userOperationId, broadcastUserOperationRequest, options); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['SmartWalletsApi.broadcastUserOperation']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); @@ -10196,26 +10194,27 @@ export const SmartWalletsApiFp = function(configuration?: Configuration) { /** * Create a new user operation on a smart wallet. * @summary Create a new user operation - * @param {string} contractAddress The contract address of the smart wallet to create the user operation on. + * @param {string} smartWalletAddress The address of the smart wallet to create the user operation on. + * @param {string} networkId The ID of the network to create the user operation on. * @param {CreateUserOperationRequest} [createUserOperationRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async createUserOperation(contractAddress: string, createUserOperationRequest?: CreateUserOperationRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.createUserOperation(contractAddress, createUserOperationRequest, options); + async createUserOperation(smartWalletAddress: string, networkId: string, createUserOperationRequest?: CreateUserOperationRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.createUserOperation(smartWalletAddress, networkId, createUserOperationRequest, options); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['SmartWalletsApi.createUserOperation']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, /** * Get smart wallet - * @summary Get smart wallet by contract address - * @param {string} contractAddress The contract address of the smart wallet to fetch. + * @summary Get smart wallet by address + * @param {string} smartWalletAddress The address of that smart wallet to fetch. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getSmartWallet(contractAddress: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getSmartWallet(contractAddress, options); + async getSmartWallet(smartWalletAddress: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getSmartWallet(smartWalletAddress, options); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['SmartWalletsApi.getSmartWallet']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); @@ -10223,13 +10222,13 @@ export const SmartWalletsApiFp = function(configuration?: Configuration) { /** * Get user operation * @summary Get user operation - * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} smartWalletAddress The address of the smart wallet the user operation belongs to. * @param {string} userOperationId The ID of the user operation to fetch. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getUserOperation(contractAddress: string, userOperationId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getUserOperation(contractAddress, userOperationId, options); + async getUserOperation(smartWalletAddress: string, userOperationId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getUserOperation(smartWalletAddress, userOperationId, options); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['SmartWalletsApi.getUserOperation']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); @@ -10261,14 +10260,14 @@ export const SmartWalletsApiFactory = function (configuration?: Configuration, b /** * Broadcast a user operation * @summary Broadcast a user operation - * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} smartWalletAddress The address of the smart wallet to broadcast the user operation from. * @param {string} userOperationId The ID of the user operation to broadcast. * @param {BroadcastUserOperationRequest} [broadcastUserOperationRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - broadcastUserOperation(contractAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options?: RawAxiosRequestConfig): AxiosPromise { - return localVarFp.broadcastUserOperation(contractAddress, userOperationId, broadcastUserOperationRequest, options).then((request) => request(axios, basePath)); + broadcastUserOperation(smartWalletAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.broadcastUserOperation(smartWalletAddress, userOperationId, broadcastUserOperationRequest, options).then((request) => request(axios, basePath)); }, /** * Create a new smart wallet, not scoped to a given network. @@ -10283,34 +10282,35 @@ export const SmartWalletsApiFactory = function (configuration?: Configuration, b /** * Create a new user operation on a smart wallet. * @summary Create a new user operation - * @param {string} contractAddress The contract address of the smart wallet to create the user operation on. + * @param {string} smartWalletAddress The address of the smart wallet to create the user operation on. + * @param {string} networkId The ID of the network to create the user operation on. * @param {CreateUserOperationRequest} [createUserOperationRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - createUserOperation(contractAddress: string, createUserOperationRequest?: CreateUserOperationRequest, options?: RawAxiosRequestConfig): AxiosPromise { - return localVarFp.createUserOperation(contractAddress, createUserOperationRequest, options).then((request) => request(axios, basePath)); + createUserOperation(smartWalletAddress: string, networkId: string, createUserOperationRequest?: CreateUserOperationRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.createUserOperation(smartWalletAddress, networkId, createUserOperationRequest, options).then((request) => request(axios, basePath)); }, /** * Get smart wallet - * @summary Get smart wallet by contract address - * @param {string} contractAddress The contract address of the smart wallet to fetch. + * @summary Get smart wallet by address + * @param {string} smartWalletAddress The address of that smart wallet to fetch. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getSmartWallet(contractAddress: string, options?: RawAxiosRequestConfig): AxiosPromise { - return localVarFp.getSmartWallet(contractAddress, options).then((request) => request(axios, basePath)); + getSmartWallet(smartWalletAddress: string, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.getSmartWallet(smartWalletAddress, options).then((request) => request(axios, basePath)); }, /** * Get user operation * @summary Get user operation - * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} smartWalletAddress The address of the smart wallet the user operation belongs to. * @param {string} userOperationId The ID of the user operation to fetch. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getUserOperation(contractAddress: string, userOperationId: string, options?: RawAxiosRequestConfig): AxiosPromise { - return localVarFp.getUserOperation(contractAddress, userOperationId, options).then((request) => request(axios, basePath)); + getUserOperation(smartWalletAddress: string, userOperationId: string, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.getUserOperation(smartWalletAddress, userOperationId, options).then((request) => request(axios, basePath)); }, /** * List smart wallets @@ -10335,14 +10335,14 @@ export interface SmartWalletsApiInterface { /** * Broadcast a user operation * @summary Broadcast a user operation - * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} smartWalletAddress The address of the smart wallet to broadcast the user operation from. * @param {string} userOperationId The ID of the user operation to broadcast. * @param {BroadcastUserOperationRequest} [broadcastUserOperationRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof SmartWalletsApiInterface */ - broadcastUserOperation(contractAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options?: RawAxiosRequestConfig): AxiosPromise; + broadcastUserOperation(smartWalletAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options?: RawAxiosRequestConfig): AxiosPromise; /** * Create a new smart wallet, not scoped to a given network. @@ -10357,34 +10357,35 @@ export interface SmartWalletsApiInterface { /** * Create a new user operation on a smart wallet. * @summary Create a new user operation - * @param {string} contractAddress The contract address of the smart wallet to create the user operation on. + * @param {string} smartWalletAddress The address of the smart wallet to create the user operation on. + * @param {string} networkId The ID of the network to create the user operation on. * @param {CreateUserOperationRequest} [createUserOperationRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof SmartWalletsApiInterface */ - createUserOperation(contractAddress: string, createUserOperationRequest?: CreateUserOperationRequest, options?: RawAxiosRequestConfig): AxiosPromise; + createUserOperation(smartWalletAddress: string, networkId: string, createUserOperationRequest?: CreateUserOperationRequest, options?: RawAxiosRequestConfig): AxiosPromise; /** * Get smart wallet - * @summary Get smart wallet by contract address - * @param {string} contractAddress The contract address of the smart wallet to fetch. + * @summary Get smart wallet by address + * @param {string} smartWalletAddress The address of that smart wallet to fetch. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof SmartWalletsApiInterface */ - getSmartWallet(contractAddress: string, options?: RawAxiosRequestConfig): AxiosPromise; + getSmartWallet(smartWalletAddress: string, options?: RawAxiosRequestConfig): AxiosPromise; /** * Get user operation * @summary Get user operation - * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} smartWalletAddress The address of the smart wallet the user operation belongs to. * @param {string} userOperationId The ID of the user operation to fetch. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof SmartWalletsApiInterface */ - getUserOperation(contractAddress: string, userOperationId: string, options?: RawAxiosRequestConfig): AxiosPromise; + getUserOperation(smartWalletAddress: string, userOperationId: string, options?: RawAxiosRequestConfig): AxiosPromise; /** * List smart wallets @@ -10409,15 +10410,15 @@ export class SmartWalletsApi extends BaseAPI implements SmartWalletsApiInterface /** * Broadcast a user operation * @summary Broadcast a user operation - * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} smartWalletAddress The address of the smart wallet to broadcast the user operation from. * @param {string} userOperationId The ID of the user operation to broadcast. * @param {BroadcastUserOperationRequest} [broadcastUserOperationRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof SmartWalletsApi */ - public broadcastUserOperation(contractAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options?: RawAxiosRequestConfig) { - return SmartWalletsApiFp(this.configuration).broadcastUserOperation(contractAddress, userOperationId, broadcastUserOperationRequest, options).then((request) => request(this.axios, this.basePath)); + public broadcastUserOperation(smartWalletAddress: string, userOperationId: string, broadcastUserOperationRequest?: BroadcastUserOperationRequest, options?: RawAxiosRequestConfig) { + return SmartWalletsApiFp(this.configuration).broadcastUserOperation(smartWalletAddress, userOperationId, broadcastUserOperationRequest, options).then((request) => request(this.axios, this.basePath)); } /** @@ -10435,39 +10436,40 @@ export class SmartWalletsApi extends BaseAPI implements SmartWalletsApiInterface /** * Create a new user operation on a smart wallet. * @summary Create a new user operation - * @param {string} contractAddress The contract address of the smart wallet to create the user operation on. + * @param {string} smartWalletAddress The address of the smart wallet to create the user operation on. + * @param {string} networkId The ID of the network to create the user operation on. * @param {CreateUserOperationRequest} [createUserOperationRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof SmartWalletsApi */ - public createUserOperation(contractAddress: string, createUserOperationRequest?: CreateUserOperationRequest, options?: RawAxiosRequestConfig) { - return SmartWalletsApiFp(this.configuration).createUserOperation(contractAddress, createUserOperationRequest, options).then((request) => request(this.axios, this.basePath)); + public createUserOperation(smartWalletAddress: string, networkId: string, createUserOperationRequest?: CreateUserOperationRequest, options?: RawAxiosRequestConfig) { + return SmartWalletsApiFp(this.configuration).createUserOperation(smartWalletAddress, networkId, createUserOperationRequest, options).then((request) => request(this.axios, this.basePath)); } /** * Get smart wallet - * @summary Get smart wallet by contract address - * @param {string} contractAddress The contract address of the smart wallet to fetch. + * @summary Get smart wallet by address + * @param {string} smartWalletAddress The address of that smart wallet to fetch. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof SmartWalletsApi */ - public getSmartWallet(contractAddress: string, options?: RawAxiosRequestConfig) { - return SmartWalletsApiFp(this.configuration).getSmartWallet(contractAddress, options).then((request) => request(this.axios, this.basePath)); + public getSmartWallet(smartWalletAddress: string, options?: RawAxiosRequestConfig) { + return SmartWalletsApiFp(this.configuration).getSmartWallet(smartWalletAddress, options).then((request) => request(this.axios, this.basePath)); } /** * Get user operation * @summary Get user operation - * @param {string} contractAddress The contract address of the smart wallet the user operation belongs to. + * @param {string} smartWalletAddress The address of the smart wallet the user operation belongs to. * @param {string} userOperationId The ID of the user operation to fetch. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof SmartWalletsApi */ - public getUserOperation(contractAddress: string, userOperationId: string, options?: RawAxiosRequestConfig) { - return SmartWalletsApiFp(this.configuration).getUserOperation(contractAddress, userOperationId, options).then((request) => request(this.axios, this.basePath)); + public getUserOperation(smartWalletAddress: string, userOperationId: string, options?: RawAxiosRequestConfig) { + return SmartWalletsApiFp(this.configuration).getUserOperation(smartWalletAddress, userOperationId, options).then((request) => request(this.axios, this.basePath)); } /** diff --git a/src/coinbase/smart_wallet.ts b/src/coinbase/smart_wallet.ts index dbbc1ae0..a33c5d57 100644 --- a/src/coinbase/smart_wallet.ts +++ b/src/coinbase/smart_wallet.ts @@ -4,7 +4,7 @@ import { Coinbase } from "./coinbase"; import type { Hex } from 'viem'; import type { - UserOperationCalls, SendUserOperationReturnType + UserOperationCalls } from 'viem/account-abstraction'; import { encodeAbiParameters, encodeFunctionData, encodePacked, LocalAccount, parseSignature, size } from "viem"; @@ -13,12 +13,11 @@ import { UserOperation } from "./user_operation"; export class SmartWallet { private model: SmartWalletModel; private account: LocalAccount; - private network?: NetworkIdentifier; + private networkId?: NetworkIdentifier; - public constructor(model: SmartWalletModel, account: LocalAccount, network?: NetworkIdentifier) { + public constructor(model: SmartWalletModel, account: LocalAccount) { this.model = model; this.account = account; - this.network = network; } public static async create({account}: {account: LocalAccount}): Promise { @@ -42,7 +41,7 @@ export class SmartWallet { } public async use({network}: {network: NetworkIdentifier}) { - this.network = network; + this.networkId = network; } public getAddress() { @@ -91,7 +90,7 @@ export class SmartWallet { public async sendUserOperation( params: { calls: UserOperationCalls } ): Promise { - if (!this.network) { + if (!this.networkId) { throw new Error('Network not set - call use({network}) first'); } @@ -114,10 +113,13 @@ export class SmartWallet { } }) - const createOpResponse = await Coinbase.apiClients.smartWallet!.createUserOperation({ - network: this.network, - calls: encodedCalls - }) + const createOpResponse = await Coinbase.apiClients.smartWallet!.createUserOperation( + this.getAddress(), + this.networkId, + { + calls: encodedCalls + } + ) if (!createOpResponse.data) { throw new Error('Failed to create user operation') diff --git a/src/coinbase/types.ts b/src/coinbase/types.ts index 708a5eae..7213a108 100644 --- a/src/coinbase/types.ts +++ b/src/coinbase/types.ts @@ -286,6 +286,8 @@ export type SmartWalletAPIClient = { @throws {APIError} If the request fails. */ createUserOperation: ( + smartWalletAddress: string, + networkId: string, createUserOperationRequest: CreateUserOperationRequest, options?: RawAxiosRequestConfig, ) => AxiosPromise; diff --git a/src/coinbase/user_operation.ts b/src/coinbase/user_operation.ts index 60b67772..e374bcd3 100644 --- a/src/coinbase/user_operation.ts +++ b/src/coinbase/user_operation.ts @@ -27,7 +27,7 @@ export class UserOperation { * @returns The UserOperation ID. */ public getId(): string { - return this.model.id!; // TODO remove the ! + return this.model.id; } /** @@ -36,7 +36,7 @@ export class UserOperation { * @returns The Network ID. */ public getNetworkId(): string { - return this.model.network; + return this.model.network_id; } From 815ac06ed6bfe3c4bf05eb2ea016a10606564541 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Thu, 6 Feb 2025 15:59:56 -0500 Subject: [PATCH 10/23] Rename --- src/coinbase/smart_wallet.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coinbase/smart_wallet.ts b/src/coinbase/smart_wallet.ts index a33c5d57..775e9708 100644 --- a/src/coinbase/smart_wallet.ts +++ b/src/coinbase/smart_wallet.ts @@ -40,8 +40,8 @@ export class SmartWallet { return smartWallet; } - public async use({network}: {network: NetworkIdentifier}) { - this.networkId = network; + public async use({networkId}: {networkId: NetworkIdentifier}) { + this.networkId = networkId; } public getAddress() { @@ -91,7 +91,7 @@ export class SmartWallet { params: { calls: UserOperationCalls } ): Promise { if (!this.networkId) { - throw new Error('Network not set - call use({network}) first'); + throw new Error('Network not set - call use({networkId}) first'); } const encodedCalls = params.calls.map((call) => { From 67c2b378e415117dbb168892336bf897d4fa410c Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Thu, 6 Feb 2025 16:09:32 -0500 Subject: [PATCH 11/23] Test smart wallet --- test_smart_wallet.ts | 342 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 342 insertions(+) create mode 100644 test_smart_wallet.ts diff --git a/test_smart_wallet.ts b/test_smart_wallet.ts new file mode 100644 index 00000000..42d982e4 --- /dev/null +++ b/test_smart_wallet.ts @@ -0,0 +1,342 @@ +import { createPublicClient, http, parseEther } from 'viem' +import { createBundlerClient, toCoinbaseSmartAccount } from 'viem/account-abstraction' +import { mainnet } from 'viem/chains' +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' +import { SmartWallet } from './src/coinbase/smart_wallet' +import { Wallet } from './src' + +const myAbi = [ + { + type: "function", + name: "transfer", + inputs: [ + { name: "to", type: "address" }, + { name: "value", type: "uint256" }, + ], + outputs: [{ name: "", type: "bool", internalType: "bool" }], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "pureInt16", + inputs: [], + outputs: [{ name: "", type: "int16", internalType: "int16" }], + stateMutability: "pure", + }, + { + type: "function", + name: "pureUint16", + inputs: [], + outputs: [{ name: "", type: "uint16", internalType: "uint16" }], + stateMutability: "pure", + }, + { + type: "function", + name: "pureUint256", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "pure", + }, + { + inputs: [], + stateMutability: "pure", + type: "function", + name: "pureInt256", + outputs: [{ internalType: "int256", name: "", type: "int256" }], + }, + { + inputs: [], + stateMutability: "pure", + type: "function", + name: "pureUint128", + outputs: [{ internalType: "uint128", name: "", type: "uint128" }], + }, + { + inputs: [], + stateMutability: "pure", + type: "function", + name: "pureUint64", + outputs: [{ internalType: "uint64", name: "", type: "uint64" }], + }, + { + inputs: [], + stateMutability: "pure", + type: "function", + name: "pureUint32", + outputs: [{ internalType: "uint32", name: "", type: "uint32" }], + }, + { + inputs: [{ internalType: "uint256", name: "y", type: "uint256" }], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [{ internalType: "uint256", name: "z", type: "uint256" }], + name: "exampleFunction", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "pureAddress", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "pureArray", + outputs: [{ internalType: "uint256[]", name: "", type: "uint256[]" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "pureBool", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "pureBytes", + outputs: [{ internalType: "bytes", name: "", type: "bytes" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "pureBytes1", + outputs: [{ internalType: "bytes1", name: "", type: "bytes1" }], + stateMutability: "pure", + type: "function", + }, + // ... (other pureBytes functions) + { + inputs: [], + name: "pureNestedStruct", + outputs: [ + { + components: [ + { internalType: "uint256", name: "a", type: "uint256" }, + { + components: [ + { + components: [{ internalType: "uint256[]", name: "a", type: "uint256[]" }], + internalType: "struct TestAllReadTypes.ArrayData", + name: "nestedArray", + type: "tuple", + }, + { internalType: "uint256", name: "a", type: "uint256" }, + ], + internalType: "struct TestAllReadTypes.NestedData", + name: "nestedFields", + type: "tuple", + }, + ], + internalType: "struct TestAllReadTypes.ExampleStruct", + name: "", + type: "tuple", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "pureString", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "pureTuple", + outputs: [ + { internalType: "uint256", name: "", type: "uint256" }, + { internalType: "uint256", name: "", type: "uint256" }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "pureTupleMixedTypes", + outputs: [ + { internalType: "uint256", name: "", type: "uint256" }, + { internalType: "address", name: "", type: "address" }, + { internalType: "bool", name: "", type: "bool" }, + ], + stateMutability: "pure", + type: "function", + }, + // ... (other pure functions) + { + inputs: [], + name: "returnFunction", + outputs: [ + { + internalType: "function (uint256) external returns (bool)", + name: "", + type: "function", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "viewUint", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "x", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ name: "x", type: "address" }], + name: "overload", + outputs: [{ name: "", type: "uint256" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { name: "x", type: "uint256" }, + { name: "y", type: "uint256" }, + ], + name: "overload", + outputs: [{ name: "", type: "uint256[]" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [{ name: "x", type: "uint256" }], + name: "overload", + outputs: [{ name: "", type: "bool" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "overload", + outputs: [{ name: "", type: "uint256" }], + stateMutability: "pure", + type: "function", + }, + { + type: "function", + name: "pureNestedStruct", + inputs: [], + outputs: [ + { + name: "", + type: "tuple", + internalType: "struct TestAllReadTypes.ExampleStruct", + components: [ + { name: "a", type: "uint256", internalType: "uint256" }, + { + name: "nestedFields", + type: "tuple", + internalType: "struct TestAllReadTypes.NestedData", + components: [ + { + name: "nestedArray", + type: "tuple", + internalType: "struct TestAllReadTypes.ArrayData", + components: [{ name: "a", type: "uint256[]", internalType: "uint256[]" }], + }, + { name: "a", type: "uint256", internalType: "uint256" }, + ], + }, + ], + }, + ], + stateMutability: "pure", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + ], + name: "mint", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "transferFrom", + outputs: [], + stateMutability: "payable", + type: "function", + }, +] as const; + + + + + +async function main() { + // create a wallet + const wallet = await Wallet.create(); + + // faucet it + const faucet = await wallet.faucet(); + + // create a smart wallet with viem wallet owner + const privateKey = generatePrivateKey() + const owner = privateKeyToAccount(privateKey) + const smartWallet = await SmartWallet.create({account: owner}) + + // send ETH from wallet to smart wallet so the smart wallet has funds to send back + const currentBalance = await wallet.getBalance("eth") + const halfBalance = currentBalance.div(2) + + const transfer = await wallet.createTransfer({ + amount: halfBalance, // send half since we need some funds for gas + assetId: "eth", + destination: smartWallet.getAddress(), + }) + await transfer.wait() + + // I believe that SCW-Manager should automatically sponsor all base-sepolia user operations so we don't need to have additional funds for gas + const userOperation = await smartWallet.sendUserOperation({ + calls: [{ + to: '0xcb98643b8786950F0461f3B0edf99D88F274574D', + value: parseEther(halfBalance.toString()), + data: '0x' + } + ] + }) + await userOperation.wait() + + console.log(userOperation.getStatus()) +} + +main(); \ No newline at end of file From 838954728e4614162ad6fa277b5310e2faa42cd1 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Thu, 6 Feb 2025 16:12:12 -0500 Subject: [PATCH 12/23] Fix --- src/coinbase/smart_wallet.ts | 12 ++++++++---- src/coinbase/types.ts | 3 +++ src/coinbase/user_operation.ts | 12 +++++++++--- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/coinbase/smart_wallet.ts b/src/coinbase/smart_wallet.ts index 775e9708..ce1e7569 100644 --- a/src/coinbase/smart_wallet.ts +++ b/src/coinbase/smart_wallet.ts @@ -132,15 +132,19 @@ export class SmartWallet { const signature = await this.account.sign({ hash: createOpResponse.data.unsigned_payload as `0x${string}` }) const wrappedSignature = this.wrapSignature({ ownerIndex: 0, signature }) - const broadcastResponse = await Coinbase.apiClients.smartWallet!.broadcastUserOperation({ - signature: wrappedSignature - }) + const broadcastResponse = await Coinbase.apiClients.smartWallet!.broadcastUserOperation( + this.getAddress(), + createOpResponse.data.id, + { + signature: wrappedSignature + } + ) if (!broadcastResponse.data) { throw new Error('Failed to broadcast user operation') } - return new UserOperation(broadcastResponse.data); + return new UserOperation(broadcastResponse.data, this.getAddress()); } } diff --git a/src/coinbase/types.ts b/src/coinbase/types.ts index 7213a108..89d8c45c 100644 --- a/src/coinbase/types.ts +++ b/src/coinbase/types.ts @@ -300,6 +300,8 @@ export type SmartWalletAPIClient = { @throws {APIError} If the request fails. */ broadcastUserOperation: ( + smartWalletAddress: string, + userOperationId: string, broadcastUserOperationRequest: BroadcastUserOperationRequest, options?: RawAxiosRequestConfig, ) => AxiosPromise; @@ -312,6 +314,7 @@ export type SmartWalletAPIClient = { @throws {APIError} If the request fails. */ getUserOperation: ( + smartWalletAddress: string, userOperationId: string, options?: RawAxiosRequestConfig, ) => AxiosPromise; diff --git a/src/coinbase/user_operation.ts b/src/coinbase/user_operation.ts index e374bcd3..d6c44570 100644 --- a/src/coinbase/user_operation.ts +++ b/src/coinbase/user_operation.ts @@ -9,7 +9,7 @@ import { TimeoutError } from "./errors"; */ export class UserOperation { private model: UserOperationModel; - + private smartWalletAddress: string; /** * Private constructor to prevent direct instantiation outside of the factory methods. * @@ -17,8 +17,9 @@ export class UserOperation { * @param userOperationModel - The UserOperation model. * @hideconstructor */ - public constructor(userOperationModel: UserOperationModel) { + public constructor(userOperationModel: UserOperationModel, smartWalletAddress: string) { this.model = userOperationModel; + this.smartWalletAddress = smartWalletAddress; } /** @@ -39,6 +40,10 @@ export class UserOperation { return this.model.network_id; } + public getSmartWalletAddress(): string { + return this.smartWalletAddress; + } + /** * TODO @@ -111,7 +116,8 @@ export class UserOperation { */ public async reload(): Promise { const result = await Coinbase.apiClients.smartWallet!.getUserOperation( - this.model.id!, + this.getSmartWalletAddress(), + this.getId(), ); this.model = result?.data; } From a54ed8244808fd1c3e29b695b314854dac537a0b Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Thu, 6 Feb 2025 16:14:04 -0500 Subject: [PATCH 13/23] Fix --- test_smart_wallet.ts | 307 +------------------------------------------ 1 file changed, 7 insertions(+), 300 deletions(-) diff --git a/test_smart_wallet.ts b/test_smart_wallet.ts index 42d982e4..c38df770 100644 --- a/test_smart_wallet.ts +++ b/test_smart_wallet.ts @@ -1,306 +1,13 @@ -import { createPublicClient, http, parseEther } from 'viem' -import { createBundlerClient, toCoinbaseSmartAccount } from 'viem/account-abstraction' -import { mainnet } from 'viem/chains' +import { parseEther } from 'viem' import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' import { SmartWallet } from './src/coinbase/smart_wallet' -import { Wallet } from './src' - -const myAbi = [ - { - type: "function", - name: "transfer", - inputs: [ - { name: "to", type: "address" }, - { name: "value", type: "uint256" }, - ], - outputs: [{ name: "", type: "bool", internalType: "bool" }], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "pureInt16", - inputs: [], - outputs: [{ name: "", type: "int16", internalType: "int16" }], - stateMutability: "pure", - }, - { - type: "function", - name: "pureUint16", - inputs: [], - outputs: [{ name: "", type: "uint16", internalType: "uint16" }], - stateMutability: "pure", - }, - { - type: "function", - name: "pureUint256", - inputs: [], - outputs: [{ name: "", type: "uint256", internalType: "uint256" }], - stateMutability: "pure", - }, - { - inputs: [], - stateMutability: "pure", - type: "function", - name: "pureInt256", - outputs: [{ internalType: "int256", name: "", type: "int256" }], - }, - { - inputs: [], - stateMutability: "pure", - type: "function", - name: "pureUint128", - outputs: [{ internalType: "uint128", name: "", type: "uint128" }], - }, - { - inputs: [], - stateMutability: "pure", - type: "function", - name: "pureUint64", - outputs: [{ internalType: "uint64", name: "", type: "uint64" }], - }, - { - inputs: [], - stateMutability: "pure", - type: "function", - name: "pureUint32", - outputs: [{ internalType: "uint32", name: "", type: "uint32" }], - }, - { - inputs: [{ internalType: "uint256", name: "y", type: "uint256" }], - stateMutability: "nonpayable", - type: "constructor", - }, - { - inputs: [{ internalType: "uint256", name: "z", type: "uint256" }], - name: "exampleFunction", - outputs: [{ internalType: "bool", name: "", type: "bool" }], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "pureAddress", - outputs: [{ internalType: "address", name: "", type: "address" }], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "pureArray", - outputs: [{ internalType: "uint256[]", name: "", type: "uint256[]" }], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "pureBool", - outputs: [{ internalType: "bool", name: "", type: "bool" }], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "pureBytes", - outputs: [{ internalType: "bytes", name: "", type: "bytes" }], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "pureBytes1", - outputs: [{ internalType: "bytes1", name: "", type: "bytes1" }], - stateMutability: "pure", - type: "function", - }, - // ... (other pureBytes functions) - { - inputs: [], - name: "pureNestedStruct", - outputs: [ - { - components: [ - { internalType: "uint256", name: "a", type: "uint256" }, - { - components: [ - { - components: [{ internalType: "uint256[]", name: "a", type: "uint256[]" }], - internalType: "struct TestAllReadTypes.ArrayData", - name: "nestedArray", - type: "tuple", - }, - { internalType: "uint256", name: "a", type: "uint256" }, - ], - internalType: "struct TestAllReadTypes.NestedData", - name: "nestedFields", - type: "tuple", - }, - ], - internalType: "struct TestAllReadTypes.ExampleStruct", - name: "", - type: "tuple", - }, - ], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "pureString", - outputs: [{ internalType: "string", name: "", type: "string" }], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "pureTuple", - outputs: [ - { internalType: "uint256", name: "", type: "uint256" }, - { internalType: "uint256", name: "", type: "uint256" }, - ], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "pureTupleMixedTypes", - outputs: [ - { internalType: "uint256", name: "", type: "uint256" }, - { internalType: "address", name: "", type: "address" }, - { internalType: "bool", name: "", type: "bool" }, - ], - stateMutability: "pure", - type: "function", - }, - // ... (other pure functions) - { - inputs: [], - name: "returnFunction", - outputs: [ - { - internalType: "function (uint256) external returns (bool)", - name: "", - type: "function", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "viewUint", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "x", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ name: "x", type: "address" }], - name: "overload", - outputs: [{ name: "", type: "uint256" }], - stateMutability: "pure", - type: "function", - }, - { - inputs: [ - { name: "x", type: "uint256" }, - { name: "y", type: "uint256" }, - ], - name: "overload", - outputs: [{ name: "", type: "uint256[]" }], - stateMutability: "pure", - type: "function", - }, - { - inputs: [{ name: "x", type: "uint256" }], - name: "overload", - outputs: [{ name: "", type: "bool" }], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "overload", - outputs: [{ name: "", type: "uint256" }], - stateMutability: "pure", - type: "function", - }, - { - type: "function", - name: "pureNestedStruct", - inputs: [], - outputs: [ - { - name: "", - type: "tuple", - internalType: "struct TestAllReadTypes.ExampleStruct", - components: [ - { name: "a", type: "uint256", internalType: "uint256" }, - { - name: "nestedFields", - type: "tuple", - internalType: "struct TestAllReadTypes.NestedData", - components: [ - { - name: "nestedArray", - type: "tuple", - internalType: "struct TestAllReadTypes.ArrayData", - components: [{ name: "a", type: "uint256[]", internalType: "uint256[]" }], - }, - { name: "a", type: "uint256", internalType: "uint256" }, - ], - }, - ], - }, - ], - stateMutability: "pure", - }, - { - inputs: [ - { - internalType: "address", - name: "to", - type: "address", - }, - ], - name: "mint", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "from", - type: "address", - }, - { - internalType: "address", - name: "to", - type: "address", - }, - { - internalType: "uint256", - name: "tokenId", - type: "uint256", - }, - ], - name: "transferFrom", - outputs: [], - stateMutability: "payable", - type: "function", - }, -] as const; - - - +import { Coinbase, Wallet } from './src' +Coinbase.configureFromJson({ + filePath: "~/.apikeys/dev.json", + debugging: true, + basePath: "http://localhost:8002" +}); async function main() { // create a wallet From ebb837deab06db1adfbb4c4c3a56a3ef4b5f744d Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Thu, 6 Feb 2025 16:16:10 -0500 Subject: [PATCH 14/23] Fix --- test_smart_wallet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_smart_wallet.ts b/test_smart_wallet.ts index c38df770..f66eb2d4 100644 --- a/test_smart_wallet.ts +++ b/test_smart_wallet.ts @@ -35,7 +35,7 @@ async function main() { // I believe that SCW-Manager should automatically sponsor all base-sepolia user operations so we don't need to have additional funds for gas const userOperation = await smartWallet.sendUserOperation({ calls: [{ - to: '0xcb98643b8786950F0461f3B0edf99D88F274574D', + to: (await wallet.getDefaultAddress()).getId() as `0x${string}`, value: parseEther(halfBalance.toString()), data: '0x' } From 3813f94d3d8c2d75a61bffd4f5618248aed12b0b Mon Sep 17 00:00:00 2001 From: Alex Stone Date: Thu, 6 Feb 2025 16:56:06 -0800 Subject: [PATCH 15/23] Encode value as a string, wait for faucet tx, and use network --- src/coinbase/smart_wallet.ts | 6 +++--- test_smart_wallet.ts | 19 ++++++++++++------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/coinbase/smart_wallet.ts b/src/coinbase/smart_wallet.ts index ce1e7569..031030fa 100644 --- a/src/coinbase/smart_wallet.ts +++ b/src/coinbase/smart_wallet.ts @@ -43,7 +43,7 @@ export class SmartWallet { public async use({networkId}: {networkId: NetworkIdentifier}) { this.networkId = networkId; } - + public getAddress() { return this.model.address; } @@ -103,13 +103,13 @@ export class SmartWallet { args: call.args }), to: call.to, - value: call.value || '0x0' + value: call.value.toString() || '0' } } return { data: call.data, to: call.to, - value: call.value || '0x0' + value: call.value.toString() || '0' } }) diff --git a/test_smart_wallet.ts b/test_smart_wallet.ts index f66eb2d4..858314dd 100644 --- a/test_smart_wallet.ts +++ b/test_smart_wallet.ts @@ -1,5 +1,5 @@ import { parseEther } from 'viem' -import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' import { SmartWallet } from './src/coinbase/smart_wallet' import { Coinbase, Wallet } from './src' @@ -16,6 +16,8 @@ async function main() { // faucet it const faucet = await wallet.faucet(); + await faucet.wait(); + // create a smart wallet with viem wallet owner const privateKey = generatePrivateKey() const owner = privateKeyToAccount(privateKey) @@ -32,13 +34,16 @@ async function main() { }) await transfer.wait() + smartWallet.use({networkId: Coinbase.networks.BaseSepolia }) + // I believe that SCW-Manager should automatically sponsor all base-sepolia user operations so we don't need to have additional funds for gas const userOperation = await smartWallet.sendUserOperation({ - calls: [{ - to: (await wallet.getDefaultAddress()).getId() as `0x${string}`, - value: parseEther(halfBalance.toString()), - data: '0x' - } + calls: [ + { + to: (await wallet.getDefaultAddress()).getId() as `0x${string}`, + value: parseEther(halfBalance.toString()), + data: '0x' + } ] }) await userOperation.wait() @@ -46,4 +51,4 @@ async function main() { console.log(userOperation.getStatus()) } -main(); \ No newline at end of file +main(); From 4f37a8c9a5cdc8b1875562dd077a2e4e53f48e5e Mon Sep 17 00:00:00 2001 From: Alex Stone Date: Fri, 7 Feb 2025 07:21:07 -0800 Subject: [PATCH 16/23] chore: Stop wrapping signature client-side This stops wrapping the signature client-side as this is handled server-side during user operation orchestration. --- src/coinbase/smart_wallet.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/coinbase/smart_wallet.ts b/src/coinbase/smart_wallet.ts index 031030fa..271dca32 100644 --- a/src/coinbase/smart_wallet.ts +++ b/src/coinbase/smart_wallet.ts @@ -43,7 +43,7 @@ export class SmartWallet { public async use({networkId}: {networkId: NetworkIdentifier}) { this.networkId = networkId; } - + public getAddress() { return this.model.address; } @@ -130,13 +130,12 @@ export class SmartWallet { } const signature = await this.account.sign({ hash: createOpResponse.data.unsigned_payload as `0x${string}` }) - const wrappedSignature = this.wrapSignature({ ownerIndex: 0, signature }) const broadcastResponse = await Coinbase.apiClients.smartWallet!.broadcastUserOperation( this.getAddress(), createOpResponse.data.id, { - signature: wrappedSignature + signature, } ) From 89dc5543a7ed1b2ce834f821f6596d48aeb3c19e Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 7 Feb 2025 13:14:35 -0500 Subject: [PATCH 17/23] CLeanup --- src/coinbase/smart_wallet.ts | 41 +----------------------------------- 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/src/coinbase/smart_wallet.ts b/src/coinbase/smart_wallet.ts index 271dca32..b5aa4a33 100644 --- a/src/coinbase/smart_wallet.ts +++ b/src/coinbase/smart_wallet.ts @@ -7,7 +7,7 @@ import type { UserOperationCalls } from 'viem/account-abstraction'; -import { encodeAbiParameters, encodeFunctionData, encodePacked, LocalAccount, parseSignature, size } from "viem"; +import { encodeFunctionData, LocalAccount } from "viem"; import { UserOperation } from "./user_operation"; export class SmartWallet { @@ -48,45 +48,6 @@ export class SmartWallet { return this.model.address; } - private wrapSignature(parameters: { - ownerIndex?: number | undefined - signature: Hex - }) { - - const { ownerIndex = 0 } = parameters - const signatureData = (() => { - if (size(parameters.signature) !== 65) return parameters.signature - const signature = parseSignature(parameters.signature) - return encodePacked( - ['bytes32', 'bytes32', 'uint8'], - [signature.r, signature.s, signature.yParity === 0 ? 27 : 28], - ) - })() - return encodeAbiParameters( - [ - { - components: [ - { - name: 'ownerIndex', - type: 'uint8', - }, - { - name: 'signatureData', - type: 'bytes', - }, - ], - type: 'tuple', - }, - ], - [ - { - ownerIndex, - signatureData, - }, - ], - ) - } - public async sendUserOperation( params: { calls: UserOperationCalls } ): Promise { From d70c3cea6b35c22222dcb0b8341d5205f9842f39 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 7 Feb 2025 13:36:21 -0500 Subject: [PATCH 18/23] toLocalAccount --- src/coinbase/accounts/toLocalAccount.ts | 85 +++++++++++++++++++++++++ src/index.ts | 5 +- 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/coinbase/accounts/toLocalAccount.ts diff --git a/src/coinbase/accounts/toLocalAccount.ts b/src/coinbase/accounts/toLocalAccount.ts new file mode 100644 index 00000000..6e83f7f3 --- /dev/null +++ b/src/coinbase/accounts/toLocalAccount.ts @@ -0,0 +1,85 @@ +import { hashTypedData, keccak256, serializeTransaction } from "viem"; +import { toAccount } from "viem/accounts"; +import type { + HashTypedDataParameters, + Hex, LocalAccount, + SerializeTransactionFn, + SignableMessage, + TransactionSerializable, + TypedData +} from "viem"; +import { WalletAddress } from "../address/wallet_address"; +import { hashMessage } from "viem"; + +export function toLocalAccount(address: WalletAddress): LocalAccount { + + + return toAccount({ + address: address.getId() as `0x${string}`, + signMessage: function ({ + message, + }: { + message: SignableMessage; + }): Promise { + return signMessage(address, message); + }, + signTransaction: function < + TTransactionSerializable extends TransactionSerializable, + >( + transaction: TTransactionSerializable, + args?: + | { serializer?: SerializeTransactionFn } + | undefined, + ): Promise { + const serializer = !args?.serializer + ? serializeTransaction + : args.serializer; + + return signTransaction( + address, + transaction, + serializer, + ); + }, + signTypedData: function ( + typedData: TypedData | { [key: string]: unknown }, + ): Promise { + return signTypedData(address, typedData); + }, + }); + + +} + +export async function signMessage( + address: WalletAddress, + message: SignableMessage +): Promise { + const hashedMessage = hashMessage(message); + const signedMessage = await address.createPayloadSignature( + hashedMessage + ); + return `${signedMessage}` as Hex; +} + +export async function signTransaction< + TTransactionSerializable extends TransactionSerializable, +>( + address: WalletAddress, + transaction: TTransactionSerializable, + serializer: SerializeTransactionFn, +): Promise { + const serializedTx = serializer(transaction); + const transactionHash = keccak256(serializedTx); + const payload = await address.createPayloadSignature(transactionHash); + return `${payload}` as Hex; +} + +export async function signTypedData( + address: WalletAddress, + data: TypedData | { [key: string]: unknown }, +): Promise { + const hashToSign = hashTypedData(data as HashTypedDataParameters); + const payload = await address.createPayloadSignature(hashToSign); + return `${payload}` as Hex; +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 3be357e2..5fc0487e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,4 +33,7 @@ export * from "./coinbase/fiat_amount"; export * from "./coinbase/fund_operation"; export * from "./coinbase/fund_quote"; export * from "./coinbase/smart_wallet"; -export * from "./coinbase/user_operation"; \ No newline at end of file +export * from "./coinbase/user_operation"; +export { + toLocalAccount, +} from './coinbase/accounts/toLocalAccount' \ No newline at end of file From 2fe9b8291bee351ca421c175418ac34385813992 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 7 Feb 2025 14:09:46 -0500 Subject: [PATCH 19/23] Changes --- src/coinbase/accounts/toLocalAccount.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/coinbase/accounts/toLocalAccount.ts b/src/coinbase/accounts/toLocalAccount.ts index 6e83f7f3..f34d788b 100644 --- a/src/coinbase/accounts/toLocalAccount.ts +++ b/src/coinbase/accounts/toLocalAccount.ts @@ -56,10 +56,10 @@ export async function signMessage( message: SignableMessage ): Promise { const hashedMessage = hashMessage(message); - const signedMessage = await address.createPayloadSignature( + const payloadSignature = await address.createPayloadSignature( hashedMessage ); - return `${signedMessage}` as Hex; + return `${payloadSignature.getSignature()}` as Hex; } export async function signTransaction< @@ -80,6 +80,6 @@ export async function signTypedData( data: TypedData | { [key: string]: unknown }, ): Promise { const hashToSign = hashTypedData(data as HashTypedDataParameters); - const payload = await address.createPayloadSignature(hashToSign); - return `${payload}` as Hex; -} \ No newline at end of file + const payloadSignature = await address.createPayloadSignature(hashToSign); + return `${payloadSignature.getSignature()}` as Hex; +} From cebedc91687fdd4d173b7b1db8f79a564abdd02a Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 7 Feb 2025 14:10:47 -0500 Subject: [PATCH 20/23] test --- test_viem_account.ts | 59 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 test_viem_account.ts diff --git a/test_viem_account.ts b/test_viem_account.ts new file mode 100644 index 00000000..32a46dc4 --- /dev/null +++ b/test_viem_account.ts @@ -0,0 +1,59 @@ +import { Coinbase, hashMessage, hashTypedDataMessage, PayloadSignature, toLocalAccount, Wallet } from './src'; + +Coinbase.configureFromJson({ + filePath: "~/.apikeys/prod.json", +}); + +async function main() { + const wallet = await Wallet.create(); + const walletAddress = await wallet.getDefaultAddress(); + + const localAccount = toLocalAccount(walletAddress); + const msg = "Hey Team" + const signature = await localAccount.signMessage({message: msg}); + console.log(signature); + + // sign with normal wallet address way + const hashedMsg = hashMessage(msg); + const signature2 = await walletAddress.createPayloadSignature(hashedMsg); + console.log(signature2.getSignature()); + + const domain = { + name: "MyDapp", + version: "1", + chainId: 1, + verifyingContract: walletAddress.getId() as `0x${string}`, + } as const; + + const types = { + MyType: [ + { name: "sender", type: "address" }, + { name: "amount", type: "uint256" }, + ], + }; + + const typedDataMessage = { + sender: walletAddress.getId(), + amount: 1000, + }; + + const signatureTypedData = await localAccount.signTypedData({ + domain, + types, + primaryType: "MyType", + message: typedDataMessage, + }); + console.log(signatureTypedData); + + const hashedTypedData = hashTypedDataMessage( + domain, + types, + typedDataMessage, + ); + + let payloadSignature: PayloadSignature = await walletAddress.createPayloadSignature(hashedTypedData); + payloadSignature = await payloadSignature.wait(); + console.log(payloadSignature.getSignature()); +} + +main(); From a95a10b0c1fb2c8aac628b943c4f08e1b1bce8f8 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 7 Feb 2025 14:11:40 -0500 Subject: [PATCH 21/23] Fix --- src/coinbase/accounts/toLocalAccount.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/coinbase/accounts/toLocalAccount.ts b/src/coinbase/accounts/toLocalAccount.ts index f34d788b..a335d2ef 100644 --- a/src/coinbase/accounts/toLocalAccount.ts +++ b/src/coinbase/accounts/toLocalAccount.ts @@ -12,8 +12,6 @@ import { WalletAddress } from "../address/wallet_address"; import { hashMessage } from "viem"; export function toLocalAccount(address: WalletAddress): LocalAccount { - - return toAccount({ address: address.getId() as `0x${string}`, signMessage: function ({ From e472398052ef63a7fd89a5db63ffe100cf8d9569 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 7 Feb 2025 14:36:40 -0500 Subject: [PATCH 22/23] Rename file --- .gitignore | 1 - .../accounts/{toLocalAccount.ts => to_local_account.ts} | 0 src/index.ts | 2 +- 3 files changed, 1 insertion(+), 2 deletions(-) rename src/coinbase/accounts/{toLocalAccount.ts => to_local_account.ts} (100%) diff --git a/.gitignore b/.gitignore index bea3c097..08803af8 100644 --- a/.gitignore +++ b/.gitignore @@ -132,7 +132,6 @@ dist .yarn/install-state.gz .pnp.* .DS_Store -**/*_local* .idea diff --git a/src/coinbase/accounts/toLocalAccount.ts b/src/coinbase/accounts/to_local_account.ts similarity index 100% rename from src/coinbase/accounts/toLocalAccount.ts rename to src/coinbase/accounts/to_local_account.ts diff --git a/src/index.ts b/src/index.ts index 5fc0487e..2ec7f652 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,4 +36,4 @@ export * from "./coinbase/smart_wallet"; export * from "./coinbase/user_operation"; export { toLocalAccount, -} from './coinbase/accounts/toLocalAccount' \ No newline at end of file +} from './coinbase/accounts/to_local_account' \ No newline at end of file From 6bc30ac131ab9138129a2ebdeb7e080c9d7284bb Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Sun, 9 Feb 2025 10:27:02 -0500 Subject: [PATCH 23/23] Changes --- test_smart_wallet.ts | 21 +++++++++++++++++++-- test_viem_account.ts | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/test_smart_wallet.ts b/test_smart_wallet.ts index 858314dd..0c8488ae 100644 --- a/test_smart_wallet.ts +++ b/test_smart_wallet.ts @@ -1,7 +1,8 @@ -import { parseEther } from 'viem' +import { createWalletClient, http, parseEther } from 'viem' import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' import { SmartWallet } from './src/coinbase/smart_wallet' import { Coinbase, Wallet } from './src' +import { mainnet } from 'viem/chains'; Coinbase.configureFromJson({ filePath: "~/.apikeys/dev.json", @@ -23,6 +24,12 @@ async function main() { const owner = privateKeyToAccount(privateKey) const smartWallet = await SmartWallet.create({account: owner}) + // const client = createWalletClient({ + // chain: mainnet + // transport: http() + // }) + // client.getAddresses() + // send ETH from wallet to smart wallet so the smart wallet has funds to send back const currentBalance = await wallet.getBalance("eth") const halfBalance = currentBalance.div(2) @@ -43,7 +50,17 @@ async function main() { to: (await wallet.getDefaultAddress()).getId() as `0x${string}`, value: parseEther(halfBalance.toString()), data: '0x' - } + }, + // { + // to: (await wallet.getDefaultAddress()).getId() as `0x${string}`, + // abi: myAbi, + // functionName: "transfer", + // args: [ + // (await wallet.getDefaultAddress()).getId() as `0x${string}`, + // parseEther(halfBalance.toString()) + // ], + // value: parseEther(halfBalance.toString()) + // } ] }) await userOperation.wait() diff --git a/test_viem_account.ts b/test_viem_account.ts index 32a46dc4..c25a2876 100644 --- a/test_viem_account.ts +++ b/test_viem_account.ts @@ -1,9 +1,26 @@ +import { createWalletClient, http, parseEther } from 'viem'; import { Coinbase, hashMessage, hashTypedDataMessage, PayloadSignature, toLocalAccount, Wallet } from './src'; +import { mainnet } from 'viem/chains'; Coinbase.configureFromJson({ filePath: "~/.apikeys/prod.json", }); +const myAbi = [ + { + inputs: [ + { + name: "amount", + type: "uint256", + }, + ], + name: "transfer", + outputs: [], + stateMutability: "nonpayable", + type: "function", + } +] as const; + async function main() { const wallet = await Wallet.create(); const walletAddress = await wallet.getDefaultAddress(); @@ -13,6 +30,23 @@ async function main() { const signature = await localAccount.signMessage({message: msg}); console.log(signature); + const walletClient = await createWalletClient({ + account: localAccount, + chain: mainnet, + transport: http(), + }); + + walletClient.writeContract({ + address: walletAddress.getId() as `0x${string}`, + abi: myAbi, + functionName: "transfer", + args: [123n] + }) + + + + // sign with normal wallet address way + const hashedMsg = hashMessage(msg); // sign with normal wallet address way const hashedMsg = hashMessage(msg); const signature2 = await walletAddress.createPayloadSignature(hashedMsg);