diff --git a/packages/accounts/src/kernel-zerodev/paymaster/base.ts b/packages/accounts/src/kernel-zerodev/paymaster/base.ts index 85c542ee..a0a0b811 100644 --- a/packages/accounts/src/kernel-zerodev/paymaster/base.ts +++ b/packages/accounts/src/kernel-zerodev/paymaster/base.ts @@ -10,7 +10,8 @@ import { ENTRYPOINT_ADDRESS } from "../constants.js"; import type { ZeroDevProvider } from "../provider.js"; import { hexifyUserOp } from "../utils/ERC4337-utils.js"; import type { PaymasterAndBundlerProviders } from "./types.js"; -import fetchAdapter from '@vespaiach/axios-fetch-adapter'; +// @ts-ignore +import fetchAdapter from '../utils/fetchAdapter.js'; if (typeof fetch === 'function') { axios.defaults.adapter = fetchAdapter diff --git a/packages/accounts/src/kernel-zerodev/utils/fetchAdapter.js b/packages/accounts/src/kernel-zerodev/utils/fetchAdapter.js new file mode 100644 index 00000000..d171abc3 --- /dev/null +++ b/packages/accounts/src/kernel-zerodev/utils/fetchAdapter.js @@ -0,0 +1,215 @@ +import axios from 'axios'; +import settle from 'axios/lib/core/settle'; +import buildURL from 'axios/lib/helpers/buildURL'; +import buildFullPath from 'axios/lib/core/buildFullPath'; +import { isUndefined, isStandardBrowserEnv, isFormData } from 'axios/lib/utils'; + +/** + * - Create a request object + * - Get response body + * - Check if timeout + */ +export default async function fetchAdapter(config) { + const request = createRequest(config); + const promiseChain = [getResponse(request, config)]; + + if (config.timeout && config.timeout > 0) { + promiseChain.push( + new Promise((res) => { + setTimeout(() => { + const message = config.timeoutErrorMessage + ? config.timeoutErrorMessage + : 'timeout of ' + config.timeout + 'ms exceeded'; + res(createError(message, config, 'ECONNABORTED', request)); + }, config.timeout); + }) + ); + } + + const data = await Promise.race(promiseChain); + return new Promise((resolve, reject) => { + if (data instanceof Error) { + reject(data); + } else { + Object.prototype.toString.call(config.settle) === '[object Function]' + ? config.settle(resolve, reject, data) + : settle(resolve, reject, data); + } + }); +} + + +/** + * Fetch API stage two is to get response body. This funtion tries to retrieve + * response body based on response's type + */ +async function getResponse(request, config) { + let stageOne; + try { + stageOne = await fetch(request); + } catch (e) { + return createError('Network Error', config, 'ERR_NETWORK', request); + } + + const response = { + ok: stageOne.ok, + status: stageOne.status, + statusText: stageOne.statusText, + headers: new Headers(stageOne.headers), // Make a copy of headers + config: config, + request, + }; + + if (stageOne.status >= 200 && stageOne.status !== 204) { + switch (config.responseType) { + case 'arraybuffer': + response.data = await stageOne.arrayBuffer(); + break; + case 'blob': + response.data = await stageOne.blob(); + break; + case 'json': + response.data = await stageOne.json(); + break; + case 'formData': + response.data = await stageOne.formData(); + break; + default: + response.data = await stageOne.text(); + break; + } + } + + return response; +} + +/** + * This function will create a Request object based on configuration's axios + */ +function createRequest(config) { + const headers = new Headers(config.headers); + + // HTTP basic authentication + if (config.auth) { + const username = config.auth.username || ''; + const password = config.auth.password ? decodeURI(encodeURIComponent(config.auth.password)) : ''; + headers.set('Authorization', `Basic ${btoa(username + ':' + password)}`); + } + + const method = config.method.toUpperCase(); + const options = { + headers: headers, + method, + }; + if (method !== 'GET' && method !== 'HEAD') { + options.body = config.data; + + // In these cases the browser will automatically set the correct Content-Type, + // but only if that header hasn't been set yet. So that's why we're deleting it. + if (isFormData(options.body) && isStandardBrowserEnv()) { + headers.delete('Content-Type'); + } + } + if (config.mode) { + options.mode = config.mode; + } + if (config.cache) { + options.cache = config.cache; + } + if (config.integrity) { + options.integrity = config.integrity; + } + if (config.redirect) { + options.redirect = config.redirect; + } + if (config.referrer) { + options.referrer = config.referrer; + } + // This config is similar to XHR’s withCredentials flag, but with three available values instead of two. + // So if withCredentials is not set, default value 'same-origin' will be used + if (!isUndefined(config.withCredentials)) { + options.credentials = config.withCredentials ? 'include' : 'omit'; + } + + const fullPath = buildFullPath(config.baseURL, config.url); + const url = buildURL(fullPath, config.params, config.paramsSerializer); + + // Expected browser to throw error if there is any wrong configuration value + return new Request(url, options); +} + + + +/** + * Note: + * + * From version >= 0.27.0, createError function is replaced by AxiosError class. + * So I copy the old createError function here for backward compatible. + * + * + * + * Create an Error with the specified message, config, error code, request and response. + * + * @param {string} message The error message. + * @param {Object} config The config. + * @param {string} [code] The error code (for example, 'ECONNABORTED'). + * @param {Object} [request] The request. + * @param {Object} [response] The response. + * @returns {Error} The created error. + */ +function createError(message, config, code, request, response) { + if (axios.AxiosError && typeof axios.AxiosError === 'function') { + return new axios.AxiosError(message, axios.AxiosError[code], config, request, response); + } + + var error = new Error(message); + return enhanceError(error, config, code, request, response); +}; + +/** + * + * Note: + * + * This function is for backward compatible. + * + * + * Update an Error with the specified config, error code, and response. + * + * @param {Error} error The error to update. + * @param {Object} config The config. + * @param {string} [code] The error code (for example, 'ECONNABORTED'). + * @param {Object} [request] The request. + * @param {Object} [response] The response. + * @returns {Error} The error. + */ +function enhanceError(error, config, code, request, response) { + error.config = config; + if (code) { + error.code = code; + } + + error.request = request; + error.response = response; + error.isAxiosError = true; + + error.toJSON = function toJSON() { + return { + // Standard + message: this.message, + name: this.name, + // Microsoft + description: this.description, + number: this.number, + // Mozilla + fileName: this.fileName, + lineNumber: this.lineNumber, + columnNumber: this.columnNumber, + stack: this.stack, + // Axios + config: this.config, + code: this.code, + status: this.response && this.response.status ? this.response.status : null + }; + }; + return error; +}; \ No newline at end of file