From 4cf79255cd6b284b8495a23615acb857d77e6545 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Thu, 20 Jun 2024 19:16:19 +0200 Subject: [PATCH 01/23] implemented starting smart account api --- smart_account_api/.nvmrc | 1 + smart_account_api/package.json | 27 + smart_account_api/src/index.ts | 138 ++++ smart_account_api/tsconfig.json | 109 +++ smart_account_api/yarn.lock | 1318 +++++++++++++++++++++++++++++++ 5 files changed, 1593 insertions(+) create mode 100644 smart_account_api/.nvmrc create mode 100644 smart_account_api/package.json create mode 100644 smart_account_api/src/index.ts create mode 100644 smart_account_api/tsconfig.json create mode 100644 smart_account_api/yarn.lock diff --git a/smart_account_api/.nvmrc b/smart_account_api/.nvmrc new file mode 100644 index 0000000..0fdd238 --- /dev/null +++ b/smart_account_api/.nvmrc @@ -0,0 +1 @@ +v20.10 \ No newline at end of file diff --git a/smart_account_api/package.json b/smart_account_api/package.json new file mode 100644 index 0000000..4a06403 --- /dev/null +++ b/smart_account_api/package.json @@ -0,0 +1,27 @@ +{ + "name": "smart-account-api", + "version": "1.0.0", + "main": "main.js", + "license": "MIT", + "scripts": { + "build": "npx tsc", + "start": "node dist/index.js", + "dev": "nodemon src/index.ts" + }, + "dependencies": { + "@biconomy/account": "^4.4.6", + "dotenv": "^16.4.5", + "entry-point-gas-estimations": "^0.0.19", + "ethers": "^6.13.1", + "express": "^4.19.2", + "viem": "^2.15.1" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^20.14.6", + "nodemon": "^3.1.4", + "ts-node": "^10.9.2", + "tsc": "^2.0.4", + "typescript": "^5.4.5" + } +} diff --git a/smart_account_api/src/index.ts b/smart_account_api/src/index.ts new file mode 100644 index 0000000..33d772f --- /dev/null +++ b/smart_account_api/src/index.ts @@ -0,0 +1,138 @@ +import express, { Express, Request, Response } from "express"; +import dotenv from "dotenv"; +import { + Account, + Address, + Hex, + WalletClient, + createWalletClient, + http, +} from "viem"; +import { polygon, sepolia } from "viem/chains"; +import { BiconomySmartAccountV2, createSmartAccountClient, Transaction } from "@biconomy/account"; +import { createGasEstimator } from "entry-point-gas-estimations"; +import { GasEstimator } from "entry-point-gas-estimations/dist/gas-estimator/entry-point-v6/GasEstimator/GasEstimator"; + +dotenv.config(); + +const bundlerUrl = process.env.BUNDLER_URL || "http://localhost:3001"; + +const app: Express = express(); +const port = process.env.PORT || 3000; + +app.listen(port, () => { + console.log(`[server]: Server is running at http://localhost:${port}`); +}); + +function getNetwork(chainId: string): { + bundlerUrl: string; + rpcUrl: string; + gasEstimator: GasEstimator; + chain: any; +} { + switch (chainId) { + case "137": + return { + bundlerUrl, + rpcUrl: polygonRpcUrl, + gasEstimator: createGasEstimator({ + rpcUrl: polygonRpcUrl, + }), + chain: polygon, + }; + case "31337": + return { + bundlerUrl, + rpcUrl: sepoliaRpcUrl, + gasEstimator: createGasEstimator({ + rpcUrl: sepoliaRpcUrl, + }), + chain: sepolia, + }; + default: + throw new Error("Invalid network"); + } +} + +type CustomTransactionType = "send" | "approve" | "swap"; +type CustomTransaction = { + type: CustomTransactionType; + summary: string; + params: { + from: string; + to: string; + value: string; + data: string; + }; +}; + +async function initAA(owner: Account | Address, chainId: string): Promise<{ + client: WalletClient; + smartAccount: BiconomySmartAccountV2; +}> { + const { chain, bundlerUrl } = getNetwork(chainId); + + const client = createWalletClient({ + account: owner, + chain: chain, + transport: http(), + }); + + const smartAccount = await createSmartAccountClient({ + signer: client, + bundlerUrl, + }); + + return { + client, + smartAccount, + } +}; + +app.get("/api/v1/account/address", async (req: Request, res: Response) => { + const owner: Address = req.query.owner as Address; + const chainId = req.query.chainId as string; + + const { smartAccount } = await initAA(owner, chainId); + + const smartAccountAddress = await smartAccount.getAccountAddress(); + + const result = smartAccountAddress; + + res.json(result); +}); + +app.post("/api/v1/account/deploy", async (req: Request, res: Response) => { + const owner: Address = req.query.owner as Address; + const chainId = req.query.chainId as string; + + const { smartAccount } = await initAA(owner, chainId); + + const response = await smartAccount.deploy(); + + const receipt = await response.waitForTxHash(); + + res.json(receipt.transactionHash); +}); + +app.post("/api/v1/account/transactions", async (req: Request, res: Response) => { + const owner: Address = req.query.owner as Address; + const chainId = req.query.chainId as string; + + const transactions: CustomTransaction[] = req.body.transactions; + + const { smartAccount } = await initAA(owner, chainId); + + const txs: Transaction[] = transactions.map((tx) => { + return { + to: tx.params.to, + value: tx.params.value, + data: tx.params.data, + }; + }); + + const response = await smartAccount.sendTransaction(txs); + const receipt = await response.waitForTxHash(); + + res.json(receipt.transactionHash); +}); diff --git a/smart_account_api/tsconfig.json b/smart_account_api/tsconfig.json new file mode 100644 index 0000000..e075f97 --- /dev/null +++ b/smart_account_api/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/smart_account_api/yarn.lock b/smart_account_api/yarn.lock new file mode 100644 index 0000000..97e8c3c --- /dev/null +++ b/smart_account_api/yarn.lock @@ -0,0 +1,1318 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@adraffy/ens-normalize@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" + integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== + +"@adraffy/ens-normalize@1.10.1": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" + integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== + +"@biconomy/account@^4.4.6": + version "4.4.6" + resolved "https://registry.yarnpkg.com/@biconomy/account/-/account-4.4.6.tgz#ecb1c0cd088e5c1395688c0dd9d2e81bcd3c8c20" + integrity sha512-ipCisN2+qs7ma1gyJwkWaeuF3B7g497ZP+PKJAzA2VjFCaxxz5fYQzYckIGiVv1eiKjSb+7bM7VQ76UU0d/uCg== + dependencies: + merkletreejs "^0.3.11" + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@ethereumjs/rlp@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" + integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== + +"@ethereumjs/util@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.1.0.tgz#299df97fb6b034e0577ce9f94c7d9d1004409ed4" + integrity sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA== + dependencies: + "@ethereumjs/rlp" "^4.0.1" + ethereum-cryptography "^2.0.0" + micro-ftch "^0.3.1" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@microsoft/tsdoc-config@0.16.2": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc-config/-/tsdoc-config-0.16.2.tgz#b786bb4ead00d54f53839a458ce626c8548d3adf" + integrity sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw== + dependencies: + "@microsoft/tsdoc" "0.14.2" + ajv "~6.12.6" + jju "~1.4.0" + resolve "~1.19.0" + +"@microsoft/tsdoc@0.14.2": + version "0.14.2" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz#c3ec604a0b54b9a9b87e9735dfc59e1a5da6a5fb" + integrity sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug== + +"@noble/curves@1.2.0", "@noble/curves@~1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + +"@noble/curves@1.4.0", "@noble/curves@~1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.0.tgz#f05771ef64da724997f69ee1261b2417a49522d6" + integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg== + dependencies: + "@noble/hashes" "1.4.0" + +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + +"@noble/hashes@1.4.0", "@noble/hashes@^1.4.0", "@noble/hashes@~1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + +"@noble/hashes@~1.3.0", "@noble/hashes@~1.3.2": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" + integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== + +"@scure/base@~1.1.0", "@scure/base@~1.1.2", "@scure/base@~1.1.6": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.7.tgz#fe973311a5c6267846aa131bc72e96c5d40d2b30" + integrity sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g== + +"@scure/bip32@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.2.tgz#90e78c027d5e30f0b22c1f8d50ff12f3fb7559f8" + integrity sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA== + dependencies: + "@noble/curves" "~1.2.0" + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.2" + +"@scure/bip32@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.4.0.tgz#4e1f1e196abedcef395b33b9674a042524e20d67" + integrity sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg== + dependencies: + "@noble/curves" "~1.4.0" + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + +"@scure/bip39@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a" + integrity sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg== + dependencies: + "@noble/hashes" "~1.3.0" + "@scure/base" "~1.1.0" + +"@scure/bip39@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.3.0.tgz#0f258c16823ddd00739461ac31398b4e7d6a18c3" + integrity sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ== + dependencies: + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/body-parser@*": + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^4.17.33": + version "4.19.5" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6" + integrity sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^4.17.21": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/node@*", "@types/node@^20.10.5", "@types/node@^20.14.6": + version "20.14.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.6.tgz#f3c19ffc98c2220e18de259bb172dd4d892a6075" + integrity sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw== + dependencies: + undici-types "~5.26.4" + +"@types/node@18.15.13": + version "18.15.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" + integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== + +"@types/qs@*": + version "6.9.15" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" + integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.7" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" + integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + +abitype@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.0.tgz#237176dace81d90d018bebf3a45cb42f2a2d9e97" + integrity sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ== + +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-walk@^8.1.1: + version "8.3.3" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e" + integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1: + version "8.12.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c" + integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw== + +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + +ajv@~6.12.6: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-sequence-parser@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz#e0aa1cdcbc8f8bb0b5bca625aac41f5f056973cf" + integrity sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +bignumber.js@^9.0.1: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +bn.js@4.11.6: + version "4.11.6" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" + integrity sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA== + +bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +body-parser@1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +buffer-reverse@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-reverse/-/buffer-reverse-1.0.1.tgz#49283c8efa6f901bc01fa3304d06027971ae2f60" + integrity sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +chokidar@^3.5.2: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +crypto-js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dotenv@^16.4.5: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +entry-point-gas-estimations@^0.0.19: + version "0.0.19" + resolved "https://registry.yarnpkg.com/entry-point-gas-estimations/-/entry-point-gas-estimations-0.0.19.tgz#6864fa483a929f7ba5f6dd6d871fe30e1a9b0f2e" + integrity sha512-8SKE9w/lvYxny3oeC+Rx4mCaMeSjZIyRHDdnXlr7hQZmSVNcBtZJOaFIelBDIgGLl9aGqdkBp7wh0wxXBr/13A== + dependencies: + "@types/node" "^20.10.5" + eslint-plugin-tsdoc "^0.2.17" + husky "^8.0.3" + typedoc "^0.25.7" + typescript "^5.3.3" + viem "^2.7.8" + zod "^3.22.4" + zod-validation-error "^2.1.0" + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +eslint-plugin-tsdoc@^0.2.17: + version "0.2.17" + resolved "https://registry.yarnpkg.com/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.2.17.tgz#27789495bbd8778abbf92db1707fec2ed3dfe281" + integrity sha512-xRmVi7Zx44lOBuYqG8vzTXuL6IdGOeF9nHX17bjJ8+VE6fsxpdGem0/SBTmAwgYMKYB1WBkqRJVQ+n8GK041pA== + dependencies: + "@microsoft/tsdoc" "0.14.2" + "@microsoft/tsdoc-config" "0.16.2" + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +ethereum-bloom-filters@^1.0.6: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.1.0.tgz#b3fc1eb789509ee30db0bf99a2988ccacb8d0397" + integrity sha512-J1gDRkLpuGNvWYzWslBQR9cDV4nd4kfvVTE/Wy4Kkm4yb3EYRSlyi0eB/inTsSTTVyA0+HyzHgbr95Fn/Z1fSw== + dependencies: + "@noble/hashes" "^1.4.0" + +ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.2.0.tgz#06e2d9c0d89f98ffc6a83818f55bf85afecd50dc" + integrity sha512-hsm9JhfytIf8QME/3B7j4bc8V+VdTU+Vas1aJlvIS96ffoNAosudXvGoEvWmc7QZYdkC8mrMJz9r0fcbw7GyCA== + dependencies: + "@noble/curves" "1.4.0" + "@noble/hashes" "1.4.0" + "@scure/bip32" "1.4.0" + "@scure/bip39" "1.3.0" + +ethers@^6.13.1: + version "6.13.1" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.13.1.tgz#2b9f9c7455cde9d38b30fe6589972eb083652961" + integrity sha512-hdJ2HOxg/xx97Lm9HdCWk949BfYqYWpyw4//78SiwOLgASyfrNszfMUNB2joKjvGUdwhHfaiMMFFwacVVoLR9A== + dependencies: + "@adraffy/ens-normalize" "1.10.1" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@types/node" "18.15.13" + aes-js "4.0.0-beta.5" + tslib "2.4.0" + ws "8.17.1" + +ethjs-unit@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" + integrity sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw== + dependencies: + bn.js "4.11.6" + number-to-bn "1.7.0" + +express@^4.19.2: + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.2" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.6.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +hasown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +husky@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" + integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.1.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-hex-prefixed@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" + integrity sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +isows@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.4.tgz#810cd0d90cc4995c26395d2aa4cfa4037ebdf061" + integrity sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ== + +jju@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" + integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +jsonc-parser@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a" + integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== + +lunr@^2.3.9: + version "2.3.9" + resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" + integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +marked@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" + integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +merkletreejs@^0.3.11: + version "0.3.11" + resolved "https://registry.yarnpkg.com/merkletreejs/-/merkletreejs-0.3.11.tgz#e0de05c3ca1fd368de05a12cb8efb954ef6fc04f" + integrity sha512-LJKTl4iVNTndhL+3Uz/tfkjD0klIWsHlUzgtuNnNrsf7bAlXR30m+xYB7lHr5Z/l6e/yAIsr26Dabx6Buo4VGQ== + dependencies: + bignumber.js "^9.0.1" + buffer-reverse "^1.0.1" + crypto-js "^4.2.0" + treeify "^1.1.0" + web3-utils "^1.3.4" + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micro-ftch@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/micro-ftch/-/micro-ftch-0.3.1.tgz#6cb83388de4c1f279a034fb0cf96dfc050853c5f" + integrity sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^9.0.3: + version "9.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" + integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== + dependencies: + brace-expansion "^2.0.1" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +nodemon@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.4.tgz#c34dcd8eb46a05723ccde60cbdd25addcc8725e4" + integrity sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ== + dependencies: + chokidar "^3.5.2" + debug "^4" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^7.5.3" + simple-update-notifier "^2.0.0" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +number-to-bn@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" + integrity sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig== + dependencies: + bn.js "4.11.6" + strip-hex-prefix "1.0.0" + +object-inspect@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-parse@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +resolve@~1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.0" + path-parse "^1.0.6" + +safe-buffer@5.2.1, safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^7.5.3: + version "7.6.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shiki@^0.14.7: + version "0.14.7" + resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.7.tgz#c3c9e1853e9737845f1d2ef81b31bcfb07056d4e" + integrity sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg== + dependencies: + ansi-sequence-parser "^1.1.0" + jsonc-parser "^3.2.0" + vscode-oniguruma "^1.7.0" + vscode-textmate "^8.0.0" + +side-channel@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +simple-update-notifier@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" + integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== + dependencies: + semver "^7.5.3" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +strip-hex-prefix@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" + integrity sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A== + dependencies: + is-hex-prefixed "1.0.0" + +supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +touch@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" + integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== + +treeify@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" + integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tsc@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/tsc/-/tsc-2.0.4.tgz#5f6499146abea5dca4420b451fa4f2f9345238f5" + integrity sha512-fzoSieZI5KKJVBYGvwbVZs/J5za84f2lSTLPYf6AGiIf43tZ3GNrI1QzTLcjtyDDP4aLxd46RTZq1nQxe7+k5Q== + +tslib@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typedoc@^0.25.7: + version "0.25.13" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.25.13.tgz#9a98819e3b2d155a6d78589b46fa4c03768f0922" + integrity sha512-pQqiwiJ+Z4pigfOnnysObszLiU3mVLWAExSPf+Mu06G/qsc3wzbuM56SZQvONhHLncLUhYzOVkjFFpFfL5AzhQ== + dependencies: + lunr "^2.3.9" + marked "^4.3.0" + minimatch "^9.0.3" + shiki "^0.14.7" + +typescript@^5.3.3, typescript@^5.4.5: + version "5.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" + integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== + +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +utf8@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" + integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +viem@^2.15.1, viem@^2.7.8: + version "2.15.1" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.15.1.tgz#05a9ef5fd74661bd77d865c334477a900e59b436" + integrity sha512-Vrveen3vDOJyPf8Q8TDyWePG2pTdK6IpSi4P6qlvAP+rXkAeqRvwYBy9AmGm+BeYpCETAyTT0SrCP6458XSt+w== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@scure/bip32" "1.3.2" + "@scure/bip39" "1.2.1" + abitype "1.0.0" + isows "1.0.4" + ws "8.17.1" + +vscode-oniguruma@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" + integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== + +vscode-textmate@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz#2c7a3b1163ef0441097e0b5d6389cd5504b59e5d" + integrity sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg== + +web3-utils@^1.3.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.4.tgz#0daee7d6841641655d8b3726baf33b08eda1cbec" + integrity sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A== + dependencies: + "@ethereumjs/util" "^8.1.0" + bn.js "^5.2.1" + ethereum-bloom-filters "^1.0.6" + ethereum-cryptography "^2.1.2" + ethjs-unit "0.1.6" + number-to-bn "1.7.0" + randombytes "^2.1.0" + utf8 "3.0.0" + +ws@8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +zod-validation-error@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-2.1.0.tgz#208eac75237dfed47c0018d2fe8fd03501bfc9ac" + integrity sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ== + +zod@^3.22.4: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== From 3b66585de7c9c35fd9286e6e73099677d5878609 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Thu, 20 Jun 2024 19:40:06 +0200 Subject: [PATCH 02/23] removed the reliance on manager.balance_of --- .../token/test_tokens_regression.py | 12 +++--- .../agents/token/research/test_advanced.py | 22 ++++++----- .../token/research/test_research_and_swap.py | 16 ++++---- .../research/test_research_swap_and_send.py | 13 ++++--- autotx/tests/agents/token/test_swap.py | 37 ++++++++++--------- .../tests/agents/token/test_swap_and_send.py | 16 ++++---- autotx/tests/integration/test_swap.py | 30 ++++++++------- 7 files changed, 77 insertions(+), 69 deletions(-) diff --git a/autotx/tests/agents/regression/token/test_tokens_regression.py b/autotx/tests/agents/regression/token/test_tokens_regression.py index d000b8e..f62a210 100644 --- a/autotx/tests/agents/regression/token/test_tokens_regression.py +++ b/autotx/tests/agents/regression/token/test_tokens_regression.py @@ -60,11 +60,11 @@ def test_auto_tx_swap(configuration, auto_tx): ] for prompt in prompts: - balance = manager.balance_of(usdc_address) + balance = get_erc20_balance(client.w3, usdc_address, manager.address) auto_tx.run(prompt, non_interactive=True) - new_balance = manager.balance_of(usdc_address) + new_balance = get_erc20_balance(client.w3, usdc_address, manager.address) try: assert balance + 100 == new_balance @@ -137,14 +137,14 @@ def test_auto_tx_swap_and_send(configuration, auto_tx, test_accounts): ] for prompt in prompts: - wbtc_safe_address = manager.balance_of(wbtc_address) - usdc_safe_address = manager.balance_of(usdc_address) + wbtc_safe_address = get_erc20_balance(client.w3, wbtc_address, manager.address) + usdc_safe_address = get_erc20_balance(client.w3, usdc_address, manager.address) receiver_usdc_balance = get_erc20_balance(client.w3, usdc_address, receiver) auto_tx.run(prompt, non_interactive=True) - new_wbtc_safe_address = manager.balance_of(wbtc_address) - new_usdc_safe_address = manager.balance_of(usdc_address) + new_wbtc_safe_address = get_erc20_balance(client.w3, wbtc_address, manager.address) + new_usdc_safe_address = get_erc20_balance(client.w3, usdc_address, manager.address) new_receiver_usdc_balance = get_erc20_balance(client.w3, usdc_address, receiver) try: diff --git a/autotx/tests/agents/token/research/test_advanced.py b/autotx/tests/agents/token/research/test_advanced.py index 0b72746..94f606b 100644 --- a/autotx/tests/agents/token/research/test_advanced.py +++ b/autotx/tests/agents/token/research/test_advanced.py @@ -1,29 +1,31 @@ from autotx.tests.agents.token.research.test_research import get_top_token_addresses_by_market_cap from autotx.eth_address import ETHAddress +from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance +from autotx.utils.ethereum.get_native_balance import get_native_balance def test_research_and_swap_many_tokens_subjective_simple(configuration, auto_tx): - (_, _, _, manager, _) = configuration + (_, _, client, manager, _) = configuration uni_address = ETHAddress(auto_tx.network.tokens["uni"]) - uni_balance_in_safe = manager.balance_of(uni_address) + uni_balance_in_safe = get_erc20_balance(client.w3, uni_address, manager.address) assert uni_balance_in_safe == 0 - starting_balance = manager.balance_of() + starting_balance = get_native_balance(client.w3, manager.address) prompt = f"I want to use 3 ETH to purchase 3 of the best projects in: GameFi, AI, and MEMEs. Please research the top projects, come up with a strategy, and purchase the tokens that look most promising. All of this should be on ETH mainnet." result = auto_tx.run(prompt, non_interactive=True) - ending_balance = manager.balance_of() + ending_balance = get_native_balance(client.w3, manager.address) gaming_token_address = get_top_token_addresses_by_market_cap("gaming", "MAINNET", 1, auto_tx)[0] - gaming_token_balance_in_safe = manager.balance_of(gaming_token_address) + gaming_token_balance_in_safe = get_erc20_balance(client.w3, gaming_token_address, manager.address) ai_token_address = get_top_token_addresses_by_market_cap("artificial-intelligence", "MAINNET", 1, auto_tx)[0] - ai_token_balance_in_safe = manager.balance_of(ai_token_address) + ai_token_balance_in_safe = get_erc20_balance(client.w3, ai_token_address, manager.address) meme_token_address = get_top_token_addresses_by_market_cap("meme-token", "MAINNET", 1, auto_tx)[0] - meme_token_balance_in_safe = manager.balance_of(meme_token_address) + meme_token_balance_in_safe = get_erc20_balance(client.w3, meme_token_address, manager.address) # Verify the balance is lower by max 3 ETH assert starting_balance - ending_balance <= 3 @@ -40,15 +42,15 @@ def test_research_and_swap_many_tokens_subjective_simple(configuration, auto_tx) assert meme_token_balance_in_safe > 0 def test_research_and_swap_many_tokens_subjective_complex(configuration, auto_tx): - (_, _, _, manager, _) = configuration + (_, _, client, manager, _) = configuration - starting_balance = manager.balance_of() + starting_balance = get_native_balance(client.w3, manager.address) prompt = f"I want to use 3 ETH to purchase exactly 10 of the best projects in: GameFi, NFTs, ZK, AI, and MEMEs. Please research the top projects, come up with a strategy, and purchase the tokens that look most promising. All of this should be on ETH mainnet." result = auto_tx.run(prompt, non_interactive=True) - ending_balance = manager.balance_of() + ending_balance = get_native_balance(client.w3, manager.address) # Verify the balance is lower by max 3 ETH assert starting_balance - ending_balance <= 3 diff --git a/autotx/tests/agents/token/research/test_research_and_swap.py b/autotx/tests/agents/token/research/test_research_and_swap.py index 87a9789..20bb764 100644 --- a/autotx/tests/agents/token/research/test_research_and_swap.py +++ b/autotx/tests/agents/token/research/test_research_and_swap.py @@ -1,7 +1,9 @@ from autotx.tests.agents.token.research.test_research import get_top_token_addresses_by_market_cap +from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance +from autotx.utils.ethereum.get_native_balance import get_native_balance def test_research_and_buy_one(configuration, auto_tx): - (_, _, _, manager, _) = configuration + (_, _, client, manager, _) = configuration prompt = ( f"Buy 1 ETH worth of a meme token with the largest market cap in Ethereum mainnet" @@ -11,13 +13,13 @@ def test_research_and_buy_one(configuration, auto_tx): token_address = get_top_token_addresses_by_market_cap("meme-token", "MAINNET", 1, auto_tx)[0] - token_balance_in_safe = manager.balance_of(token_address) + token_balance_in_safe = get_erc20_balance(client.w3, token_address, manager.address) assert token_balance_in_safe > 1000 def test_research_and_buy_multiple(configuration, auto_tx): - (_, _, _, manager, _) = configuration + (_, _, client, manager, _) = configuration - old_eth_balance = manager.balance_of() + old_eth_balance = get_native_balance(client.w3, manager.address) prompt = f""" Buy 1 ETH worth of a meme token with the largest market cap @@ -27,7 +29,7 @@ def test_research_and_buy_multiple(configuration, auto_tx): auto_tx.run(prompt, non_interactive=True) - new_eth_balance = manager.balance_of() + new_eth_balance = get_native_balance(client.w3, manager.address) assert old_eth_balance - new_eth_balance == 1.5 @@ -35,8 +37,8 @@ def test_research_and_buy_multiple(configuration, auto_tx): governance_token_address = get_top_token_addresses_by_market_cap("governance", "MAINNET", 1, auto_tx)[0] - meme_token_balance_in_safe = manager.balance_of(meme_token_address) + meme_token_balance_in_safe = get_erc20_balance(client.w3, meme_token_address, manager.address) assert meme_token_balance_in_safe > 1000 - governance_token_balance_in_safe = manager.balance_of(governance_token_address) + governance_token_balance_in_safe = get_erc20_balance(client.w3, governance_token_address, manager.address) assert governance_token_balance_in_safe > 90 diff --git a/autotx/tests/agents/token/research/test_research_swap_and_send.py b/autotx/tests/agents/token/research/test_research_swap_and_send.py index 14bc8c4..ce69ecf 100644 --- a/autotx/tests/agents/token/research/test_research_swap_and_send.py +++ b/autotx/tests/agents/token/research/test_research_swap_and_send.py @@ -1,5 +1,6 @@ from autotx.tests.agents.token.research.test_research import get_top_token_addresses_by_market_cap from autotx.utils.ethereum import get_erc20_balance +from autotx.utils.ethereum.get_native_balance import get_native_balance DIFFERENCE_PERCENTAGE = 0.01 @@ -16,7 +17,7 @@ def test_research_buy_one_send_one(configuration, auto_tx, test_accounts): auto_tx.run(prompt, non_interactive=True) token_address = get_top_token_addresses_by_market_cap("meme-token", "MAINNET", 1, auto_tx)[0] - token_balance_in_safe = manager.balance_of(token_address) + token_balance_in_safe = get_erc20_balance(client.w3, token_address, manager.address) receiver_balance = get_erc20_balance(web3, token_address, receiver) assert receiver_balance > 10000 @@ -37,7 +38,7 @@ def test_research_buy_one_send_multiple(configuration, auto_tx, test_accounts): auto_tx.run(prompt, non_interactive=True) meme_token_address = get_top_token_addresses_by_market_cap("meme-token", "MAINNET", 1, auto_tx)[0] - meme_token_balance_in_safe = manager.balance_of(meme_token_address) + meme_token_balance_in_safe = get_erc20_balance(client.w3, meme_token_address, manager.address) receiver_1_balance = get_erc20_balance(web3, meme_token_address, receiver_1) assert receiver_1_balance == 10000 @@ -54,7 +55,7 @@ def test_research_buy_multiple_send_multiple(configuration, auto_tx, test_accoun receiver_1 = test_accounts[0] receiver_2 = test_accounts[1] - old_eth_balance = manager.balance_of() + old_eth_balance = get_native_balance(client.w3, manager.address) prompt = f""" Buy 1 ETH worth of a meme token with the largest market cap @@ -65,15 +66,15 @@ def test_research_buy_multiple_send_multiple(configuration, auto_tx, test_accoun auto_tx.run(prompt, non_interactive=True) - new_eth_balance = manager.balance_of() + new_eth_balance = get_native_balance(client.w3, manager.address) assert old_eth_balance - new_eth_balance == 1.5 meme_token_address = get_top_token_addresses_by_market_cap("meme-token", "MAINNET", 1, auto_tx)[0] - meme_token_balance_in_safe = manager.balance_of(meme_token_address) + meme_token_balance_in_safe = get_erc20_balance(client.w3, meme_token_address, manager.address) governance_token_address = get_top_token_addresses_by_market_cap("governance", "MAINNET", 1, auto_tx)[0] - governance_token_balance_in_safe = manager.balance_of(governance_token_address) + governance_token_balance_in_safe = get_erc20_balance(client.w3, governance_token_address, manager.address) meme_balance = get_erc20_balance(web3, meme_token_address, receiver_1) assert meme_balance > 10000 diff --git a/autotx/tests/agents/token/test_swap.py b/autotx/tests/agents/token/test_swap.py index 9894c27..5fc0271 100644 --- a/autotx/tests/agents/token/test_swap.py +++ b/autotx/tests/agents/token/test_swap.py @@ -1,3 +1,4 @@ +from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance from autotx.utils.ethereum.networks import NetworkInfo from autotx.eth_address import ETHAddress @@ -10,11 +11,11 @@ def test_swap_with_non_default_token(configuration, auto_tx): shib_address = ETHAddress(network_info.tokens["shib"]) prompt = "Buy 100000 SHIB with ETH" - balance = manager.balance_of(shib_address) + balance = get_erc20_balance(client.w3, shib_address, manager.address) assert balance == 0 auto_tx.run(prompt, non_interactive=True) - new_balance = manager.balance_of(shib_address) + new_balance = get_erc20_balance(client.w3, shib_address, manager.address) expected_shib_amount = 100000 assert expected_shib_amount <= new_balance <= expected_shib_amount * DIFFERENCE_PERCENTAGE @@ -28,7 +29,7 @@ def test_swap_native(configuration, auto_tx): prompt = "Buy 100 USDC with ETH" auto_tx.run(prompt, non_interactive=True) - new_balance = manager.balance_of(usdc_address) + new_balance = get_erc20_balance(client.w3, usdc_address, manager.address) expected_usdc_amount = 100 assert expected_usdc_amount <= new_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE @@ -40,16 +41,16 @@ def test_swap_multiple_1(configuration, auto_tx): wbtc_address = ETHAddress(network_info.tokens["wbtc"]) prompt = "Buy 1000 USDC with ETH and then buy WBTC with 500 USDC" - wbtc_balance = manager.balance_of(wbtc_address) + wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) auto_tx.run(prompt, non_interactive=True) expected_usdc_amount = 500 - usdc_balance = manager.balance_of(usdc_address) + usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) # 1000 is the amount bought so we need to get the difference from that amount expected_usdc_amount_plus_slippage = 1000 * DIFFERENCE_PERCENTAGE assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount_plus_slippage - expected_usdc_amount - assert wbtc_balance < manager.balance_of(wbtc_address) + assert wbtc_balance < get_erc20_balance(client.w3, wbtc_address, manager.address) def test_swap_multiple_2(configuration, auto_tx): (_, _, client, manager, _) = configuration @@ -59,14 +60,14 @@ def test_swap_multiple_2(configuration, auto_tx): wbtc_address = ETHAddress(network_info.tokens["wbtc"]) prompt = "Sell ETH for 1000 USDC and then sell 500 USDC for WBTC" - wbtc_balance = manager.balance_of(wbtc_address) + wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) auto_tx.run(prompt, non_interactive=True) expected_amount = 500 - usdc_balance = manager.balance_of(usdc_address) + usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) assert expected_amount <= usdc_balance - assert wbtc_balance < manager.balance_of(wbtc_address) + assert wbtc_balance < get_erc20_balance(client.w3, wbtc_address, manager.address) def test_swap_triple(configuration, auto_tx): (_, _, client, manager, _) = configuration @@ -83,9 +84,9 @@ def test_swap_triple(configuration, auto_tx): expected_usdc_amount = 1 expected_uni_amount = 0.5 expected_wbtc_amount = 0.05 - usdc_balance = manager.balance_of(usdc_address) - uni_balance = manager.balance_of(uni_address) - wbtc_balance = manager.balance_of(wbtc_address) + usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) + uni_balance = get_erc20_balance(client.w3, uni_address, manager.address) + wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE assert expected_uni_amount <= uni_balance <= expected_uni_amount * DIFFERENCE_PERCENTAGE assert expected_wbtc_amount <= wbtc_balance <= expected_wbtc_amount * DIFFERENCE_PERCENTAGE @@ -98,13 +99,13 @@ def test_swap_complex_1(configuration, auto_tx): # This one is complex because i wbtc_address = ETHAddress(network_info.tokens["wbtc"]) prompt = "Swap ETH to 0.05 WBTC, then, swap WBTC to 1000 USDC" - wbtc_balance = manager.balance_of(wbtc_address) + wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) auto_tx.run(prompt, non_interactive=True) expected_usdc_amount = 1000 - usdc_balance = manager.balance_of(usdc_address) + usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE - assert wbtc_balance < manager.balance_of(wbtc_address) + assert wbtc_balance < get_erc20_balance(client.w3, wbtc_address, manager.address) def test_swap_complex_2(configuration, auto_tx): # This one is complex because it confuses the LLM with WBTC amount (_, _, client, manager, _) = configuration @@ -114,11 +115,11 @@ def test_swap_complex_2(configuration, auto_tx): # This one is complex because i wbtc_address = ETHAddress(network_info.tokens["wbtc"]) prompt = "Buy 1000 USDC with ETH, then sell USDC to buy 0.001 WBTC" - usdc_balance = manager.balance_of(usdc_address) + usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) auto_tx.run(prompt, non_interactive=True) - wbtc_balance = manager.balance_of(wbtc_address) + wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) expected_wbtc_amount = 0.001 assert expected_wbtc_amount <= wbtc_balance <= expected_wbtc_amount * DIFFERENCE_PERCENTAGE - assert usdc_balance < manager.balance_of(usdc_address) + assert usdc_balance < get_erc20_balance(client.w3, usdc_address, manager.address) diff --git a/autotx/tests/agents/token/test_swap_and_send.py b/autotx/tests/agents/token/test_swap_and_send.py index d69aaf7..225b7d4 100644 --- a/autotx/tests/agents/token/test_swap_and_send.py +++ b/autotx/tests/agents/token/test_swap_and_send.py @@ -16,7 +16,7 @@ def test_swap_and_send_simple(configuration, auto_tx, test_accounts): auto_tx.run(prompt, non_interactive=True) - new_wbtc_safe_address = manager.balance_of(wbtc_address) + new_wbtc_safe_address = get_erc20_balance(client.w3, wbtc_address, manager.address) new_receiver_wbtc_balance = get_erc20_balance(client.w3, wbtc_address, receiver) excepted_safe_wbtc_balance = 0.04 assert excepted_safe_wbtc_balance <= new_wbtc_safe_address <= new_wbtc_safe_address * DIFFERENCE_PERCENTAGE @@ -33,11 +33,11 @@ def test_swap_and_send_complex(configuration, auto_tx, test_accounts): prompt = f"Swap ETH to 0.05 WBTC, then, swap WBTC to 1000 USDC and send 50 USDC to {receiver}" - wbtc_safe_address = manager.balance_of(wbtc_address) + wbtc_safe_address = get_erc20_balance(client.w3, wbtc_address, manager.address) auto_tx.run(prompt, non_interactive=True) - new_wbtc_safe_address = manager.balance_of(wbtc_address) - new_usdc_safe_address = manager.balance_of(usdc_address) + new_wbtc_safe_address = get_erc20_balance(client.w3, wbtc_address, manager.address) + new_usdc_safe_address = get_erc20_balance(client.w3, usdc_address, manager.address) new_receiver_usdc_balance = get_erc20_balance(client.w3, usdc_address, receiver) expected_usdc_safe_balance = 950 @@ -60,7 +60,7 @@ def test_send_and_swap_simple(configuration, auto_tx, test_accounts): auto_tx.run(prompt, non_interactive=True) - safe_wbtc_balance = manager.balance_of(wbtc_address) + safe_wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) new_receiver_native_balance = get_native_balance(client.w3, receiver) new_receiver_wbtc_balance = get_erc20_balance(client.w3, wbtc_address, receiver) @@ -82,14 +82,14 @@ def test_send_and_swap_complex(configuration, auto_tx, test_accounts): prompt = f"Send 0.1 ETH to {receiver_1}, then swap ETH to 0.05 WBTC, then, swap WBTC to 1000 USDC and send 50 USDC to {receiver_2}" - wbtc_safe_balance = manager.balance_of(wbtc_address) + wbtc_safe_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) receiver_1_native_balance = get_native_balance(client.w3, receiver_1) receiver_2_usdc_balance = get_erc20_balance(client.w3, usdc_address, receiver_2) auto_tx.run(prompt, non_interactive=True) - new_wbtc_safe_balance = manager.balance_of(wbtc_address) - new_usdc_safe_balance = manager.balance_of(usdc_address) + new_wbtc_safe_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) + new_usdc_safe_balance = get_erc20_balance(client.w3, usdc_address, manager.address) new_receiver_1_native_balance = get_native_balance(client.w3, receiver_1) new_receiver_1_usdc_balance = get_erc20_balance(client.w3, usdc_address, receiver_1) new_receiver_1_wbtc_balance = get_erc20_balance(client.w3, wbtc_address, receiver_1) diff --git a/autotx/tests/integration/test_swap.py b/autotx/tests/integration/test_swap.py index 32d5b38..9185b30 100644 --- a/autotx/tests/integration/test_swap.py +++ b/autotx/tests/integration/test_swap.py @@ -1,5 +1,7 @@ from decimal import Decimal from autotx.eth_address import ETHAddress +from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance +from autotx.utils.ethereum.get_native_balance import get_native_balance from autotx.utils.ethereum.lifi.swap import build_swap_transaction from autotx.utils.ethereum.networks import NetworkInfo @@ -23,7 +25,7 @@ def test_buy_one_usdc(configuration): ) hash = manager.send_tx(buy_usdc_with_eth_transaction[0].params) manager.wait(hash) - usdc_balance = manager.balance_of(usdc_address) + usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE @@ -45,7 +47,7 @@ def test_buy_one_thousand_usdc(configuration): print(buy_usdc_with_eth_transaction[0].summary) hash = manager.send_tx(buy_usdc_with_eth_transaction[0].params) manager.wait(hash) - usdc_balance = manager.balance_of(usdc_address) + usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE @@ -55,7 +57,7 @@ def test_receive_native(configuration): network_info = NetworkInfo(client.w3.eth.chain_id) eth_address = ETHAddress(network_info.tokens["eth"]) usdc_address = ETHAddress(network_info.tokens["usdc"]) - safe_eth_balance = manager.balance_of() + safe_eth_balance = get_native_balance(client.w3, manager.address) assert safe_eth_balance == 10 buy_usdc_with_eth_transaction = build_swap_transaction( client.w3, @@ -68,7 +70,7 @@ def test_receive_native(configuration): ) hash = manager.send_tx(buy_usdc_with_eth_transaction[0].params) manager.wait(hash) - safe_eth_balance = manager.balance_of() + safe_eth_balance = get_native_balance(client.w3, manager.address) assert safe_eth_balance == 5 buy_eth_with_usdc_transaction = build_swap_transaction( @@ -84,7 +86,7 @@ def test_receive_native(configuration): manager.wait(hash) hash = manager.send_tx(buy_eth_with_usdc_transaction[1].params) manager.wait(hash) - safe_eth_balance = manager.balance_of() + safe_eth_balance = get_native_balance(client.w3, manager.address) assert safe_eth_balance >= 9 @@ -105,7 +107,7 @@ def test_buy_small_amount_wbtc_with_eth(configuration): ) hash = manager.send_tx(buy_wbtc_with_eth_transaction[0].params) manager.wait(hash) - wbtc_balance = manager.balance_of(wbtc_address) + wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) assert expected_wbtc_amount <= wbtc_balance <= expected_wbtc_amount * DIFFERENCE_PERCENTAGE @@ -126,7 +128,7 @@ def test_buy_big_amount_wbtc_with_eth(configuration): ) hash = manager.send_tx(buy_wbtc_with_eth_transaction[0].params) manager.wait(hash) - wbtc_balance = manager.balance_of(wbtc_address) + wbtc_balance = get_erc20_balance(client.w3, wbtc_balance, manager.address) assert expected_wbtc_amount <= wbtc_balance <= expected_wbtc_amount * DIFFERENCE_PERCENTAGE @@ -139,7 +141,7 @@ def test_swap_multiple_tokens(configuration): wbtc_address = ETHAddress(network_info.tokens["wbtc"]) shib_address = ETHAddress(network_info.tokens["shib"]) - usdc_balance = manager.balance_of(usdc_address) + usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) assert usdc_balance == 0 sell_eth_for_usdc_transaction = build_swap_transaction( @@ -153,10 +155,10 @@ def test_swap_multiple_tokens(configuration): ) hash = manager.send_tx(sell_eth_for_usdc_transaction[0].params) manager.wait(hash) - usdc_balance = manager.balance_of(usdc_address) + usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) assert usdc_balance > 2900 - wbtc_balance = manager.balance_of(wbtc_address) + wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) assert wbtc_balance == 0 buy_wbtc_with_usdc_transaction = build_swap_transaction( @@ -173,10 +175,10 @@ def test_swap_multiple_tokens(configuration): manager.wait(hash) hash = manager.send_tx(buy_wbtc_with_usdc_transaction[1].params) manager.wait(hash) - wbtc_balance = manager.balance_of(wbtc_address) + wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) assert wbtc_balance >= 0.01 - shib_balance = manager.balance_of(shib_address) + shib_balance = get_erc20_balance(client.w3, shib_address, manager.address) assert shib_balance == 0 sell_wbtc_for_shib = build_swap_transaction( @@ -192,6 +194,6 @@ def test_swap_multiple_tokens(configuration): manager.wait(hash) hash = manager.send_tx(sell_wbtc_for_shib[1].params) manager.wait(hash) - shib_balance = manager.balance_of(shib_address) - shib_balance = manager.balance_of(shib_address) + shib_balance = get_erc20_balance(client.w3, shib_address, manager.address) + shib_balance = get_erc20_balance(client.w3, shib_address, manager.address) assert shib_balance > 0 From cf5e54258937eff82aa4c53b748f4215daa1ce08 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Thu, 20 Jun 2024 23:10:00 +0200 Subject: [PATCH 03/23] decoupling safe manager from smart wallet --- autotx/AutoTx.py | 6 +- autotx/cli.py | 24 ++- autotx/server.py | 43 +++--- autotx/setup.py | 9 +- autotx/smart_accounts/api_smart_account.py | 37 +++++ .../local_biconomy_smart_account.py | 44 ++++++ autotx/smart_accounts/safe_smart_account.py | 75 +++++++++ autotx/smart_accounts/smart_account.py | 35 +++++ .../token/test_tokens_regression.py | 46 +++--- .../agents/token/research/test_advanced.py | 22 ++- .../token/research/test_research_and_swap.py | 16 +- .../research/test_research_swap_and_send.py | 34 ++--- autotx/tests/agents/token/send/test_send.py | 55 +++---- autotx/tests/agents/token/test_swap.py | 82 +++++----- .../tests/agents/token/test_swap_and_send.py | 66 ++++---- autotx/tests/conftest.py | 30 ++-- autotx/tests/integration/test_swap.py | 142 +++++++++--------- autotx/utils/configuration.py | 49 +----- autotx/utils/constants.py | 3 +- .../helpers/fill_dev_account_with_tokens.py | 14 +- .../utils/ethereum/helpers/swap_from_eoa.py | 14 +- autotx/wallets/api_smart_wallet.py | 30 ---- autotx/wallets/safe_smart_wallet.py | 26 ---- autotx/wallets/smart_wallet.py | 26 ---- 24 files changed, 480 insertions(+), 448 deletions(-) create mode 100644 autotx/smart_accounts/api_smart_account.py create mode 100644 autotx/smart_accounts/local_biconomy_smart_account.py create mode 100644 autotx/smart_accounts/safe_smart_account.py create mode 100644 autotx/smart_accounts/smart_account.py delete mode 100644 autotx/wallets/api_smart_wallet.py delete mode 100644 autotx/wallets/safe_smart_wallet.py delete mode 100644 autotx/wallets/smart_wallet.py diff --git a/autotx/AutoTx.py b/autotx/AutoTx.py index 26a558c..20c8364 100644 --- a/autotx/AutoTx.py +++ b/autotx/AutoTx.py @@ -19,7 +19,7 @@ from autotx.utils.logging.Logger import Logger from autotx.utils.ethereum.networks import NetworkInfo from autotx.utils.constants import OPENAI_BASE_URL, OPENAI_MODEL_NAME -from autotx.wallets.smart_wallet import SmartWallet +from autotx.smart_accounts.smart_account import SmartAccount @dataclass(kw_only=True) class Config: @@ -57,7 +57,7 @@ class RunResult: class AutoTx: web3: Web3 - wallet: SmartWallet + wallet: SmartAccount logger: Logger intents: list[Intent] network: NetworkInfo @@ -74,7 +74,7 @@ class AutoTx: def __init__( self, web3: Web3, - wallet: SmartWallet, + wallet: SmartAccount, network: NetworkInfo, agents: list[AutoTxAgent], config: Config, diff --git a/autotx/cli.py b/autotx/cli.py index 1a4a2f7..a1263a8 100644 --- a/autotx/cli.py +++ b/autotx/cli.py @@ -1,20 +1,25 @@ -import uuid from dotenv import load_dotenv - -from autotx import db -from autotx.wallets.safe_smart_wallet import SafeSmartWallet load_dotenv() -import uvicorn +from eth_account import Account +import uvicorn from typing import cast import click +import uuid +from eth_account.signers.local import LocalAccount +from autotx import db +from autotx.utils.constants import SMART_ACCOUNT_OWNER_PK +from autotx.smart_accounts.safe_smart_account import SafeSmartAccount +from autotx.smart_accounts.smart_account import SmartAccount from autotx.utils.configuration import AppConfig from autotx.utils.is_dev_env import is_dev_env from autotx.setup import print_agent_address, setup_agents from autotx.server import setup_server from autotx.AutoTx import AutoTx, Config from autotx.utils.ethereum.helpers.show_address_balances import show_address_balances +from autotx.smart_accounts.smart_account import SmartAccount +from autotx.smart_accounts.local_biconomy_smart_account import LocalBiconomySmartAccount def print_autotx_info() -> None: print(""" @@ -48,8 +53,13 @@ def run(prompt: str | None, non_interactive: bool, verbose: bool, logs: str | No if prompt == None: prompt = click.prompt("What do you want to do?") - app_config = AppConfig.load(fill_dev_account=True) - wallet = SafeSmartWallet(app_config.manager, auto_submit_tx=non_interactive) + app_config = AppConfig() + wallet: SmartAccount + if SMART_ACCOUNT_OWNER_PK is not None: + smart_account_owner = cast(LocalAccount, Account.from_key(SMART_ACCOUNT_OWNER_PK)) + wallet = LocalBiconomySmartAccount(app_config.web3, smart_account_owner, auto_submit_tx=non_interactive) + else: + wallet = SafeSmartAccount(app_config.rpc_url, app_config.network_info, auto_submit_tx=non_interactive, fill_dev_account=True) (get_llm_config, agents, logs_dir) = setup_agents(logs, cache) diff --git a/autotx/server.py b/autotx/server.py index a5f4f0c..d1abd13 100644 --- a/autotx/server.py +++ b/autotx/server.py @@ -12,12 +12,13 @@ from autotx import db from autotx.AutoTx import AutoTx, Config as AutoTxConfig from autotx.intents import Intent +from autotx.smart_accounts.smart_account import SmartAccount from autotx.transactions import Transaction from autotx.utils.configuration import AppConfig from autotx.utils.ethereum.chain_short_names import CHAIN_ID_TO_SHORT_NAME from autotx.utils.ethereum.networks import SUPPORTED_NETWORKS_CONFIGURATION_MAP -from autotx.wallets.api_smart_wallet import ApiSmartWallet -from autotx.wallets.smart_wallet import SmartWallet +from autotx.smart_accounts.api_smart_account import ApiSmartAccount +from autotx.smart_accounts.safe_smart_account import SafeSmartAccount class AutoTxParams: verbose: bool @@ -61,7 +62,7 @@ def authorize(authorization: str | None) -> models.App: return app -def load_config_for_user(app_id: str, user_id: str, address: str, chain_id: int) -> AppConfig: +def load_wallet_for_user(app_config: AppConfig, app_id: str, user_id: str, address: str) -> SmartAccount: agent_private_key = db.get_agent_private_key(app_id, user_id) if not agent_private_key: @@ -69,9 +70,9 @@ def load_config_for_user(app_id: str, user_id: str, address: str, chain_id: int) agent = Account.from_key(agent_private_key) - app_config = AppConfig.load(smart_account_addr=address, subsidized_chain_id=chain_id, agent=agent) + wallet = SafeSmartAccount(app_config.rpc_url, app_config.network_info, auto_submit_tx=False, smart_account_addr=address, agent=agent) - return app_config + return wallet def authorize_app_and_user(authorization: str | None, user_id: str) -> tuple[models.App, models.AppUser]: app = authorize(authorization) @@ -86,7 +87,8 @@ async def build_transactions(app_id: str, user_id: str, chain_id: int, address: if task.running: raise HTTPException(status_code=400, detail="Task is still running") - app_config = load_config_for_user(app_id, user_id, address, chain_id) + app_config = AppConfig(subsidized_chain_id=chain_id) + wallet = load_wallet_for_user(app_config, app_id, user_id, address) if task.intents is None or len(task.intents) == 0: return [] @@ -94,7 +96,7 @@ async def build_transactions(app_id: str, user_id: str, chain_id: int, address: transactions: list[Transaction] = [] for intent in task.intents: - transactions.extend(await intent.build_transactions(app_config.web3, app_config.network_info, app_config.manager.address)) + transactions.extend(await intent.build_transactions(app_config.web3, app_config.network_info, wallet.address)) return transactions @@ -113,17 +115,14 @@ async def create_task(task: models.TaskCreate, background_tasks: BackgroundTasks prompt = task.prompt - app_config = AppConfig.load(smart_account_addr=task.address, subsidized_chain_id=task.chain_id) + app_config = AppConfig(subsidized_chain_id=task.chain_id) - wallet: SmartWallet - if autotx_params.is_dev: - wallet = ApiSmartWallet(app_config.web3, app_config.manager, tasks) - else: - wallet = ApiSmartWallet(app_config.web3, app_config.manager, tasks) + wallet = SafeSmartAccount(app_config.rpc_url, app_config.network_info, smart_account_addr=task.address) + api_wallet = ApiSmartAccount(app_config.web3, wallet, tasks) - created_task: models.Task = tasks.start(prompt, wallet.address.hex, app_config.network_info.chain_id.value, app_user.id) + created_task: models.Task = tasks.start(prompt, api_wallet.address.hex, app_config.network_info.chain_id.value, app_user.id) task_id = created_task.id - wallet.task_id = task_id + api_wallet.task_id = task_id try: (get_llm_config, agents, logs_dir) = setup.setup_agents(autotx_params.logs, cache=autotx_params.cache) @@ -138,7 +137,7 @@ def on_notify_user(message: str) -> None: autotx = AutoTx( app_config.web3, - wallet, + api_wallet, app_config.network_info, agents, AutoTxConfig(verbose=autotx_params.verbose, get_llm_config=get_llm_config, logs_dir=logs_dir, max_rounds=autotx_params.max_rounds), @@ -296,12 +295,10 @@ def send_transactions( return f"https://app.safe.global/transactions/queue?safe={CHAIN_ID_TO_SHORT_NAME[str(chain_id)]}:{address}" try: - app_config = load_config_for_user(app.id, user_id, address, chain_id) + app_config = AppConfig(subsidized_chain_id=chain_id) + wallet = load_wallet_for_user(app_config, app.id, user_id, address) - app_config.manager.send_multisend_tx_batch( - transactions, - require_approval=False, - ) + wallet.send_transactions(transactions) except SafeAPIException as e: if "is not an owner or delegate" in str(e): raise HTTPException(status_code=400, detail="Agent is not an owner or delegate") @@ -339,7 +336,9 @@ async def get_version() -> Dict[str, str]: def setup_server(verbose: bool, logs: str | None, max_rounds: int | None, cache: bool, is_dev: bool, check_valid_safe: bool) -> None: if is_dev: - AppConfig.load(check_valid_safe=check_valid_safe) # Loading the configuration deploys the dev wallet in dev mode + app_config = AppConfig() + # Loading the SafeSmartAccount will deploy a new Safe if one is not already deployed + SafeSmartAccount(app_config.rpc_url, app_config.network_info, fill_dev_account=True, check_valid_safe=check_valid_safe) global autotx_params autotx_params = AutoTxParams( diff --git a/autotx/setup.py b/autotx/setup.py index 5bd71af..8def330 100644 --- a/autotx/setup.py +++ b/autotx/setup.py @@ -10,9 +10,7 @@ from autotx.autotx_agent import AutoTxAgent from autotx.utils.constants import COINGECKO_API_KEY, OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL_NAME from autotx.utils.ethereum import SafeManager -from autotx.utils.ethereum.cached_safe_address import get_cached_safe_address from autotx.eth_address import ETHAddress -from autotx.utils.ethereum.helpers.fill_dev_account_with_tokens import fill_dev_account_with_tokens from autotx.utils.ethereum.helpers.get_dev_account import get_dev_account from autotx.utils.ethereum.helpers.show_address_balances import show_address_balances from autotx.utils.ethereum.networks import NetworkInfo @@ -23,7 +21,7 @@ def print_agent_address() -> None: acc = get_or_create_agent_account() print(f"Agent address: {acc.address}") -def setup_safe(smart_account_addr: ETHAddress | None, agent: LocalAccount, client: EthereumClient, fill_dev_account: bool, check_valid_safe: bool) -> SafeManager: +def setup_safe(smart_account_addr: ETHAddress | None, agent: LocalAccount, client: EthereumClient, check_valid_safe: bool) -> SafeManager: web3 = client.w3 chain_id = web3.eth.chain_id @@ -51,13 +49,8 @@ def setup_safe(smart_account_addr: ETHAddress | None, agent: LocalAccount, clien print("No smart account connected, deploying a new one...") dev_account = get_dev_account() - is_safe_deployed = get_cached_safe_address() manager = SafeManager.deploy_safe(client, dev_account, agent, [dev_account.address, agent.address], 1) print(f"Smart account deployed: {manager.address}") - - if not is_safe_deployed and fill_dev_account: - fill_dev_account_with_tokens(client, dev_account, manager.address, network_info) - print(f"Funds sent to smart account for testing purposes") print("=" * 50) print("Starting smart account balances:") diff --git a/autotx/smart_accounts/api_smart_account.py b/autotx/smart_accounts/api_smart_account.py new file mode 100644 index 0000000..4b82391 --- /dev/null +++ b/autotx/smart_accounts/api_smart_account.py @@ -0,0 +1,37 @@ +from web3 import Web3 +from autotx import db +from autotx.intents import Intent +from autotx.smart_accounts.smart_account import SmartAccount +from autotx.transactions import TransactionBase + +# ApiSmartAccount is a wrapper around other SmartAccounts that allows for hooks to be added to the transaction process +class ApiSmartAccount(SmartAccount): + wallet: SmartAccount + tasks: db.TasksRepository + task_id: str | None + + def __init__(self, web3: Web3, wallet: SmartAccount, tasks: db.TasksRepository, task_id: str | None = None): + super().__init__(web3, wallet.address) + self.wallet = wallet + self.tasks = tasks + self.task_id = task_id + + def on_intents_prepared(self, intents: list[Intent]) -> None: + if self.task_id is None: + raise ValueError("Task ID is required") + + saved_task = self.tasks.get(self.task_id) + if saved_task is None: + raise ValueError("Task not found") + + saved_task.intents.extend(intents) + self.tasks.update(saved_task) + + async def on_intents_ready(self, _intents: list[Intent]) -> bool | str: + return True + + def send_transaction(self, transaction: TransactionBase) -> None: + self.wallet.send_transaction(transaction) + + def send_transactions(self, transactions: list[TransactionBase]) -> None: + self.wallet.send_transactions(transactions) \ No newline at end of file diff --git a/autotx/smart_accounts/local_biconomy_smart_account.py b/autotx/smart_accounts/local_biconomy_smart_account.py new file mode 100644 index 0000000..f59f3a2 --- /dev/null +++ b/autotx/smart_accounts/local_biconomy_smart_account.py @@ -0,0 +1,44 @@ +import json +import requests +from eth_account.signers.local import LocalAccount +from web3 import Web3 + +from autotx.intents import Intent +from autotx.transactions import TransactionBase +from autotx.smart_accounts.smart_account import SmartAccount + +class LocalBiconomySmartAccount(SmartAccount): + web3: Web3 + owner: LocalAccount + auto_submit_tx: bool + + def __init__(self, web3: Web3, owner: LocalAccount, auto_submit_tx: bool): + super().__init__(web3, owner.address) + + self.web3 = web3 + self.owner = owner + self.auto_submit_tx = auto_submit_tx + + def on_intents_prepared(self, intents: list[Intent]) -> None: + pass + + async def on_intents_ready(self, intents: list[Intent]) -> bool | str: + return False + + def send_transaction(self, transaction: TransactionBase) -> None: + response = requests.post( + f"http://localhost:3000/api/v1/account/transactions?account={self.owner.address}&chainId={self.web3.eth.chain_id}", + json=json.dumps([transaction]), + ) + + if response.status_code != 200: + raise ValueError(f"Transaction failed: {response.json()}") + + def send_transactions(self, transactions: list[TransactionBase]) -> None: + response = requests.post( + f"http://localhost:3000/api/v1/account/transactions?account={self.owner.address}&chainId={self.web3.eth.chain_id}", + json=json.dumps(transactions), + ) + + if response.status_code != 200: + raise ValueError(f"Transaction failed: {response.json()}") diff --git a/autotx/smart_accounts/safe_smart_account.py b/autotx/smart_accounts/safe_smart_account.py new file mode 100644 index 0000000..d5611b6 --- /dev/null +++ b/autotx/smart_accounts/safe_smart_account.py @@ -0,0 +1,75 @@ +import os +from eth_account.signers.local import LocalAccount +from eth_typing import URI +from eth_account.signers.local import LocalAccount +from gnosis.eth import EthereumClient + +from autotx.intents import Intent +from autotx.transactions import TransactionBase +from autotx.utils.ethereum import SafeManager +from autotx.smart_accounts.smart_account import SmartAccount +from autotx.setup import setup_safe +from autotx.utils.ethereum import SafeManager +from autotx.utils.ethereum.agent_account import get_or_create_agent_account +from autotx.utils.ethereum.cached_safe_address import get_cached_safe_address +from autotx.eth_address import ETHAddress +from autotx.utils.ethereum.helpers.fill_dev_account_with_tokens import fill_dev_account_with_tokens +from autotx.smart_accounts.smart_account import SmartAccount +from autotx.utils.ethereum.networks import NetworkInfo + +class SafeSmartAccount(SmartAccount): + agent: LocalAccount + manager: SafeManager + auto_submit_tx: bool + + def __init__( + self, + rpc_url: str, + network_info: NetworkInfo, + auto_submit_tx: bool = False, + smart_account_addr: str | None = None, + agent: LocalAccount | None = None, + check_valid_safe: bool = False, + fill_dev_account: bool = False, + ): + client = EthereumClient(URI(rpc_url)) + + agent = agent if agent else get_or_create_agent_account() + + smart_account_addr = smart_account_addr if smart_account_addr else os.getenv("SMART_ACCOUNT_ADDRESS") + smart_account = ETHAddress(smart_account_addr) if smart_account_addr else None + + is_safe_deployed = get_cached_safe_address() + + manager = setup_safe(smart_account, agent, client, check_valid_safe) + + if not is_safe_deployed and fill_dev_account: + fill_dev_account_with_tokens(client.w3, manager.address, network_info) + print(f"Funds sent to smart account for testing purposes") + + super().__init__(client.w3, manager.address) + + self.manager = manager + self.agent = agent + self.auto_submit_tx = auto_submit_tx + + def on_intents_prepared(self, intents: list[Intent]) -> None: + pass + + async def on_intents_ready(self, intents: list[Intent]) -> bool | str: + transactions: list[TransactionBase] = [] + + for intent in intents: + transactions.extend(await intent.build_transactions(self.manager.web3, self.manager.network, self.manager.address)) + + return self.manager.send_multisend_tx_batch(transactions, not self.auto_submit_tx) + + def send_transaction(self, transaction: TransactionBase) -> None: + self.manager.send_multisend_tx_batch([transaction], require_approval=False) + + def send_transactions(self, transactions: list[TransactionBase]) -> None: + self.manager.send_multisend_tx_batch( + transactions, + require_approval=False, + ) + diff --git a/autotx/smart_accounts/smart_account.py b/autotx/smart_accounts/smart_account.py new file mode 100644 index 0000000..dd0bfac --- /dev/null +++ b/autotx/smart_accounts/smart_account.py @@ -0,0 +1,35 @@ +from abc import abstractmethod +from hexbytes import HexBytes +from web3 import Web3 +from web3.types import TxReceipt + +from autotx.intents import Intent +from autotx.transactions import TransactionBase +from autotx.eth_address import ETHAddress + + +class SmartAccount: + web3: Web3 + address: ETHAddress + + def __init__(self, web3: Web3, address: ETHAddress): + self.web3 = web3 + self.address = address + + def on_intents_prepared(self, intents: list[Intent]) -> None: + pass + + @abstractmethod + async def on_intents_ready(self, intents: list[Intent]) -> bool | str: # True if sent, False if declined, str if feedback + pass + + @abstractmethod + def send_transaction(self, transaction: TransactionBase) -> None: + pass + + @abstractmethod + def send_transactions(self, transactions: list[TransactionBase]) -> None: + pass + + def wait(self, tx_hash: HexBytes) -> TxReceipt: + return self.web3.eth.wait_for_transaction_receipt(tx_hash) \ No newline at end of file diff --git a/autotx/tests/agents/regression/token/test_tokens_regression.py b/autotx/tests/agents/regression/token/test_tokens_regression.py index f62a210..ad61920 100644 --- a/autotx/tests/agents/regression/token/test_tokens_regression.py +++ b/autotx/tests/agents/regression/token/test_tokens_regression.py @@ -5,8 +5,7 @@ @pytest.mark.skip() -def test_auto_tx_send_erc20(configuration, auto_tx, usdc, test_accounts): - (_, _, client, _, _) = configuration +def test_auto_tx_send_erc20(smart_account, auto_tx, usdc, test_accounts): receiver = test_accounts[0] @@ -25,11 +24,11 @@ def test_auto_tx_send_erc20(configuration, auto_tx, usdc, test_accounts): ] for prompt in prompts: - balance = get_erc20_balance(client.w3, usdc, receiver) + balance = get_erc20_balance(smart_account.web3, usdc, receiver) auto_tx.run(prompt, non_interactive=True) - new_balance = get_erc20_balance(client.w3, usdc, receiver) + new_balance = get_erc20_balance(smart_account.web3, usdc, receiver) try: assert balance + 10 == new_balance @@ -39,10 +38,8 @@ def test_auto_tx_send_erc20(configuration, auto_tx, usdc, test_accounts): raise @pytest.mark.skip() -def test_auto_tx_swap(configuration, auto_tx): - (_, _, client, manager, _) = configuration - web3 = client.w3 - network_info = NetworkInfo(web3.eth.chain_id) +def test_auto_tx_swap(smart_account, auto_tx): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) prompts = [ @@ -60,11 +57,11 @@ def test_auto_tx_swap(configuration, auto_tx): ] for prompt in prompts: - balance = get_erc20_balance(client.w3, usdc_address, manager.address) + balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) auto_tx.run(prompt, non_interactive=True) - new_balance = get_erc20_balance(client.w3, usdc_address, manager.address) + new_balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) try: assert balance + 100 == new_balance @@ -74,8 +71,7 @@ def test_auto_tx_swap(configuration, auto_tx): raise @pytest.mark.skip() -def test_auto_tx_multiple_sends(configuration, auto_tx, usdc, test_accounts): - (_, _, client, _, _) = configuration +def test_auto_tx_multiple_sends(smart_account, auto_tx, usdc, test_accounts): receiver_one = test_accounts[0] receiver_two = test_accounts[1] @@ -95,13 +91,13 @@ def test_auto_tx_multiple_sends(configuration, auto_tx, usdc, test_accounts): ] for prompt in prompts: - balance_one = get_erc20_balance(client.w3, usdc, receiver_one) - balance_two = get_erc20_balance(client.w3, usdc, receiver_two) + balance_one = get_erc20_balance(smart_account.web3, usdc, receiver_one) + balance_two = get_erc20_balance(smart_account.web3, usdc, receiver_two) auto_tx.run(prompt, non_interactive=True) - new_balance_one = get_erc20_balance(client.w3, usdc, receiver_one) - new_balance_two = get_erc20_balance(client.w3, usdc, receiver_two) + new_balance_one = get_erc20_balance(smart_account.web3, usdc, receiver_one) + new_balance_two = get_erc20_balance(smart_account.web3, usdc, receiver_two) try: assert balance_one + 10 == new_balance_one @@ -113,10 +109,8 @@ def test_auto_tx_multiple_sends(configuration, auto_tx, usdc, test_accounts): raise @pytest.mark.skip() -def test_auto_tx_swap_and_send(configuration, auto_tx, test_accounts): - (_, _, client, manager, _) = configuration - web3 = client.w3 - network_info = NetworkInfo(web3.eth.chain_id) +def test_auto_tx_swap_and_send(smart_account, auto_tx, test_accounts): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) @@ -137,15 +131,15 @@ def test_auto_tx_swap_and_send(configuration, auto_tx, test_accounts): ] for prompt in prompts: - wbtc_safe_address = get_erc20_balance(client.w3, wbtc_address, manager.address) - usdc_safe_address = get_erc20_balance(client.w3, usdc_address, manager.address) - receiver_usdc_balance = get_erc20_balance(client.w3, usdc_address, receiver) + wbtc_safe_address = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) + usdc_safe_address = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) + receiver_usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, receiver) auto_tx.run(prompt, non_interactive=True) - new_wbtc_safe_address = get_erc20_balance(client.w3, wbtc_address, manager.address) - new_usdc_safe_address = get_erc20_balance(client.w3, usdc_address, manager.address) - new_receiver_usdc_balance = get_erc20_balance(client.w3, usdc_address, receiver) + new_wbtc_safe_address = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) + new_usdc_safe_address = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) + new_receiver_usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, receiver) try: assert new_wbtc_safe_address > wbtc_safe_address diff --git a/autotx/tests/agents/token/research/test_advanced.py b/autotx/tests/agents/token/research/test_advanced.py index 94f606b..8d01e08 100644 --- a/autotx/tests/agents/token/research/test_advanced.py +++ b/autotx/tests/agents/token/research/test_advanced.py @@ -3,29 +3,28 @@ from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance from autotx.utils.ethereum.get_native_balance import get_native_balance -def test_research_and_swap_many_tokens_subjective_simple(configuration, auto_tx): - (_, _, client, manager, _) = configuration +def test_research_and_swap_many_tokens_subjective_simple(smart_account, auto_tx): uni_address = ETHAddress(auto_tx.network.tokens["uni"]) - uni_balance_in_safe = get_erc20_balance(client.w3, uni_address, manager.address) + uni_balance_in_safe = get_erc20_balance(smart_account.web3, uni_address, smart_account.address) assert uni_balance_in_safe == 0 - starting_balance = get_native_balance(client.w3, manager.address) + starting_balance = get_native_balance(smart_account.web3, smart_account.address) prompt = f"I want to use 3 ETH to purchase 3 of the best projects in: GameFi, AI, and MEMEs. Please research the top projects, come up with a strategy, and purchase the tokens that look most promising. All of this should be on ETH mainnet." result = auto_tx.run(prompt, non_interactive=True) - ending_balance = get_native_balance(client.w3, manager.address) + ending_balance = get_native_balance(smart_account.web3, smart_account.address) gaming_token_address = get_top_token_addresses_by_market_cap("gaming", "MAINNET", 1, auto_tx)[0] - gaming_token_balance_in_safe = get_erc20_balance(client.w3, gaming_token_address, manager.address) + gaming_token_balance_in_safe = get_erc20_balance(smart_account.web3, gaming_token_address, smart_account.address) ai_token_address = get_top_token_addresses_by_market_cap("artificial-intelligence", "MAINNET", 1, auto_tx)[0] - ai_token_balance_in_safe = get_erc20_balance(client.w3, ai_token_address, manager.address) + ai_token_balance_in_safe = get_erc20_balance(smart_account.web3, ai_token_address, smart_account.address) meme_token_address = get_top_token_addresses_by_market_cap("meme-token", "MAINNET", 1, auto_tx)[0] - meme_token_balance_in_safe = get_erc20_balance(client.w3, meme_token_address, manager.address) + meme_token_balance_in_safe = get_erc20_balance(smart_account.web3, meme_token_address, smart_account.address) # Verify the balance is lower by max 3 ETH assert starting_balance - ending_balance <= 3 @@ -41,16 +40,15 @@ def test_research_and_swap_many_tokens_subjective_simple(configuration, auto_tx) assert ai_token_balance_in_safe > 0 assert meme_token_balance_in_safe > 0 -def test_research_and_swap_many_tokens_subjective_complex(configuration, auto_tx): - (_, _, client, manager, _) = configuration +def test_research_and_swap_many_tokens_subjective_complex(smart_account, auto_tx): - starting_balance = get_native_balance(client.w3, manager.address) + starting_balance = get_native_balance(smart_account.web3, smart_account.address) prompt = f"I want to use 3 ETH to purchase exactly 10 of the best projects in: GameFi, NFTs, ZK, AI, and MEMEs. Please research the top projects, come up with a strategy, and purchase the tokens that look most promising. All of this should be on ETH mainnet." result = auto_tx.run(prompt, non_interactive=True) - ending_balance = get_native_balance(client.w3, manager.address) + ending_balance = get_native_balance(smart_account.web3, smart_account.address) # Verify the balance is lower by max 3 ETH assert starting_balance - ending_balance <= 3 diff --git a/autotx/tests/agents/token/research/test_research_and_swap.py b/autotx/tests/agents/token/research/test_research_and_swap.py index 20bb764..365c0fb 100644 --- a/autotx/tests/agents/token/research/test_research_and_swap.py +++ b/autotx/tests/agents/token/research/test_research_and_swap.py @@ -2,8 +2,7 @@ from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance from autotx.utils.ethereum.get_native_balance import get_native_balance -def test_research_and_buy_one(configuration, auto_tx): - (_, _, client, manager, _) = configuration +def test_research_and_buy_one(smart_account, auto_tx): prompt = ( f"Buy 1 ETH worth of a meme token with the largest market cap in Ethereum mainnet" @@ -13,13 +12,12 @@ def test_research_and_buy_one(configuration, auto_tx): token_address = get_top_token_addresses_by_market_cap("meme-token", "MAINNET", 1, auto_tx)[0] - token_balance_in_safe = get_erc20_balance(client.w3, token_address, manager.address) + token_balance_in_safe = get_erc20_balance(smart_account.web3, token_address, smart_account.address) assert token_balance_in_safe > 1000 -def test_research_and_buy_multiple(configuration, auto_tx): - (_, _, client, manager, _) = configuration +def test_research_and_buy_multiple(smart_account, auto_tx): - old_eth_balance = get_native_balance(client.w3, manager.address) + old_eth_balance = get_native_balance(smart_account.web3, smart_account.address) prompt = f""" Buy 1 ETH worth of a meme token with the largest market cap @@ -29,7 +27,7 @@ def test_research_and_buy_multiple(configuration, auto_tx): auto_tx.run(prompt, non_interactive=True) - new_eth_balance = get_native_balance(client.w3, manager.address) + new_eth_balance = get_native_balance(smart_account.web3, smart_account.address) assert old_eth_balance - new_eth_balance == 1.5 @@ -37,8 +35,8 @@ def test_research_and_buy_multiple(configuration, auto_tx): governance_token_address = get_top_token_addresses_by_market_cap("governance", "MAINNET", 1, auto_tx)[0] - meme_token_balance_in_safe = get_erc20_balance(client.w3, meme_token_address, manager.address) + meme_token_balance_in_safe = get_erc20_balance(smart_account.web3, meme_token_address, smart_account.address) assert meme_token_balance_in_safe > 1000 - governance_token_balance_in_safe = get_erc20_balance(client.w3, governance_token_address, manager.address) + governance_token_balance_in_safe = get_erc20_balance(smart_account.web3, governance_token_address, smart_account.address) assert governance_token_balance_in_safe > 90 diff --git a/autotx/tests/agents/token/research/test_research_swap_and_send.py b/autotx/tests/agents/token/research/test_research_swap_and_send.py index ce69ecf..81e72dd 100644 --- a/autotx/tests/agents/token/research/test_research_swap_and_send.py +++ b/autotx/tests/agents/token/research/test_research_swap_and_send.py @@ -4,9 +4,7 @@ DIFFERENCE_PERCENTAGE = 0.01 -def test_research_buy_one_send_one(configuration, auto_tx, test_accounts): - (_, _, client, manager, _) = configuration - web3 = client.w3 +def test_research_buy_one_send_one(smart_account, auto_tx, test_accounts): receiver = test_accounts[0] @@ -17,16 +15,14 @@ def test_research_buy_one_send_one(configuration, auto_tx, test_accounts): auto_tx.run(prompt, non_interactive=True) token_address = get_top_token_addresses_by_market_cap("meme-token", "MAINNET", 1, auto_tx)[0] - token_balance_in_safe = get_erc20_balance(client.w3, token_address, manager.address) + token_balance_in_safe = get_erc20_balance(smart_account.web3, token_address, smart_account.address) - receiver_balance = get_erc20_balance(web3, token_address, receiver) + receiver_balance = get_erc20_balance(smart_account.web3, token_address, receiver) assert receiver_balance > 10000 assert token_balance_in_safe / receiver_balance < DIFFERENCE_PERCENTAGE -def test_research_buy_one_send_multiple(configuration, auto_tx, test_accounts): - (_, _, client, manager, _) = configuration - web3 = client.w3 +def test_research_buy_one_send_multiple(smart_account, auto_tx, test_accounts): receiver_1 = test_accounts[0] receiver_2 = test_accounts[1] @@ -38,24 +34,22 @@ def test_research_buy_one_send_multiple(configuration, auto_tx, test_accounts): auto_tx.run(prompt, non_interactive=True) meme_token_address = get_top_token_addresses_by_market_cap("meme-token", "MAINNET", 1, auto_tx)[0] - meme_token_balance_in_safe = get_erc20_balance(client.w3, meme_token_address, manager.address) + meme_token_balance_in_safe = get_erc20_balance(smart_account.web3, meme_token_address, smart_account.address) - receiver_1_balance = get_erc20_balance(web3, meme_token_address, receiver_1) + receiver_1_balance = get_erc20_balance(smart_account.web3, meme_token_address, receiver_1) assert receiver_1_balance == 10000 - receiver_2_balance = get_erc20_balance(web3, meme_token_address, receiver_2) + receiver_2_balance = get_erc20_balance(smart_account.web3, meme_token_address, receiver_2) assert receiver_2_balance == 250 assert meme_token_balance_in_safe > 10000 -def test_research_buy_multiple_send_multiple(configuration, auto_tx, test_accounts): - (_, _, client, manager, _) = configuration - web3 = client.w3 +def test_research_buy_multiple_send_multiple(smart_account, auto_tx, test_accounts): receiver_1 = test_accounts[0] receiver_2 = test_accounts[1] - old_eth_balance = get_native_balance(client.w3, manager.address) + old_eth_balance = get_native_balance(smart_account.web3, smart_account.address) prompt = f""" Buy 1 ETH worth of a meme token with the largest market cap @@ -66,20 +60,20 @@ def test_research_buy_multiple_send_multiple(configuration, auto_tx, test_accoun auto_tx.run(prompt, non_interactive=True) - new_eth_balance = get_native_balance(client.w3, manager.address) + new_eth_balance = get_native_balance(smart_account.web3, smart_account.address) assert old_eth_balance - new_eth_balance == 1.5 meme_token_address = get_top_token_addresses_by_market_cap("meme-token", "MAINNET", 1, auto_tx)[0] - meme_token_balance_in_safe = get_erc20_balance(client.w3, meme_token_address, manager.address) + meme_token_balance_in_safe = get_erc20_balance(smart_account.web3, meme_token_address, smart_account.address) governance_token_address = get_top_token_addresses_by_market_cap("governance", "MAINNET", 1, auto_tx)[0] - governance_token_balance_in_safe = get_erc20_balance(client.w3, governance_token_address, manager.address) + governance_token_balance_in_safe = get_erc20_balance(smart_account.web3, governance_token_address, smart_account.address) - meme_balance = get_erc20_balance(web3, meme_token_address, receiver_1) + meme_balance = get_erc20_balance(smart_account.web3, meme_token_address, receiver_1) assert meme_balance > 10000 - governance_balance = get_erc20_balance(web3, governance_token_address, receiver_2) + governance_balance = get_erc20_balance(smart_account.web3, governance_token_address, receiver_2) assert governance_balance > 90 assert meme_token_balance_in_safe / meme_balance < DIFFERENCE_PERCENTAGE diff --git a/autotx/tests/agents/token/send/test_send.py b/autotx/tests/agents/token/send/test_send.py index 03f06b6..fb79edc 100644 --- a/autotx/tests/agents/token/send/test_send.py +++ b/autotx/tests/agents/token/send/test_send.py @@ -1,67 +1,62 @@ from autotx.utils.ethereum import get_erc20_balance from autotx.utils.ethereum.get_native_balance import get_native_balance -def test_send_native(configuration, auto_tx, test_accounts): - (_, _, client, _, _) = configuration +def test_send_native(smart_account, auto_tx, test_accounts): receiver = test_accounts[0] - balance = get_native_balance(client.w3, receiver) + balance = get_native_balance(smart_account.web3, receiver) assert balance == 0 auto_tx.run(f"Send 1 ETH to {receiver}", non_interactive=True) - balance = get_native_balance(client.w3, receiver) + balance = get_native_balance(smart_account.web3, receiver) assert balance == 1 -def test_send_erc20(configuration, auto_tx, usdc, test_accounts): - (_, _, client, _, _) = configuration +def test_send_erc20(smart_account, auto_tx, usdc, test_accounts): receiver = test_accounts[0] prompt = f"Send 10 USDC to {receiver}" - balance = get_erc20_balance(client.w3, usdc, receiver) + balance = get_erc20_balance(smart_account.web3, usdc, receiver) auto_tx.run(prompt, non_interactive=True) - new_balance = get_erc20_balance(client.w3, usdc, receiver) + new_balance = get_erc20_balance(smart_account.web3, usdc, receiver) assert balance + 10 == new_balance -def test_send_native_sequential(configuration, auto_tx, test_accounts): - (_, _, client, _, _) = configuration +def test_send_native_sequential(smart_account, auto_tx, test_accounts): receiver = test_accounts[0] auto_tx.run(f"Send 1 ETH to {receiver}", non_interactive=True) - balance = get_native_balance(client.w3, receiver) + balance = get_native_balance(smart_account.web3, receiver) assert balance == 1 auto_tx.run(f"Send 0.5 ETH to {receiver}", non_interactive=True) - balance = get_native_balance(client.w3, receiver) + balance = get_native_balance(smart_account.web3, receiver) assert balance == 1.5 -def test_send_erc20_parallel(configuration, auto_tx, usdc, test_accounts): - (_, _, client, _, _) = configuration +def test_send_erc20_parallel(smart_account, auto_tx, usdc, test_accounts): receiver_one = test_accounts[0] receiver_two = test_accounts[1] prompt = f"Send 2 USDC to {receiver_one} and 3 USDC to {receiver_two}" - balance_one = get_erc20_balance(client.w3, usdc, receiver_one) - balance_two = get_erc20_balance(client.w3, usdc, receiver_two) + balance_one = get_erc20_balance(smart_account.web3, usdc, receiver_one) + balance_two = get_erc20_balance(smart_account.web3, usdc, receiver_two) auto_tx.run(prompt, non_interactive=True) - new_balance_one = get_erc20_balance(client.w3, usdc, receiver_one) - new_balance_two = get_erc20_balance(client.w3, usdc, receiver_two) + new_balance_one = get_erc20_balance(smart_account.web3, usdc, receiver_one) + new_balance_two = get_erc20_balance(smart_account.web3, usdc, receiver_two) assert balance_one + 2 == new_balance_one assert balance_two + 3 == new_balance_two -def test_send_eth_multiple(configuration, auto_tx, usdc, test_accounts): - (_, _, client, _, _) = configuration +def test_send_eth_multiple(smart_account, auto_tx, usdc, test_accounts): receiver_1 = test_accounts[0] receiver_2 = test_accounts[1] @@ -71,19 +66,19 @@ def test_send_eth_multiple(configuration, auto_tx, usdc, test_accounts): prompt = f"Send 1.3 USDC to {receiver_1}, {receiver_2}, {receiver_3}, {receiver_4} and {receiver_5}" - balance_1 = get_erc20_balance(client.w3, usdc, receiver_1) - balance_2 = get_erc20_balance(client.w3, usdc, receiver_2) - balance_3 = get_erc20_balance(client.w3, usdc, receiver_3) - balance_4 = get_erc20_balance(client.w3, usdc, receiver_4) - balance_5 = get_erc20_balance(client.w3, usdc, receiver_5) + balance_1 = get_erc20_balance(smart_account.web3, usdc, receiver_1) + balance_2 = get_erc20_balance(smart_account.web3, usdc, receiver_2) + balance_3 = get_erc20_balance(smart_account.web3, usdc, receiver_3) + balance_4 = get_erc20_balance(smart_account.web3, usdc, receiver_4) + balance_5 = get_erc20_balance(smart_account.web3, usdc, receiver_5) auto_tx.run(prompt, non_interactive=True) - new_balance_1 = get_erc20_balance(client.w3, usdc, receiver_1) - new_balance_2 = get_erc20_balance(client.w3, usdc, receiver_2) - new_balance_3 = get_erc20_balance(client.w3, usdc, receiver_3) - new_balance_4 = get_erc20_balance(client.w3, usdc, receiver_4) - new_balance_5 = get_erc20_balance(client.w3, usdc, receiver_5) + new_balance_1 = get_erc20_balance(smart_account.web3, usdc, receiver_1) + new_balance_2 = get_erc20_balance(smart_account.web3, usdc, receiver_2) + new_balance_3 = get_erc20_balance(smart_account.web3, usdc, receiver_3) + new_balance_4 = get_erc20_balance(smart_account.web3, usdc, receiver_4) + new_balance_5 = get_erc20_balance(smart_account.web3, usdc, receiver_5) assert balance_1 + 1.3 == new_balance_1 assert balance_2+ 1.3 == new_balance_2 diff --git a/autotx/tests/agents/token/test_swap.py b/autotx/tests/agents/token/test_swap.py index 5fc0271..0999684 100644 --- a/autotx/tests/agents/token/test_swap.py +++ b/autotx/tests/agents/token/test_swap.py @@ -4,75 +4,65 @@ DIFFERENCE_PERCENTAGE = 1.01 -def test_swap_with_non_default_token(configuration, auto_tx): - (_, _, client, manager, _) = configuration - web3 = client.w3 - network_info = NetworkInfo(web3.eth.chain_id) +def test_swap_with_non_default_token(smart_account, auto_tx): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) shib_address = ETHAddress(network_info.tokens["shib"]) prompt = "Buy 100000 SHIB with ETH" - balance = get_erc20_balance(client.w3, shib_address, manager.address) + balance = get_erc20_balance(smart_account.web3, shib_address, smart_account.address) assert balance == 0 auto_tx.run(prompt, non_interactive=True) - new_balance = get_erc20_balance(client.w3, shib_address, manager.address) + new_balance = get_erc20_balance(smart_account.web3, shib_address, smart_account.address) expected_shib_amount = 100000 assert expected_shib_amount <= new_balance <= expected_shib_amount * DIFFERENCE_PERCENTAGE -def test_swap_native(configuration, auto_tx): - (_, _, client, manager, _) = configuration - web3 = client.w3 - network_info = NetworkInfo(web3.eth.chain_id) +def test_swap_native(smart_account, auto_tx): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) prompt = "Buy 100 USDC with ETH" auto_tx.run(prompt, non_interactive=True) - new_balance = get_erc20_balance(client.w3, usdc_address, manager.address) + new_balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) expected_usdc_amount = 100 assert expected_usdc_amount <= new_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE -def test_swap_multiple_1(configuration, auto_tx): - (_, _, client, manager, _) = configuration - web3 = client.w3 - network_info = NetworkInfo(web3.eth.chain_id) +def test_swap_multiple_1(smart_account, auto_tx): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) prompt = "Buy 1000 USDC with ETH and then buy WBTC with 500 USDC" - wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) + wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) auto_tx.run(prompt, non_interactive=True) expected_usdc_amount = 500 - usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) + usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) # 1000 is the amount bought so we need to get the difference from that amount expected_usdc_amount_plus_slippage = 1000 * DIFFERENCE_PERCENTAGE assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount_plus_slippage - expected_usdc_amount - assert wbtc_balance < get_erc20_balance(client.w3, wbtc_address, manager.address) + assert wbtc_balance < get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) -def test_swap_multiple_2(configuration, auto_tx): - (_, _, client, manager, _) = configuration - web3 = client.w3 - network_info = NetworkInfo(web3.eth.chain_id) +def test_swap_multiple_2(smart_account, auto_tx): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) prompt = "Sell ETH for 1000 USDC and then sell 500 USDC for WBTC" - wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) + wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) auto_tx.run(prompt, non_interactive=True) expected_amount = 500 - usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) + usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) assert expected_amount <= usdc_balance - assert wbtc_balance < get_erc20_balance(client.w3, wbtc_address, manager.address) + assert wbtc_balance < get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) -def test_swap_triple(configuration, auto_tx): - (_, _, client, manager, _) = configuration - web3 = client.w3 - network_info = NetworkInfo(web3.eth.chain_id) +def test_swap_triple(smart_account, auto_tx): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) uni_address = ETHAddress(network_info.tokens["uni"]) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) @@ -84,42 +74,42 @@ def test_swap_triple(configuration, auto_tx): expected_usdc_amount = 1 expected_uni_amount = 0.5 expected_wbtc_amount = 0.05 - usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) - uni_balance = get_erc20_balance(client.w3, uni_address, manager.address) - wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) + usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) + uni_balance = get_erc20_balance(smart_account.web3, uni_address, smart_account.address) + wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE assert expected_uni_amount <= uni_balance <= expected_uni_amount * DIFFERENCE_PERCENTAGE assert expected_wbtc_amount <= wbtc_balance <= expected_wbtc_amount * DIFFERENCE_PERCENTAGE -def test_swap_complex_1(configuration, auto_tx): # This one is complex because it confuses the LLM with WBTC amount - (_, _, client, manager, _) = configuration - web3 = client.w3 - network_info = NetworkInfo(web3.eth.chain_id) +def test_swap_complex_1(smart_account, auto_tx): # This one is complex because it confuses the LLM with WBTC amount + (_, _, client, manager, _) = smart_account + smart_account.web3 = smart_account.web3 + network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) prompt = "Swap ETH to 0.05 WBTC, then, swap WBTC to 1000 USDC" - wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) + wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) auto_tx.run(prompt, non_interactive=True) expected_usdc_amount = 1000 - usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) + usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE - assert wbtc_balance < get_erc20_balance(client.w3, wbtc_address, manager.address) + assert wbtc_balance < get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) -def test_swap_complex_2(configuration, auto_tx): # This one is complex because it confuses the LLM with WBTC amount - (_, _, client, manager, _) = configuration - web3 = client.w3 - network_info = NetworkInfo(web3.eth.chain_id) +def test_swap_complex_2(smart_account, auto_tx): # This one is complex because it confuses the LLM with WBTC amount + (_, _, client, manager, _) = smart_account + smart_account.web3 = smart_account.web3 + network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) prompt = "Buy 1000 USDC with ETH, then sell USDC to buy 0.001 WBTC" - usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) + usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) auto_tx.run(prompt, non_interactive=True) - wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) + wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) expected_wbtc_amount = 0.001 assert expected_wbtc_amount <= wbtc_balance <= expected_wbtc_amount * DIFFERENCE_PERCENTAGE - assert usdc_balance < get_erc20_balance(client.w3, usdc_address, manager.address) + assert usdc_balance < get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) diff --git a/autotx/tests/agents/token/test_swap_and_send.py b/autotx/tests/agents/token/test_swap_and_send.py index 225b7d4..adc15b9 100644 --- a/autotx/tests/agents/token/test_swap_and_send.py +++ b/autotx/tests/agents/token/test_swap_and_send.py @@ -4,10 +4,8 @@ DIFFERENCE_PERCENTAGE = 1.01 -def test_swap_and_send_simple(configuration, auto_tx, test_accounts): - (_, _, client, manager, _) = configuration - web3 = client.w3 - network_info = NetworkInfo(web3.eth.chain_id) +def test_swap_and_send_simple(smart_account, auto_tx, test_accounts): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) receiver = test_accounts[0] @@ -16,16 +14,14 @@ def test_swap_and_send_simple(configuration, auto_tx, test_accounts): auto_tx.run(prompt, non_interactive=True) - new_wbtc_safe_address = get_erc20_balance(client.w3, wbtc_address, manager.address) - new_receiver_wbtc_balance = get_erc20_balance(client.w3, wbtc_address, receiver) + new_wbtc_safe_address = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) + new_receiver_wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, receiver) excepted_safe_wbtc_balance = 0.04 assert excepted_safe_wbtc_balance <= new_wbtc_safe_address <= new_wbtc_safe_address * DIFFERENCE_PERCENTAGE assert new_receiver_wbtc_balance == 0.01 -def test_swap_and_send_complex(configuration, auto_tx, test_accounts): - (_, _, client, manager, _) = configuration - web3 = client.w3 - network_info = NetworkInfo(web3.eth.chain_id) +def test_swap_and_send_complex(smart_account, auto_tx, test_accounts): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) @@ -33,36 +29,34 @@ def test_swap_and_send_complex(configuration, auto_tx, test_accounts): prompt = f"Swap ETH to 0.05 WBTC, then, swap WBTC to 1000 USDC and send 50 USDC to {receiver}" - wbtc_safe_address = get_erc20_balance(client.w3, wbtc_address, manager.address) + wbtc_safe_address = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) auto_tx.run(prompt, non_interactive=True) - new_wbtc_safe_address = get_erc20_balance(client.w3, wbtc_address, manager.address) - new_usdc_safe_address = get_erc20_balance(client.w3, usdc_address, manager.address) - new_receiver_usdc_balance = get_erc20_balance(client.w3, usdc_address, receiver) + new_wbtc_safe_address = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) + new_usdc_safe_address = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) + new_receiver_usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, receiver) expected_usdc_safe_balance = 950 assert new_wbtc_safe_address > wbtc_safe_address assert expected_usdc_safe_balance <= new_usdc_safe_address <= expected_usdc_safe_balance * DIFFERENCE_PERCENTAGE assert new_receiver_usdc_balance == 50 -def test_send_and_swap_simple(configuration, auto_tx, test_accounts): - (_, _, client, manager, _) = configuration - web3 = client.w3 - network_info = NetworkInfo(web3.eth.chain_id) +def test_send_and_swap_simple(smart_account, auto_tx, test_accounts): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) receiver = test_accounts[0] prompt = f"Send 0.1 ETH to {receiver}, and then swap ETH to 0.05 WBTC" - receiver_native_balance = get_native_balance(client.w3, receiver) - receiver_wbtc_balance = get_erc20_balance(client.w3, wbtc_address, receiver) + receiver_native_balance = get_native_balance(smart_account.web3, receiver) + receiver_wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, receiver) auto_tx.run(prompt, non_interactive=True) - safe_wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) - new_receiver_native_balance = get_native_balance(client.w3, receiver) - new_receiver_wbtc_balance = get_erc20_balance(client.w3, wbtc_address, receiver) + safe_wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) + new_receiver_native_balance = get_native_balance(smart_account.web3, receiver) + new_receiver_wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, receiver) expected_wbtc_safe_balance = 0.05 assert expected_wbtc_safe_balance <= safe_wbtc_balance <= expected_wbtc_safe_balance * DIFFERENCE_PERCENTAGE @@ -70,10 +64,8 @@ def test_send_and_swap_simple(configuration, auto_tx, test_accounts): assert new_receiver_wbtc_balance == receiver_wbtc_balance assert new_receiver_native_balance == receiver_native_balance + 0.1 -def test_send_and_swap_complex(configuration, auto_tx, test_accounts): - (_, _, client, manager, _) = configuration - web3 = client.w3 - network_info = NetworkInfo(web3.eth.chain_id) +def test_send_and_swap_complex(smart_account, auto_tx, test_accounts): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) @@ -82,19 +74,19 @@ def test_send_and_swap_complex(configuration, auto_tx, test_accounts): prompt = f"Send 0.1 ETH to {receiver_1}, then swap ETH to 0.05 WBTC, then, swap WBTC to 1000 USDC and send 50 USDC to {receiver_2}" - wbtc_safe_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) - receiver_1_native_balance = get_native_balance(client.w3, receiver_1) - receiver_2_usdc_balance = get_erc20_balance(client.w3, usdc_address, receiver_2) + wbtc_safe_balance = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) + receiver_1_native_balance = get_native_balance(smart_account.web3, receiver_1) + receiver_2_usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, receiver_2) auto_tx.run(prompt, non_interactive=True) - new_wbtc_safe_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) - new_usdc_safe_balance = get_erc20_balance(client.w3, usdc_address, manager.address) - new_receiver_1_native_balance = get_native_balance(client.w3, receiver_1) - new_receiver_1_usdc_balance = get_erc20_balance(client.w3, usdc_address, receiver_1) - new_receiver_1_wbtc_balance = get_erc20_balance(client.w3, wbtc_address, receiver_1) - new_receiver_2_wbtc_balance = get_erc20_balance(client.w3, wbtc_address, receiver_2) - new_receiver_2_usdc_balance = get_erc20_balance(client.w3, usdc_address, receiver_2) + new_wbtc_safe_balance = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) + new_usdc_safe_balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) + new_receiver_1_native_balance = get_native_balance(smart_account.web3, receiver_1) + new_receiver_1_usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, receiver_1) + new_receiver_1_wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, receiver_1) + new_receiver_2_wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, receiver_2) + new_receiver_2_usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, receiver_2) expected_usdc_safe_balance = 950 assert expected_usdc_safe_balance <= new_usdc_safe_balance <= expected_usdc_safe_balance * DIFFERENCE_PERCENTAGE assert new_wbtc_safe_balance > wbtc_safe_balance diff --git a/autotx/tests/conftest.py b/autotx/tests/conftest.py index d416ac0..bdedb9b 100644 --- a/autotx/tests/conftest.py +++ b/autotx/tests/conftest.py @@ -3,7 +3,7 @@ from autotx.utils.configuration import AppConfig from autotx.utils.ethereum.helpers.swap_from_eoa import swap from autotx.utils.ethereum.send_native import send_native -from autotx.wallets.safe_smart_wallet import SafeSmartWallet +from autotx.smart_accounts.safe_smart_account import SafeSmartAccount load_dotenv() from autotx.agents.DelegateResearchTokensAgent import DelegateResearchTokensAgent @@ -33,24 +33,23 @@ def start_and_stop_local_fork(): stop() @pytest.fixture() -def configuration(): - app_config = AppConfig.load() - wallet = SafeSmartWallet(app_config.manager, auto_submit_tx=True) +def smart_account() -> SafeSmartAccount: + app_config = AppConfig() + wallet = SafeSmartAccount(app_config.rpc_url, app_config.network_info, auto_submit_tx=True) dev_account = get_dev_account() send_native(dev_account, wallet.address, 10, app_config.web3) - return (dev_account, app_config.agent, app_config.client, app_config.manager, wallet) + return smart_account @pytest.fixture() -def auto_tx(configuration): - (_, _, client, _, wallet) = configuration - network_info = NetworkInfo(client.w3.eth.chain_id) +def auto_tx(smart_account): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) get_llm_config = lambda: { "cache_seed": None, "config_list": [{"model": OPENAI_MODEL_NAME, "api_key": OPENAI_API_KEY}]} return AutoTx( - client.w3, - wallet, + smart_account.web3, + smart_account, network_info, [ SendTokensAgent(), @@ -61,10 +60,8 @@ def auto_tx(configuration): ) @pytest.fixture() -def usdc(configuration) -> ETHAddress: - (user, _, client, _, wallet) = configuration - - chain_id = client.w3.eth.chain_id +def usdc(smart_account) -> ETHAddress: + chain_id = smart_account.web3.eth.chain_id network_info = NetworkInfo(chain_id) eth_address = ETHAddress(network_info.tokens["eth"]) @@ -72,9 +69,10 @@ def usdc(configuration) -> ETHAddress: amount = 100 - swap(client, user, amount, eth_address, usdc_address, network_info.chain_id) + dev_account = get_dev_account() + swap(smart_account.web3, dev_account, amount, eth_address, usdc_address, network_info.chain_id) - transfer_erc20(client.w3, usdc_address, user, wallet.address, amount) + transfer_erc20(smart_account.web3, usdc_address, dev_account, smart_account.address, amount) return usdc_address diff --git a/autotx/tests/integration/test_swap.py b/autotx/tests/integration/test_swap.py index 9185b30..70f9581 100644 --- a/autotx/tests/integration/test_swap.py +++ b/autotx/tests/integration/test_swap.py @@ -8,192 +8,186 @@ DIFFERENCE_PERCENTAGE = 1.01 -def test_buy_one_usdc(configuration): - (_, _, client, manager, _) = configuration - network_info = NetworkInfo(client.w3.eth.chain_id) +def test_buy_one_usdc(smart_account): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) eth_address = ETHAddress(network_info.tokens["eth"]) usdc_address = ETHAddress(network_info.tokens["usdc"]) expected_usdc_amount = 1 buy_usdc_with_eth_transaction = build_swap_transaction( - client.w3, + smart_account.web3., expected_usdc_amount, eth_address, usdc_address, - manager.address, + smart_account.address, False, network_info.chain_id, ) - hash = manager.send_tx(buy_usdc_with_eth_transaction[0].params) - manager.wait(hash) - usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) + hash = smart_account.send_tx(buy_usdc_with_eth_transaction[0].params) + smart_account.wait(hash) + usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE -def test_buy_one_thousand_usdc(configuration): - (_, _, client, manager, _) = configuration - network_info = NetworkInfo(client.w3.eth.chain_id) +def test_buy_one_thousand_usdc(smart_account): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) eth_address = ETHAddress(network_info.tokens["eth"]) usdc_address = ETHAddress(network_info.tokens["usdc"]) expected_usdc_amount = 1000 buy_usdc_with_eth_transaction = build_swap_transaction( - client.w3, + smart_account.web3, expected_usdc_amount, eth_address, usdc_address, - manager.address, + smart_account.address, False, network_info.chain_id, ) print(buy_usdc_with_eth_transaction[0].summary) - hash = manager.send_tx(buy_usdc_with_eth_transaction[0].params) - manager.wait(hash) - usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) + hash = smart_account.send_transaction(buy_usdc_with_eth_transaction[0].params) + smart_account.wait(hash) + usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE -def test_receive_native(configuration): - (_, _, client, manager, _) = configuration +def test_receive_native(smart_account): - network_info = NetworkInfo(client.w3.eth.chain_id) + network_info = NetworkInfo(smart_account.web3.eth.chain_id) eth_address = ETHAddress(network_info.tokens["eth"]) usdc_address = ETHAddress(network_info.tokens["usdc"]) - safe_eth_balance = get_native_balance(client.w3, manager.address) + safe_eth_balance = get_native_balance(smart_account.web3, smart_account.address) assert safe_eth_balance == 10 buy_usdc_with_eth_transaction = build_swap_transaction( - client.w3, + smart_account.web3, 5, eth_address, usdc_address, - manager.address, + smart_account.address, True, network_info.chain_id, ) - hash = manager.send_tx(buy_usdc_with_eth_transaction[0].params) - manager.wait(hash) - safe_eth_balance = get_native_balance(client.w3, manager.address) + hash = smart_account.send_transaction(buy_usdc_with_eth_transaction[0].params) + smart_account.wait(hash) + safe_eth_balance = get_native_balance(smart_account.web3, smart_account.address) assert safe_eth_balance == 5 buy_eth_with_usdc_transaction = build_swap_transaction( - client.w3, + smart_account.web3, 4, usdc_address, eth_address, - manager.address, + smart_account.address, False, network_info.chain_id, ) - hash = manager.send_tx(buy_eth_with_usdc_transaction[0].params) - manager.wait(hash) - hash = manager.send_tx(buy_eth_with_usdc_transaction[1].params) - manager.wait(hash) - safe_eth_balance = get_native_balance(client.w3, manager.address) + hash = smart_account.send_tx(buy_eth_with_usdc_transaction[0].params) + smart_account.wait(hash) + hash = smart_account.send_tx(buy_eth_with_usdc_transaction[1].params) + smart_account.wait(hash) + safe_eth_balance = get_native_balance(smart_account.web3, smart_account.address) assert safe_eth_balance >= 9 -def test_buy_small_amount_wbtc_with_eth(configuration): - (_, _, client, manager, _) = configuration - network_info = NetworkInfo(client.w3.eth.chain_id) +def test_buy_small_amount_wbtc_with_eth(smart_account): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) eth_address = ETHAddress(network_info.tokens["eth"]) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) expected_wbtc_amount = 0.01 buy_wbtc_with_eth_transaction = build_swap_transaction( - client.w3, + smart_account.web3, Decimal(str(expected_wbtc_amount)), eth_address, wbtc_address, - manager.address, + smart_account.address, False, network_info.chain_id, ) - hash = manager.send_tx(buy_wbtc_with_eth_transaction[0].params) - manager.wait(hash) - wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) + hash = smart_account.send_tx(buy_wbtc_with_eth_transaction[0].params) + smart_account.wait(hash) + wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) assert expected_wbtc_amount <= wbtc_balance <= expected_wbtc_amount * DIFFERENCE_PERCENTAGE -def test_buy_big_amount_wbtc_with_eth(configuration): - (_, _, client, manager, _) = configuration - network_info = NetworkInfo(client.w3.eth.chain_id) +def test_buy_big_amount_wbtc_with_eth(smart_account): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) eth_address = ETHAddress(network_info.tokens["eth"]) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) expected_wbtc_amount = 0.1 buy_wbtc_with_eth_transaction = build_swap_transaction( - client.w3, + smart_account.web3, Decimal(str(expected_wbtc_amount)), eth_address, wbtc_address, - manager.address, + smart_account.address, False, network_info.chain_id, ) - hash = manager.send_tx(buy_wbtc_with_eth_transaction[0].params) - manager.wait(hash) - wbtc_balance = get_erc20_balance(client.w3, wbtc_balance, manager.address) + hash = smart_account.send_tx(buy_wbtc_with_eth_transaction[0].params) + smart_account.wait(hash) + wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_balance, smart_account.address) assert expected_wbtc_amount <= wbtc_balance <= expected_wbtc_amount * DIFFERENCE_PERCENTAGE -def test_swap_multiple_tokens(configuration): - (_, _, client, manager, _) = configuration - network_info = NetworkInfo(client.w3.eth.chain_id) +def test_swap_multiple_tokens(smart_account): + network_info = NetworkInfo(smart_account.web3.eth.chain_id) eth_address = ETHAddress(network_info.tokens["eth"]) usdc_address = ETHAddress(network_info.tokens["usdc"]) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) shib_address = ETHAddress(network_info.tokens["shib"]) - usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) + usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) assert usdc_balance == 0 sell_eth_for_usdc_transaction = build_swap_transaction( - client.w3, + smart_account.web3, 1, eth_address, usdc_address, - manager.address, + smart_account.address, True, network_info.chain_id, ) - hash = manager.send_tx(sell_eth_for_usdc_transaction[0].params) - manager.wait(hash) - usdc_balance = get_erc20_balance(client.w3, usdc_address, manager.address) + hash = smart_account.send_tx(sell_eth_for_usdc_transaction[0].params) + smart_account.wait(hash) + usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) assert usdc_balance > 2900 - wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) + wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) assert wbtc_balance == 0 buy_wbtc_with_usdc_transaction = build_swap_transaction( - client.w3, + smart_account.web3, 0.01, usdc_address, wbtc_address, - manager.address, + smart_account.address, False, network_info.chain_id, ) - hash = manager.send_tx(buy_wbtc_with_usdc_transaction[0].params) - manager.wait(hash) - hash = manager.send_tx(buy_wbtc_with_usdc_transaction[1].params) - manager.wait(hash) - wbtc_balance = get_erc20_balance(client.w3, wbtc_address, manager.address) + hash = smart_account.send_tx(buy_wbtc_with_usdc_transaction[0].params) + smart_account.wait(hash) + hash = smart_account.send_tx(buy_wbtc_with_usdc_transaction[1].params) + smart_account.wait(hash) + wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) assert wbtc_balance >= 0.01 - shib_balance = get_erc20_balance(client.w3, shib_address, manager.address) + shib_balance = get_erc20_balance(smart_account.web3, shib_address, smart_account.address) assert shib_balance == 0 sell_wbtc_for_shib = build_swap_transaction( - client.w3, + smart_account.web3, 0.005, wbtc_address, shib_address, - manager.address, + smart_account.address, True, network_info.chain_id, ) - hash = manager.send_tx(sell_wbtc_for_shib[0].params) - manager.wait(hash) - hash = manager.send_tx(sell_wbtc_for_shib[1].params) - manager.wait(hash) - shib_balance = get_erc20_balance(client.w3, shib_address, manager.address) - shib_balance = get_erc20_balance(client.w3, shib_address, manager.address) + hash = smart_account.send_tx(sell_wbtc_for_shib[0].params) + smart_account.wait(hash) + hash = smart_account.send_tx(sell_wbtc_for_shib[1].params) + smart_account.wait(hash) + shib_balance = get_erc20_balance(smart_account.web3, shib_address, smart_account.address) + shib_balance = get_erc20_balance(smart_account.web3, shib_address, smart_account.address) assert shib_balance > 0 diff --git a/autotx/utils/configuration.py b/autotx/utils/configuration.py index 60bd533..c5d5eae 100644 --- a/autotx/utils/configuration.py +++ b/autotx/utils/configuration.py @@ -2,52 +2,25 @@ import sys from time import sleep -from web3 import Web3 +from web3 import HTTPProvider, Web3 from autotx.get_env_vars import get_env_vars -from gnosis.eth import EthereumClient from eth_typing import URI -from eth_account.signers.local import LocalAccount -from autotx.setup import setup_safe -from autotx.utils.ethereum import SafeManager -from autotx.utils.ethereum.agent_account import get_or_create_agent_account from autotx.utils.ethereum.constants import DEVNET_RPC_URL -from autotx.eth_address import ETHAddress from autotx.utils.ethereum.networks import NetworkInfo from autotx.utils.is_dev_env import is_dev_env -from autotx.wallets.smart_wallet import SmartWallet smart_account_addr = get_env_vars() class AppConfig: + rpc_url: str web3: Web3 - client: EthereumClient - agent: LocalAccount - manager: SafeManager network_info: NetworkInfo def __init__( self, - web3: Web3, - client: EthereumClient, - agent: LocalAccount, - manager: SafeManager, - network_info: NetworkInfo, - ): - self.web3 = web3 - self.client = client - self.agent = agent - self.manager = manager - self.network_info = network_info - - @staticmethod - def load( - smart_account_addr: str | None = None, subsidized_chain_id: int | None = None, - fill_dev_account: bool = False, - agent: LocalAccount | None = None, - check_valid_safe: bool = False, - ) -> "AppConfig": + ): rpc_url: str if subsidized_chain_id: @@ -66,10 +39,9 @@ def load( rpc_url = provided_rpc_url - client = EthereumClient(URI(rpc_url)) - + web3 = Web3(HTTPProvider(rpc_url)) for i in range(16): - if client.w3.is_connected(): + if web3.is_connected(): break if i == 15: if is_dev_env(): @@ -78,11 +50,6 @@ def load( sys.exit("Can not connect with remote node. Check your CHAIN_RPC_URL") sleep(0.5) - agent = agent if agent else get_or_create_agent_account() - - smart_account_addr = smart_account_addr if smart_account_addr else os.getenv("SMART_ACCOUNT_ADDRESS") - smart_account = ETHAddress(smart_account_addr) if smart_account_addr else None - - manager = setup_safe(smart_account, agent, client, fill_dev_account, check_valid_safe) - - return AppConfig(client.w3, client, agent, manager, NetworkInfo(client.w3.eth.chain_id)) + self.rpc_url = rpc_url + self.web3 = web3 + self.network_info = NetworkInfo(web3.eth.chain_id) diff --git a/autotx/utils/constants.py b/autotx/utils/constants.py index 09bd2dd..442df41 100644 --- a/autotx/utils/constants.py +++ b/autotx/utils/constants.py @@ -8,4 +8,5 @@ COINGECKO_API_KEY = os.environ.get("COINGECKO_API_KEY", None) LIFI_API_KEY = os.environ.get("LIFI_API_KEY", None) ALCHEMY_API_KEY = os.environ.get("ALCHEMY_API_KEY") -MAINNET_DEFAULT_RPC = f"https://eth-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY}" \ No newline at end of file +MAINNET_DEFAULT_RPC = f"https://eth-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY}" +SMART_ACCOUNT_OWNER_PK = os.environ.get("SMART_ACCOUNT_OWNER_PK", None) \ No newline at end of file diff --git a/autotx/utils/ethereum/helpers/fill_dev_account_with_tokens.py b/autotx/utils/ethereum/helpers/fill_dev_account_with_tokens.py index 8e66679..f3f0211 100644 --- a/autotx/utils/ethereum/helpers/fill_dev_account_with_tokens.py +++ b/autotx/utils/ethereum/helpers/fill_dev_account_with_tokens.py @@ -1,23 +1,23 @@ +from web3 import Web3 from autotx.utils.ethereum import transfer_erc20 from autotx.utils.ethereum.constants import NATIVE_TOKEN_ADDRESS from autotx.eth_address import ETHAddress +from autotx.utils.ethereum.helpers.get_dev_account import get_dev_account from autotx.utils.ethereum.helpers.swap_from_eoa import swap from autotx.utils.ethereum.networks import ChainId, NetworkInfo -from eth_account.signers.local import LocalAccount -from gnosis.eth import EthereumClient from autotx.utils.ethereum.send_native import send_native def fill_dev_account_with_tokens( - client: EthereumClient, - dev_account: LocalAccount, + web3: Web3, safe_address: ETHAddress, network_info: NetworkInfo, ) -> None: + dev_account = get_dev_account() # XDAI or MATIC doesn't have the same value as ETH, so we need to fill more amount_to_fill = 3000 if network_info.chain_id in [ChainId.POLYGON, ChainId.GNOSIS] else 10 - send_native(dev_account, safe_address, amount_to_fill, client.w3) + send_native(dev_account, safe_address, amount_to_fill, web3) tokens_to_transfer = {"usdc": 3500, "dai": 3500, "wbtc": 0.1} if network_info.chain_id is ChainId.GNOSIS: @@ -31,11 +31,11 @@ def fill_dev_account_with_tokens( token_address = ETHAddress(network_info.tokens[token]) amount = tokens_to_transfer[token] swap( - client, + web3, dev_account, amount, native_token_address, token_address, network_info.chain_id, ) - transfer_erc20(client.w3, token_address, dev_account, safe_address, amount) + transfer_erc20(web3, token_address, dev_account, safe_address, amount) diff --git a/autotx/utils/ethereum/helpers/swap_from_eoa.py b/autotx/utils/ethereum/helpers/swap_from_eoa.py index 54a1276..c9cf065 100644 --- a/autotx/utils/ethereum/helpers/swap_from_eoa.py +++ b/autotx/utils/ethereum/helpers/swap_from_eoa.py @@ -1,7 +1,7 @@ from decimal import Decimal from typing import cast from eth_account.signers.local import LocalAccount -from gnosis.eth import EthereumClient +from web3 import Web3 from web3.types import TxParams from autotx.eth_address import ETHAddress @@ -10,7 +10,7 @@ def swap( - client: EthereumClient, + web3: Web3, user: LocalAccount, amount: float, from_token: ETHAddress, @@ -18,7 +18,7 @@ def swap( chain: ChainId, ) -> None: txs = build_swap_transaction( - client.w3, + web3, Decimal(str(amount)), from_token, to_token, @@ -29,19 +29,19 @@ def swap( for tx in txs: del tx.params["gas"] - gas = client.w3.eth.estimate_gas(cast(TxParams, tx.params)) + gas = web3.eth.estimate_gas(cast(TxParams, tx.params)) tx.params.update({"gas": gas}) transaction = user.sign_transaction( # type: ignore { **tx.params, - "nonce": client.w3.eth.get_transaction_count(user.address), + "nonce": web3.eth.get_transaction_count(user.address), } ) - hash = client.w3.eth.send_raw_transaction(transaction.rawTransaction) + hash = web3.eth.send_raw_transaction(transaction.rawTransaction) - receipt = client.w3.eth.wait_for_transaction_receipt(hash) + receipt = web3.eth.wait_for_transaction_receipt(hash) if receipt["status"] == 0: print(f"Transaction to swap {from_token.hex} to {amount} {to_token.hex} failed") diff --git a/autotx/wallets/api_smart_wallet.py b/autotx/wallets/api_smart_wallet.py deleted file mode 100644 index ee2c4b5..0000000 --- a/autotx/wallets/api_smart_wallet.py +++ /dev/null @@ -1,30 +0,0 @@ -from web3 import Web3 -from autotx import db -from autotx.intents import Intent -from autotx.transactions import Transaction -from autotx.utils.ethereum.SafeManager import SafeManager -from autotx.wallets.smart_wallet import SmartWallet - - -class ApiSmartWallet(SmartWallet): - manager: SafeManager - - def __init__(self, web3: Web3, manager: SafeManager, tasks: db.TasksRepository, task_id: str | None = None): - super().__init__(web3, manager.address) - self.task_id = task_id - self.manager = manager - self.tasks = tasks - - def on_intents_prepared(self, intents: list[Intent]) -> None: - if self.task_id is None: - raise ValueError("Task ID is required") - - saved_task = self.tasks.get(self.task_id) - if saved_task is None: - raise ValueError("Task not found") - - saved_task.intents.extend(intents) - self.tasks.update(saved_task) - - async def on_intents_ready(self, _intents: list[Intent]) -> bool | str: - return True \ No newline at end of file diff --git a/autotx/wallets/safe_smart_wallet.py b/autotx/wallets/safe_smart_wallet.py deleted file mode 100644 index d472a35..0000000 --- a/autotx/wallets/safe_smart_wallet.py +++ /dev/null @@ -1,26 +0,0 @@ -from autotx.intents import Intent -from autotx.transactions import TransactionBase -from autotx.utils.ethereum import SafeManager -from autotx.wallets.smart_wallet import SmartWallet - - -class SafeSmartWallet(SmartWallet): - manager: SafeManager - auto_submit_tx: bool - - def __init__(self, manager: SafeManager, auto_submit_tx: bool): - super().__init__(manager.client.w3, manager.address) - - self.manager = manager - self.auto_submit_tx = auto_submit_tx - - def on_intents_prepared(self, intents: list[Intent]) -> None: - pass - - async def on_intents_ready(self, intents: list[Intent]) -> bool | str: - transactions: list[TransactionBase] = [] - - for intent in intents: - transactions.extend(await intent.build_transactions(self.manager.web3, self.manager.network, self.manager.address)) - - return self.manager.send_multisend_tx_batch(transactions, not self.auto_submit_tx) diff --git a/autotx/wallets/smart_wallet.py b/autotx/wallets/smart_wallet.py deleted file mode 100644 index dadcb6d..0000000 --- a/autotx/wallets/smart_wallet.py +++ /dev/null @@ -1,26 +0,0 @@ -from abc import abstractmethod - -from web3 import Web3 -from autotx.intents import Intent -from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance -from autotx.eth_address import ETHAddress -from autotx.utils.ethereum.get_native_balance import get_native_balance - - -class SmartWallet: - def __init__(self, web3: Web3, address: ETHAddress): - self.web3 = web3 - self.address = address - - def on_intents_prepared(self, intents: list[Intent]) -> None: - pass - - @abstractmethod - async def on_intents_ready(self, intents: list[Intent]) -> bool | str: # True if sent, False if declined, str if feedback - pass - - def balance_of(self, token_address: ETHAddress | None = None) -> float: - if token_address is None: - return get_native_balance(self.web3, self.address) - else: - return get_erc20_balance(self.web3, token_address, self.address) \ No newline at end of file From eca4e24ae206dc01bca7eceec879626e56d4b768 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Mon, 24 Jun 2024 20:49:58 +0200 Subject: [PATCH 04/23] smart-account-api docker implementation --- autotx/smart_account_api.py | 33 +++++++++++++++++++++++++++++++++ pyproject.toml | 2 ++ smart-account-api.Dockerfile | 15 +++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 autotx/smart_account_api.py create mode 100644 smart-account-api.Dockerfile diff --git a/autotx/smart_account_api.py b/autotx/smart_account_api.py new file mode 100644 index 0000000..3767b4f --- /dev/null +++ b/autotx/smart_account_api.py @@ -0,0 +1,33 @@ +import subprocess +import sys + +container_name = "smart_account_api" + +def start() -> None: + build = subprocess.run( + ["docker", "build", "-t", "smart_account_api", ".", "-f", "smart-account-api.Dockerfile"], capture_output=True + ) + + if build.returncode != 0: + sys.exit( + "Local node start up has failed. Make sure you have docker desktop installed and running" + ) + + subprocess.run( + [ + "docker", + "run", + "-d", + "--name", + container_name, + "-p", + "7080:7080", + "--env-file", + ".env", + "smart_account_api", + ], + check=True, + ) + +def stop() -> None: + subprocess.run(["docker", "container", "rm", container_name, "-f"], check=True) diff --git a/pyproject.toml b/pyproject.toml index 7678976..a60350c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,8 @@ pytest-vcr = "^1.0.2" [tool.poetry.scripts] start-devnet = "autotx.chain_fork:start" stop-devnet = "autotx.chain_fork:stop" +start-smart-account-api = "autotx.smart_account_api:start" +stop-smart-account-api = "autotx.smart_account_api:stop" ask = "autotx.cli:run" agent = "autotx.cli:agent" serve = "autotx.cli:serve" diff --git a/smart-account-api.Dockerfile b/smart-account-api.Dockerfile new file mode 100644 index 0000000..13e609b --- /dev/null +++ b/smart-account-api.Dockerfile @@ -0,0 +1,15 @@ +FROM node:20 + +WORKDIR /usr/src/app + +ENV APP_HOME /root +WORKDIR $APP_HOME +COPY /smart_account_api $APP_HOME +COPY .env $APP_HOME/.env + +RUN yarn install + +RUN yarn build + +EXPOSE 7080 +CMD ["yarn", "start"] \ No newline at end of file From 56cff6dd9a3dd90e05dc66c86f624a1f05c2804c Mon Sep 17 00:00:00 2001 From: nerfZael Date: Mon, 24 Jun 2024 20:51:47 +0200 Subject: [PATCH 05/23] sending transactions with local biconomy account --- autotx/cli.py | 20 +++++++- .../local_biconomy_smart_account.py | 50 ++++++++++++++++--- autotx/smart_accounts/safe_smart_account.py | 2 +- autotx/tests/agents/token/test_swap.py | 4 -- autotx/tests/conftest.py | 6 +-- autotx/utils/is_dev_env.py | 2 +- 6 files changed, 65 insertions(+), 19 deletions(-) diff --git a/autotx/cli.py b/autotx/cli.py index 77cde3c..8c8da10 100644 --- a/autotx/cli.py +++ b/autotx/cli.py @@ -2,7 +2,8 @@ load_dotenv() from eth_account import Account - +import time +from web3 import Web3 import uuid import uvicorn from typing import cast @@ -10,6 +11,8 @@ import uuid from eth_account.signers.local import LocalAccount +from autotx.utils.ethereum.get_native_balance import get_native_balance +from autotx.utils.ethereum.networks import NetworkInfo from autotx.utils.constants import SMART_ACCOUNT_OWNER_PK from autotx.smart_accounts.safe_smart_account import SafeSmartAccount from autotx.smart_accounts.smart_account import SmartAccount @@ -40,6 +43,15 @@ def print_autotx_info() -> None: def main() -> None: pass +def wait_for_native_top_up(web3: Web3, address: str) -> None: + network = NetworkInfo(web3.eth.chain_id) + + print(f"Detected empty account balance.\nTo use your new smart account, please top it up with some native currency.\nSend the funds to: {address} on {network.chain_id.name}") + print("Waiting...") + while get_native_balance(web3, address) == 0: + time.sleep(2) + print(f"Account balance detected ({get_native_balance(web3, address)}). Ready to use.") + @main.command() @click.argument('prompt', required=False) @click.option("-n", "--non-interactive", is_flag=True, help="Non-interactive mode (will not expect further user input or approval)") @@ -55,11 +67,15 @@ def run(prompt: str | None, non_interactive: bool, verbose: bool, logs: str | No app_config = AppConfig() wallet: SmartAccount - if SMART_ACCOUNT_OWNER_PK is not None: + if SMART_ACCOUNT_OWNER_PK: smart_account_owner = cast(LocalAccount, Account.from_key(SMART_ACCOUNT_OWNER_PK)) wallet = LocalBiconomySmartAccount(app_config.web3, smart_account_owner, auto_submit_tx=non_interactive) + print(f"Using Biconomy smart account: {wallet.address}") + if get_native_balance(app_config.web3, wallet.address) == 0: + wait_for_native_top_up(app_config.web3, wallet.address) else: wallet = SafeSmartAccount(app_config.rpc_url, app_config.network_info, auto_submit_tx=non_interactive, fill_dev_account=True) + print("Using Safe smart account: {wallet.address}") (get_llm_config, agents, logs_dir) = setup_agents(logs, cache) diff --git a/autotx/smart_accounts/local_biconomy_smart_account.py b/autotx/smart_accounts/local_biconomy_smart_account.py index f59f3a2..532efb1 100644 --- a/autotx/smart_accounts/local_biconomy_smart_account.py +++ b/autotx/smart_accounts/local_biconomy_smart_account.py @@ -3,9 +3,11 @@ from eth_account.signers.local import LocalAccount from web3 import Web3 +from autotx.eth_address import ETHAddress from autotx.intents import Intent from autotx.transactions import TransactionBase from autotx.smart_accounts.smart_account import SmartAccount +from autotx.utils.ethereum.networks import NetworkInfo class LocalBiconomySmartAccount(SmartAccount): web3: Web3 @@ -13,32 +15,64 @@ class LocalBiconomySmartAccount(SmartAccount): auto_submit_tx: bool def __init__(self, web3: Web3, owner: LocalAccount, auto_submit_tx: bool): - super().__init__(web3, owner.address) - self.web3 = web3 self.owner = owner self.auto_submit_tx = auto_submit_tx + address = self.get_address() + super().__init__(web3, ETHAddress(address)) def on_intents_prepared(self, intents: list[Intent]) -> None: pass async def on_intents_ready(self, intents: list[Intent]) -> bool | str: - return False + if self.auto_submit_tx: + if not intents: + return False + + transactions: list[TransactionBase] = [] + + for intent in intents: + transactions.extend(await intent.build_transactions(self.web3, NetworkInfo(self.web3.eth.chain_id), self.address)) + + dict_transactions = [json.loads(transaction.json()) for transaction in transactions] + + self.send_transactions(dict_transactions) + + return True + else: + return False + + def get_address(self) -> str: + response = requests.get( + f"http://localhost:7080/api/v1/account/address?chainId={self.web3.eth.chain_id}", + headers={"Content-Type": "application/json"}, + ) + + if response.status_code != 200: + raise ValueError(f"Failed to get address: Biconomy API internal error") + + return response.json() def send_transaction(self, transaction: TransactionBase) -> None: response = requests.post( - f"http://localhost:3000/api/v1/account/transactions?account={self.owner.address}&chainId={self.web3.eth.chain_id}", - json=json.dumps([transaction]), + f"http://localhost:7080/api/v1/account/transactions?chainId={self.web3.eth.chain_id}", + headers={"Content-Type": "application/json"}, + data=json.dumps([transaction]), ) if response.status_code != 200: raise ValueError(f"Transaction failed: {response.json()}") + print(f"Transaction sent: {response.json()}") + def send_transactions(self, transactions: list[TransactionBase]) -> None: response = requests.post( - f"http://localhost:3000/api/v1/account/transactions?account={self.owner.address}&chainId={self.web3.eth.chain_id}", - json=json.dumps(transactions), + f"http://localhost:7080/api/v1/account/transactions?chainId={self.web3.eth.chain_id}", + headers={"Content-Type": "application/json"}, + data=json.dumps(transactions) ) if response.status_code != 200: - raise ValueError(f"Transaction failed: {response.json()}") + raise ValueError(f"Transaction failed: Biconomy API internal error") + + print(f"Transaction sent: {response.json()}") \ No newline at end of file diff --git a/autotx/smart_accounts/safe_smart_account.py b/autotx/smart_accounts/safe_smart_account.py index d5611b6..2e2fb2a 100644 --- a/autotx/smart_accounts/safe_smart_account.py +++ b/autotx/smart_accounts/safe_smart_account.py @@ -60,7 +60,7 @@ async def on_intents_ready(self, intents: list[Intent]) -> bool | str: transactions: list[TransactionBase] = [] for intent in intents: - transactions.extend(await intent.build_transactions(self.manager.web3, self.manager.network, self.manager.address)) + transactions.extend(await intent.build_transactions(self.web3, self.manager.network, self.address)) return self.manager.send_multisend_tx_batch(transactions, not self.auto_submit_tx) diff --git a/autotx/tests/agents/token/test_swap.py b/autotx/tests/agents/token/test_swap.py index 0999684..2443d09 100644 --- a/autotx/tests/agents/token/test_swap.py +++ b/autotx/tests/agents/token/test_swap.py @@ -82,8 +82,6 @@ def test_swap_triple(smart_account, auto_tx): assert expected_wbtc_amount <= wbtc_balance <= expected_wbtc_amount * DIFFERENCE_PERCENTAGE def test_swap_complex_1(smart_account, auto_tx): # This one is complex because it confuses the LLM with WBTC amount - (_, _, client, manager, _) = smart_account - smart_account.web3 = smart_account.web3 network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) @@ -98,8 +96,6 @@ def test_swap_complex_1(smart_account, auto_tx): # This one is complex because i assert wbtc_balance < get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) def test_swap_complex_2(smart_account, auto_tx): # This one is complex because it confuses the LLM with WBTC amount - (_, _, client, manager, _) = smart_account - smart_account.web3 = smart_account.web3 network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) diff --git a/autotx/tests/conftest.py b/autotx/tests/conftest.py index bdedb9b..374de96 100644 --- a/autotx/tests/conftest.py +++ b/autotx/tests/conftest.py @@ -35,12 +35,12 @@ def start_and_stop_local_fork(): @pytest.fixture() def smart_account() -> SafeSmartAccount: app_config = AppConfig() - wallet = SafeSmartAccount(app_config.rpc_url, app_config.network_info, auto_submit_tx=True) + account = SafeSmartAccount(app_config.rpc_url, app_config.network_info, auto_submit_tx=True) dev_account = get_dev_account() - send_native(dev_account, wallet.address, 10, app_config.web3) + send_native(dev_account, account.address, 10, app_config.web3) - return smart_account + return account @pytest.fixture() def auto_tx(smart_account): diff --git a/autotx/utils/is_dev_env.py b/autotx/utils/is_dev_env.py index 8ad9473..ed096cb 100644 --- a/autotx/utils/is_dev_env.py +++ b/autotx/utils/is_dev_env.py @@ -1,4 +1,4 @@ import os def is_dev_env() -> bool: - return not os.getenv("SMART_ACCOUNT_ADDRESS") \ No newline at end of file + return not os.getenv("SMART_ACCOUNT_ADDRESS") and not os.getenv("SMART_ACCOUNT_OWNER_PK") \ No newline at end of file From e352897c3a1dd0e4c38bdc2d34dfbbd59401c8da Mon Sep 17 00:00:00 2001 From: nerfZael Date: Mon, 24 Jun 2024 20:53:19 +0200 Subject: [PATCH 06/23] initial biconomy api implementation --- smart_account_api/src/account-abstraction.ts | 27 +++ smart_account_api/src/constants.ts | 9 + smart_account_api/src/index.ts | 172 ++++++++----------- smart_account_api/tsconfig.json | 100 +---------- 4 files changed, 110 insertions(+), 198 deletions(-) create mode 100644 smart_account_api/src/account-abstraction.ts create mode 100644 smart_account_api/src/constants.ts diff --git a/smart_account_api/src/account-abstraction.ts b/smart_account_api/src/account-abstraction.ts new file mode 100644 index 0000000..600d0cc --- /dev/null +++ b/smart_account_api/src/account-abstraction.ts @@ -0,0 +1,27 @@ +import { BiconomySmartAccountV2, createSmartAccountClient } from "@biconomy/account"; +import { WalletClient, createWalletClient, Hex, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { getNetwork } from "./networks"; + +export async function initClientWithAccount(ownerPk: string, chainId: number): Promise<{ + client: WalletClient; + smartAccount: BiconomySmartAccountV2; + }> { + const { chain, bundlerUrl } = getNetwork(chainId); + + const client = createWalletClient({ + account: privateKeyToAccount(ownerPk as Hex), + chain: chain, + transport: http(), + }); + + const smartAccount = await createSmartAccountClient({ + signer: client, + bundlerUrl, + }); + + return { + client, + smartAccount, + } +}; diff --git a/smart_account_api/src/constants.ts b/smart_account_api/src/constants.ts new file mode 100644 index 0000000..4fc4497 --- /dev/null +++ b/smart_account_api/src/constants.ts @@ -0,0 +1,9 @@ +export const CHAIN_RPC_URL = process.env.CHAIN_RPC_URL as string; +export const SMART_ACCOUNT_OWNER_PK = process.env.SMART_ACCOUNT_OWNER_PK as string; +if (!CHAIN_RPC_URL) { + throw new Error("CHAIN_RPC_URL is not set"); +} + +if (!SMART_ACCOUNT_OWNER_PK) { + throw new Error("SMART_ACCOUNT_OWNER_PK is not set"); +} diff --git a/smart_account_api/src/index.ts b/smart_account_api/src/index.ts index 33d772f..423bb1d 100644 --- a/smart_account_api/src/index.ts +++ b/smart_account_api/src/index.ts @@ -1,127 +1,73 @@ -import express, { Express, Request, Response } from "express"; +import express, { Express, NextFunction, Request, Response } from "express"; import dotenv from "dotenv"; -import { - Account, - Address, - Hex, - WalletClient, - createWalletClient, - http, -} from "viem"; -import { polygon, sepolia } from "viem/chains"; -import { BiconomySmartAccountV2, createSmartAccountClient, Transaction } from "@biconomy/account"; -import { createGasEstimator } from "entry-point-gas-estimations"; -import { GasEstimator } from "entry-point-gas-estimations/dist/gas-estimator/entry-point-v6/GasEstimator/GasEstimator"; +import { Hex, Transaction } from "@biconomy/account"; +import { initClientWithAccount } from "./account-abstraction"; +import { SMART_ACCOUNT_OWNER_PK } from "./constants"; +import { privateKeyToAccount } from "viem/accounts"; dotenv.config(); -const bundlerUrl = process.env.BUNDLER_URL || "http://localhost:3001"; - const app: Express = express(); -const port = process.env.PORT || 3000; +const port = process.env.PORT || 7080; -app.listen(port, () => { - console.log(`[server]: Server is running at http://localhost:${port}`); -}); +app.use(express.json()); + +app.all('*', handleError(async (req: Request, res: Response, next: NextFunction) => { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); -function getNetwork(chainId: string): { - bundlerUrl: string; - rpcUrl: string; - gasEstimator: GasEstimator; - chain: any; -} { - switch (chainId) { - case "137": - return { - bundlerUrl, - rpcUrl: polygonRpcUrl, - gasEstimator: createGasEstimator({ - rpcUrl: polygonRpcUrl, - }), - chain: polygon, - }; - case "31337": - return { - bundlerUrl, - rpcUrl: sepoliaRpcUrl, - gasEstimator: createGasEstimator({ - rpcUrl: sepoliaRpcUrl, - }), - chain: sepolia, - }; - default: - throw new Error("Invalid network"); + //Trim and redirect multiple slashes in URL + if (req.url.match(/[/]{2,}/g)) { + req.url = req.url.replace(/[/]+/g, '/'); + res.redirect(req.url); + return; } -} -type CustomTransactionType = "send" | "approve" | "swap"; -type CustomTransaction = { - type: CustomTransactionType; - summary: string; - params: { - from: string; - to: string; - value: string; - data: string; - }; -}; + if (req.method === 'OPTIONS') { + res.send(200); + } else { + console.log(`Request: ${req.method} --- ${req.url}`); + next(); + } +})); -async function initAA(owner: Account | Address, chainId: string): Promise<{ - client: WalletClient; - smartAccount: BiconomySmartAccountV2; -}> { - const { chain, bundlerUrl } = getNetwork(chainId); - - const client = createWalletClient({ - account: owner, - chain: chain, - transport: http(), - }); - - const smartAccount = await createSmartAccountClient({ - signer: client, - bundlerUrl, +app.use((req: Request, res: Response, next: NextFunction) => { + res.on('finish', () => { + console.log(`Response: ${req.method} ${res.statusCode} ${req.url}`); }); + next(); +}); - return { - client, - smartAccount, - } -}; +app.get("/api/v1/account/address", handleError(async (req: Request, res: Response) => { + const chainId = parseInt(req.query.chainId as string); -app.get("/api/v1/account/address", async (req: Request, res: Response) => { - const owner: Address = req.query.owner as Address; - const chainId = req.query.chainId as string; + const { smartAccount } = await initClientWithAccount(SMART_ACCOUNT_OWNER_PK, chainId); - const { smartAccount } = await initAA(owner, chainId); - const smartAccountAddress = await smartAccount.getAccountAddress(); const result = smartAccountAddress; res.json(result); -}); +})); -app.post("/api/v1/account/deploy", async (req: Request, res: Response) => { - const owner: Address = req.query.owner as Address; - const chainId = req.query.chainId as string; +app.post("/api/v1/account/deploy", handleError(async (req: Request, res: Response) => { + const chainId = parseInt(req.query.chainId as string); - const { smartAccount } = await initAA(owner, chainId); + const { smartAccount } = await initClientWithAccount(SMART_ACCOUNT_OWNER_PK, chainId); const response = await smartAccount.deploy(); - const receipt = await response.waitForTxHash(); + const receipt = await response.wait(); - res.json(receipt.transactionHash); -}); + res.json(receipt.receipt.transactionHash) +})); -app.post("/api/v1/account/transactions", async (req: Request, res: Response) => { - const owner: Address = req.query.owner as Address; - const chainId = req.query.chainId as string; +app.post("/api/v1/account/transactions", handleError(async (req: Request, res: Response, next: NextFunction) => { + const chainId = parseInt(req.query.chainId as string); - const transactions: CustomTransaction[] = req.body.transactions; + const transactions: TransactionDto[] = req.body; - const { smartAccount } = await initAA(owner, chainId); + const { smartAccount } = await initClientWithAccount(SMART_ACCOUNT_OWNER_PK, chainId); const txs: Transaction[] = transactions.map((tx) => { return { @@ -131,8 +77,36 @@ app.post("/api/v1/account/transactions", async (req: Request, res: Response) => }; }); + console.log(txs); + const response = await smartAccount.sendTransaction(txs); - const receipt = await response.waitForTxHash(); - - res.json(receipt.transactionHash); + + console.log(response); + + const receipt = await response.wait(); + + res.json(receipt.receipt.transactionHash); +})); + +app.listen(port, () => { + console.log(`[server]: Server is running at http://localhost:${port}`); }); + +type TransactionType = "send" | "approve" | "swap"; +type TransactionDto = { + type: TransactionType; + summary: string; + params: { + from: string; + to: string; + value: string; + data: string; + }; +}; + +export function handleError(callback: (req: Request<{}>, res: Response, next: NextFunction) => Promise) { + return function (req: Request<{}>, res: Response, next: NextFunction) { + callback(req, res, next) + .catch(next) + } +} diff --git a/smart_account_api/tsconfig.json b/smart_account_api/tsconfig.json index e075f97..71423c5 100644 --- a/smart_account_api/tsconfig.json +++ b/smart_account_api/tsconfig.json @@ -1,109 +1,11 @@ { "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ + "outDir": "./dist", "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ "module": "commonjs", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ } } From 27d67ef778a31f5f1a225fff8e368bc09ce9913d Mon Sep 17 00:00:00 2001 From: nerfZael Date: Mon, 24 Jun 2024 20:53:37 +0200 Subject: [PATCH 07/23] updated readme with biconomy instructions --- Biconomy.md | 23 +++++++++++++++++++++++ README.md | 2 ++ 2 files changed, 25 insertions(+) create mode 100644 Biconomy.md diff --git a/Biconomy.md b/Biconomy.md new file mode 100644 index 0000000..0bc9f85 --- /dev/null +++ b/Biconomy.md @@ -0,0 +1,23 @@ +# Biconomy Smart Accounts + +## Description +AutoTx can be used with a Biconomy Smart Account. + +## Getting started +First, add the private key of the account you want to use as the owner to the `.env` file. Use the variable `SMART_ACCOUNT_OWNER_PK`. +Since the private key is stored as plain text, it is recommended to use a test account. + +Next, start the `smart_account_api`, run: `poetry run start-smart-account-api`. This API server will be used to interact with the Biconomy Smart Account. +You can run `poetry run stop-smart-account-api` to stop the server. + +To start AutoTx, you can now run: `poetry run ask "my-prompt-here"`. + +When running for the first time, you will be prompted to send funds to your new Biconomy Smart Account in order to pay for the gas fees. +``` +Using Biconomy smart account: {SMART-ACCOUNT-ADDRESS} +Detected empty account balance. +To use your new smart account, please top it up with some native currency. +Send the funds to: {SMART-ACCOUNT-ADDRESS} on {NETWORK-NAME} +Waiting... +``` +After sending the funds, AutoTx will automatically detect the balance and continue with the transaction. \ No newline at end of file diff --git a/README.md b/README.md index 8b63362..40169dd 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,8 @@ python benchmarks.py ./autotx/tests/file_name.py::function_name 5 # run a specific test with 5 iterations and name the output folder (instead of the default timestamp) python benchmarks.py ./autotx/tests/file_name.py::function_name 5 output_folder_name ``` +# Biconomy Smart Accounts +To view the Biconomy Smart Accounts documentation, please see the [Biconomy.md](./Biconomy.md) file. # API Server To view the API server documentation, please see the [API.md](./API.md) file. From 0f45fbbd3b872f0c4d453c0624e824334570fa34 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Mon, 24 Jun 2024 20:55:17 +0200 Subject: [PATCH 08/23] added network with bundler url --- smart_account_api/src/networks.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 smart_account_api/src/networks.ts diff --git a/smart_account_api/src/networks.ts b/smart_account_api/src/networks.ts new file mode 100644 index 0000000..be0407b --- /dev/null +++ b/smart_account_api/src/networks.ts @@ -0,0 +1,24 @@ +import { createGasEstimator } from "entry-point-gas-estimations"; +import { GasEstimator } from "entry-point-gas-estimations/dist/gas-estimator/entry-point-v6/GasEstimator/GasEstimator"; +import { CHAIN_RPC_URL } from "./constants"; +import { getChain } from "@biconomy/account"; + +export function getNetwork(chainId: number): { + bundlerUrl: string; + rpcUrl: string; + gasEstimator: GasEstimator; + chain: any; + } { + return { + bundlerUrl: getBundlerUrl(chainId), + rpcUrl: CHAIN_RPC_URL, + gasEstimator: createGasEstimator({ + rpcUrl: CHAIN_RPC_URL, + }), + chain: getChain(chainId), + }; + } + +function getBundlerUrl(chainId: number): string { + return `https://bundler.biconomy.io/api/v2/${chainId}/dicj2189.wh1269hU-7Z42-45ic-af42-h7VjQ2wNs`; +} From d03dc7047dc2878251f8b18af7792553d22e6d7d Mon Sep 17 00:00:00 2001 From: nerfZael Date: Mon, 24 Jun 2024 21:13:57 +0200 Subject: [PATCH 09/23] typing fixes --- autotx/cli.py | 4 +++- autotx/smart_accounts/local_biconomy_smart_account.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/autotx/cli.py b/autotx/cli.py index 8c8da10..2fe1124 100644 --- a/autotx/cli.py +++ b/autotx/cli.py @@ -1,4 +1,6 @@ from dotenv import load_dotenv + +from autotx.eth_address import ETHAddress load_dotenv() from eth_account import Account @@ -43,7 +45,7 @@ def print_autotx_info() -> None: def main() -> None: pass -def wait_for_native_top_up(web3: Web3, address: str) -> None: +def wait_for_native_top_up(web3: Web3, address: ETHAddress) -> None: network = NetworkInfo(web3.eth.chain_id) print(f"Detected empty account balance.\nTo use your new smart account, please top it up with some native currency.\nSend the funds to: {address} on {network.chain_id.name}") diff --git a/autotx/smart_accounts/local_biconomy_smart_account.py b/autotx/smart_accounts/local_biconomy_smart_account.py index 532efb1..49e1312 100644 --- a/autotx/smart_accounts/local_biconomy_smart_account.py +++ b/autotx/smart_accounts/local_biconomy_smart_account.py @@ -1,4 +1,5 @@ import json +from typing import cast import requests from eth_account.signers.local import LocalAccount from web3 import Web3 @@ -51,7 +52,7 @@ def get_address(self) -> str: if response.status_code != 200: raise ValueError(f"Failed to get address: Biconomy API internal error") - return response.json() + return cast(str, response.json()) def send_transaction(self, transaction: TransactionBase) -> None: response = requests.post( From 8197f6be63b23fc1a1b0482d120369d2d033f2e4 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Thu, 27 Jun 2024 17:05:46 +0200 Subject: [PATCH 10/23] swapping is now async --- autotx/agents/SwapTokensAgent.py | 3 +- autotx/cli.py | 5 +- autotx/load_tokens.py | 13 +- autotx/server.py | 4 +- autotx/smart_accounts/api_smart_account.py | 8 +- .../local_biconomy_smart_account.py | 34 ++- autotx/smart_accounts/safe_smart_account.py | 4 +- autotx/smart_accounts/smart_account.py | 4 +- autotx/tests/conftest.py | 10 +- autotx/tests/integration/test_swap.py | 25 +- autotx/utils/ethereum/lifi/__init__.py | 53 ++-- autotx/utils/ethereum/lifi/swap.py | 62 +--- autotx/utils/http_requests.py | 11 + poetry.lock | 274 +++++++++--------- pyproject.toml | 2 +- 15 files changed, 250 insertions(+), 262 deletions(-) create mode 100644 autotx/utils/http_requests.py diff --git a/autotx/agents/SwapTokensAgent.py b/autotx/agents/SwapTokensAgent.py index 895f541..012e222 100644 --- a/autotx/agents/SwapTokensAgent.py +++ b/autotx/agents/SwapTokensAgent.py @@ -171,7 +171,8 @@ async def run( for swap_str in swaps: (token_to_sell, token_to_buy) = swap_str.strip().split(" to ") try: - all_intents.append(await swap(autotx, token_to_sell, token_to_buy)) + intent = await swap(autotx, token_to_sell, token_to_buy) + all_intents.append(intent) except InvalidInput as e: all_errors.append(e) except Exception as e: diff --git a/autotx/cli.py b/autotx/cli.py index 2fe1124..c15c2c6 100644 --- a/autotx/cli.py +++ b/autotx/cli.py @@ -1,6 +1,4 @@ from dotenv import load_dotenv - -from autotx.eth_address import ETHAddress load_dotenv() from eth_account import Account @@ -13,6 +11,7 @@ import uuid from eth_account.signers.local import LocalAccount +from autotx.eth_address import ETHAddress from autotx.utils.ethereum.get_native_balance import get_native_balance from autotx.utils.ethereum.networks import NetworkInfo from autotx.utils.constants import SMART_ACCOUNT_OWNER_PK @@ -77,7 +76,7 @@ def run(prompt: str | None, non_interactive: bool, verbose: bool, logs: str | No wait_for_native_top_up(app_config.web3, wallet.address) else: wallet = SafeSmartAccount(app_config.rpc_url, app_config.network_info, auto_submit_tx=non_interactive, fill_dev_account=True) - print("Using Safe smart account: {wallet.address}") + print(f"Using Safe smart account: {wallet.address}") (get_llm_config, agents, logs_dir) = setup_agents(logs, cache) diff --git a/autotx/load_tokens.py b/autotx/load_tokens.py index f3ecec4..a6cf6db 100644 --- a/autotx/load_tokens.py +++ b/autotx/load_tokens.py @@ -2,7 +2,7 @@ from textwrap import dedent from typing import Union -import requests +from autotx.utils import http_requests KLEROS_TOKENS_LIST = "https://t2crtokens.eth.link/" COINGECKO_TOKENS_LISTS = [ @@ -19,13 +19,14 @@ TOKENS_LIST = [KLEROS_TOKENS_LIST, *COINGECKO_TOKENS_LISTS] -def fetch_tokens_list() -> None: +async def fetch_tokens_list() -> None: loaded_tokens: list[dict[str, Union[str, int]]] = [] for token_list_url in TOKENS_LIST: try: - response = requests.get(token_list_url) - tokens = json.loads(response.text)["tokens"] + response = await http_requests.get(token_list_url) + result = await response.json() + tokens = result["tokens"] loaded_tokens.extend(tokens) except: print("Error while trying to fetch list:", token_list_url) @@ -41,5 +42,5 @@ def fetch_tokens_list() -> None: f.write(content) -def run() -> None: - fetch_tokens_list() +async def run() -> None: + await fetch_tokens_list() diff --git a/autotx/server.py b/autotx/server.py index 9323f89..1742381 100644 --- a/autotx/server.py +++ b/autotx/server.py @@ -294,7 +294,7 @@ async def prepare_transactions( return PreparedTransactionsDto(batch_id=submitted_batch_id, transactions=transactions) @app_router.post("/api/v1/tasks/{task_id}/transactions") -def send_transactions( +async def send_transactions( task_id: str, address: str, chain_id: int, @@ -332,7 +332,7 @@ def send_transactions( app_config = AppConfig(subsidized_chain_id=chain_id) wallet = load_wallet_for_user(app_config, app.id, user_id, address) - wallet.send_transactions(transactions) + await wallet.send_transactions(transactions) except SafeAPIException as e: if "is not an owner or delegate" in str(e): raise HTTPException(status_code=400, detail="Agent is not an owner or delegate") diff --git a/autotx/smart_accounts/api_smart_account.py b/autotx/smart_accounts/api_smart_account.py index 4b82391..0f83920 100644 --- a/autotx/smart_accounts/api_smart_account.py +++ b/autotx/smart_accounts/api_smart_account.py @@ -30,8 +30,8 @@ def on_intents_prepared(self, intents: list[Intent]) -> None: async def on_intents_ready(self, _intents: list[Intent]) -> bool | str: return True - def send_transaction(self, transaction: TransactionBase) -> None: - self.wallet.send_transaction(transaction) + async def send_transaction(self, transaction: TransactionBase) -> None: + await self.wallet.send_transaction(transaction) - def send_transactions(self, transactions: list[TransactionBase]) -> None: - self.wallet.send_transactions(transactions) \ No newline at end of file + async def send_transactions(self, transactions: list[TransactionBase]) -> None: + await self.wallet.send_transactions(transactions) \ No newline at end of file diff --git a/autotx/smart_accounts/local_biconomy_smart_account.py b/autotx/smart_accounts/local_biconomy_smart_account.py index 49e1312..ee64486 100644 --- a/autotx/smart_accounts/local_biconomy_smart_account.py +++ b/autotx/smart_accounts/local_biconomy_smart_account.py @@ -1,6 +1,7 @@ +import asyncio import json from typing import cast -import requests + from eth_account.signers.local import LocalAccount from web3 import Web3 @@ -8,6 +9,7 @@ from autotx.intents import Intent from autotx.transactions import TransactionBase from autotx.smart_accounts.smart_account import SmartAccount +from autotx.utils import http_requests from autotx.utils.ethereum.networks import NetworkInfo class LocalBiconomySmartAccount(SmartAccount): @@ -19,7 +21,7 @@ def __init__(self, web3: Web3, owner: LocalAccount, auto_submit_tx: bool): self.web3 = web3 self.owner = owner self.auto_submit_tx = auto_submit_tx - address = self.get_address() + address = asyncio.run(self.get_address()) super().__init__(web3, ETHAddress(address)) def on_intents_prepared(self, intents: list[Intent]) -> None: @@ -37,43 +39,43 @@ async def on_intents_ready(self, intents: list[Intent]) -> bool | str: dict_transactions = [json.loads(transaction.json()) for transaction in transactions] - self.send_transactions(dict_transactions) + await self.send_transactions(dict_transactions) return True else: return False - def get_address(self) -> str: - response = requests.get( + async def get_address(self) -> str: + response = await http_requests.get( f"http://localhost:7080/api/v1/account/address?chainId={self.web3.eth.chain_id}", headers={"Content-Type": "application/json"}, ) - if response.status_code != 200: + if response.status != 200: raise ValueError(f"Failed to get address: Biconomy API internal error") - return cast(str, response.json()) + return cast(str, await response.json()) - def send_transaction(self, transaction: TransactionBase) -> None: - response = requests.post( + async def send_transaction(self, transaction: TransactionBase) -> None: + response = await http_requests.post( f"http://localhost:7080/api/v1/account/transactions?chainId={self.web3.eth.chain_id}", headers={"Content-Type": "application/json"}, data=json.dumps([transaction]), ) - if response.status_code != 200: - raise ValueError(f"Transaction failed: {response.json()}") + if response.status != 200: + raise ValueError(f"Transaction failed: {await response.json()}") - print(f"Transaction sent: {response.json()}") + print(f"Transaction sent: {await response.json()}") - def send_transactions(self, transactions: list[TransactionBase]) -> None: - response = requests.post( + async def send_transactions(self, transactions: list[TransactionBase]) -> None: + response = await http_requests.post( f"http://localhost:7080/api/v1/account/transactions?chainId={self.web3.eth.chain_id}", headers={"Content-Type": "application/json"}, data=json.dumps(transactions) ) - if response.status_code != 200: + if response.status != 200: raise ValueError(f"Transaction failed: Biconomy API internal error") - print(f"Transaction sent: {response.json()}") \ No newline at end of file + print(f"Transaction sent: {await response.json()}") \ No newline at end of file diff --git a/autotx/smart_accounts/safe_smart_account.py b/autotx/smart_accounts/safe_smart_account.py index 2e2fb2a..b5aedac 100644 --- a/autotx/smart_accounts/safe_smart_account.py +++ b/autotx/smart_accounts/safe_smart_account.py @@ -64,10 +64,10 @@ async def on_intents_ready(self, intents: list[Intent]) -> bool | str: return self.manager.send_multisend_tx_batch(transactions, not self.auto_submit_tx) - def send_transaction(self, transaction: TransactionBase) -> None: + async def send_transaction(self, transaction: TransactionBase) -> None: self.manager.send_multisend_tx_batch([transaction], require_approval=False) - def send_transactions(self, transactions: list[TransactionBase]) -> None: + async def send_transactions(self, transactions: list[TransactionBase]) -> None: self.manager.send_multisend_tx_batch( transactions, require_approval=False, diff --git a/autotx/smart_accounts/smart_account.py b/autotx/smart_accounts/smart_account.py index dd0bfac..646d0ac 100644 --- a/autotx/smart_accounts/smart_account.py +++ b/autotx/smart_accounts/smart_account.py @@ -24,11 +24,11 @@ async def on_intents_ready(self, intents: list[Intent]) -> bool | str: # True if pass @abstractmethod - def send_transaction(self, transaction: TransactionBase) -> None: + async def send_transaction(self, transaction: TransactionBase) -> None: pass @abstractmethod - def send_transactions(self, transactions: list[TransactionBase]) -> None: + async def send_transactions(self, transactions: list[TransactionBase]) -> None: pass def wait(self, tx_hash: HexBytes) -> TxReceipt: diff --git a/autotx/tests/conftest.py b/autotx/tests/conftest.py index e8d176c..a70a27b 100644 --- a/autotx/tests/conftest.py +++ b/autotx/tests/conftest.py @@ -1,23 +1,19 @@ from dotenv import load_dotenv +load_dotenv() +import pytest +from eth_account import Account from autotx.utils.configuration import AppConfig from autotx.utils.ethereum.helpers.swap_from_eoa import swap from autotx.utils.ethereum.send_native import send_native from autotx.smart_accounts.safe_smart_account import SafeSmartAccount -load_dotenv() - from autotx.agents.DelegateResearchTokensAgent import DelegateResearchTokensAgent from autotx.agents.SendTokensAgent import SendTokensAgent from autotx.agents.SwapTokensAgent import SwapTokensAgent - -from eth_account import Account - from autotx.utils.constants import OPENAI_API_KEY, OPENAI_MODEL_NAME from autotx.utils.ethereum.networks import NetworkInfo from autotx.eth_address import ETHAddress from autotx.utils.ethereum.helpers.get_dev_account import get_dev_account - -import pytest from autotx.AutoTx import AutoTx, Config from autotx.chain_fork import stop, start from autotx.utils.ethereum import ( diff --git a/autotx/tests/integration/test_swap.py b/autotx/tests/integration/test_swap.py index 70f9581..9e4f37d 100644 --- a/autotx/tests/integration/test_swap.py +++ b/autotx/tests/integration/test_swap.py @@ -1,3 +1,4 @@ +import asyncio from decimal import Decimal from autotx.eth_address import ETHAddress from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance @@ -14,7 +15,7 @@ def test_buy_one_usdc(smart_account): usdc_address = ETHAddress(network_info.tokens["usdc"]) expected_usdc_amount = 1 buy_usdc_with_eth_transaction = build_swap_transaction( - smart_account.web3., + smart_account.web3, expected_usdc_amount, eth_address, usdc_address, @@ -43,7 +44,7 @@ def test_buy_one_thousand_usdc(smart_account): network_info.chain_id, ) print(buy_usdc_with_eth_transaction[0].summary) - hash = smart_account.send_transaction(buy_usdc_with_eth_transaction[0].params) + hash = asyncio.run(smart_account.send_transaction(buy_usdc_with_eth_transaction[0].params)) smart_account.wait(hash) usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE @@ -65,7 +66,7 @@ def test_receive_native(smart_account): True, network_info.chain_id, ) - hash = smart_account.send_transaction(buy_usdc_with_eth_transaction[0].params) + hash = asyncio.run(smart_account.send_transaction(buy_usdc_with_eth_transaction[0].params)) smart_account.wait(hash) safe_eth_balance = get_native_balance(smart_account.web3, smart_account.address) assert safe_eth_balance == 5 @@ -79,9 +80,9 @@ def test_receive_native(smart_account): False, network_info.chain_id, ) - hash = smart_account.send_tx(buy_eth_with_usdc_transaction[0].params) + hash = asyncio.run(smart_account.send_transaction(buy_eth_with_usdc_transaction[0].params)) smart_account.wait(hash) - hash = smart_account.send_tx(buy_eth_with_usdc_transaction[1].params) + hash = asyncio.run(smart_account.send_transaction(buy_eth_with_usdc_transaction[1].params)) smart_account.wait(hash) safe_eth_balance = get_native_balance(smart_account.web3, smart_account.address) assert safe_eth_balance >= 9 @@ -101,7 +102,7 @@ def test_buy_small_amount_wbtc_with_eth(smart_account): False, network_info.chain_id, ) - hash = smart_account.send_tx(buy_wbtc_with_eth_transaction[0].params) + hash = asyncio.run(smart_account.send_transaction(buy_wbtc_with_eth_transaction[0].params)) smart_account.wait(hash) wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) assert expected_wbtc_amount <= wbtc_balance <= expected_wbtc_amount * DIFFERENCE_PERCENTAGE @@ -121,7 +122,7 @@ def test_buy_big_amount_wbtc_with_eth(smart_account): False, network_info.chain_id, ) - hash = smart_account.send_tx(buy_wbtc_with_eth_transaction[0].params) + hash = asyncio.run(smart_account.send_transaction(buy_wbtc_with_eth_transaction[0].params)) smart_account.wait(hash) wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_balance, smart_account.address) assert expected_wbtc_amount <= wbtc_balance <= expected_wbtc_amount * DIFFERENCE_PERCENTAGE @@ -147,7 +148,7 @@ def test_swap_multiple_tokens(smart_account): True, network_info.chain_id, ) - hash = smart_account.send_tx(sell_eth_for_usdc_transaction[0].params) + hash = asyncio.run(smart_account.send_transaction(sell_eth_for_usdc_transaction[0].params)) smart_account.wait(hash) usdc_balance = get_erc20_balance(smart_account.web3, usdc_address, smart_account.address) assert usdc_balance > 2900 @@ -165,9 +166,9 @@ def test_swap_multiple_tokens(smart_account): network_info.chain_id, ) - hash = smart_account.send_tx(buy_wbtc_with_usdc_transaction[0].params) + hash = asyncio.run(smart_account.send_transaction(buy_wbtc_with_usdc_transaction[0].params)) smart_account.wait(hash) - hash = smart_account.send_tx(buy_wbtc_with_usdc_transaction[1].params) + hash = asyncio.run(smart_account.send_transaction(buy_wbtc_with_usdc_transaction[1].params)) smart_account.wait(hash) wbtc_balance = get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) assert wbtc_balance >= 0.01 @@ -184,9 +185,9 @@ def test_swap_multiple_tokens(smart_account): True, network_info.chain_id, ) - hash = smart_account.send_tx(sell_wbtc_for_shib[0].params) + hash = asyncio.run(smart_account.send_transaction(sell_wbtc_for_shib[0].params)) smart_account.wait(hash) - hash = smart_account.send_tx(sell_wbtc_for_shib[1].params) + hash = asyncio.run(smart_account.send_transaction(sell_wbtc_for_shib[1].params)) smart_account.wait(hash) shib_balance = get_erc20_balance(smart_account.web3, shib_address, smart_account.address) shib_balance = get_erc20_balance(smart_account.web3, shib_address, smart_account.address) diff --git a/autotx/utils/ethereum/lifi/__init__.py b/autotx/utils/ethereum/lifi/__init__.py index ccafabc..986c291 100644 --- a/autotx/utils/ethereum/lifi/__init__.py +++ b/autotx/utils/ethereum/lifi/__init__.py @@ -1,9 +1,9 @@ -import json -import os +import asyncio from typing import Any -import requests import re +import aiohttp +from autotx.utils import http_requests from autotx.utils.constants import LIFI_API_KEY from autotx.eth_address import ETHAddress from autotx.utils.ethereum.networks import ChainId @@ -19,9 +19,9 @@ def __init__(self, token_address: str): self.token_address = token_address -def handle_lifi_response(response: requests.Response) -> dict[str, Any]: - response_json: dict[str, Any] = json.loads(response.text) - if response.status_code == 200: +async def handle_lifi_response(response: aiohttp.ClientResponse) -> dict[str, Any]: + response_json: dict[str, Any] = await response.json() + if response.status == 200: return response_json if response_json["code"] == 1011: @@ -30,7 +30,7 @@ def handle_lifi_response(response: requests.Response) -> dict[str, Any]: token_address = match.group() raise TokenNotSupported(token_address) - if response.status_code == 429 or ( + if response.status == 429 or ( response_json["message"] == "Unauthorized" and response_json["code"] == 1005 ): raise LifiApiError("Rate limit exceeded") @@ -50,7 +50,7 @@ class Lifi: BASE_URL = "https://li.quest/v1" @classmethod - def get_quote_to_amount( # type: ignore + async def get_quote_to_amount( cls, from_token: ETHAddress, to_token: ETHAddress, @@ -70,24 +70,21 @@ def get_quote_to_amount( # type: ignore "contractCalls": [], } headers = add_authorization_info_if_provided(params) + attempt_count = 0 - while attempt_count < 2: - response = requests.post(cls.BASE_URL + "/quote/contractCalls", json=params, headers=headers) + while True: try: - return handle_lifi_response(response) + return await handle_lifi_response(await http_requests.post(cls.BASE_URL + "/quote/contractCalls", json=params, headers=headers)) except LifiApiError as e: - if ( - str(e) - == "Fetch quote failed with error: Unable to find quote to match expected output." - and attempt_count < 1 - ): - attempt_count += 1 - continue - else: - raise e + if "No available quotes for the requested transfer" in str(e) or "Unable to find quote to match expected output" in str(e): + if attempt_count < 5: + attempt_count += 1 + await asyncio.sleep(1) + continue + raise e @classmethod - def get_quote_from_amount( + async def get_quote_from_amount( cls, from_token: ETHAddress, to_token: ETHAddress, @@ -106,5 +103,15 @@ def get_quote_from_amount( "slippage": slippage, } headers = add_authorization_info_if_provided(params) - response = requests.get(cls.BASE_URL + "/quote", params=params, headers=headers) # type: ignore - return handle_lifi_response(response) + + attempt_count = 0 + while True: + try: + return await handle_lifi_response( await http_requests.get(cls.BASE_URL + "/quote", params=params, headers=headers)) + except LifiApiError as e: + if "No available quotes for the requested transfer" in str(e) or "Unable to find quote to match expected output" in str(e): + if attempt_count < 5: + attempt_count += 1 + await asyncio.sleep(1) + continue + raise e \ No newline at end of file diff --git a/autotx/utils/ethereum/lifi/swap.py b/autotx/utils/ethereum/lifi/swap.py index 7abb5c8..0967f80 100644 --- a/autotx/utils/ethereum/lifi/swap.py +++ b/autotx/utils/ethereum/lifi/swap.py @@ -37,7 +37,7 @@ class QuoteInformation: exchange_name: str -def get_quote( +async def get_quote( token_in_address: ETHAddress, token_in_decimals: int, token_in_symbol: str, @@ -52,7 +52,7 @@ def get_quote( quote: dict[str, Any] | None = None try: if amount_is_output: - quote = Lifi.get_quote_to_amount( + quote = await Lifi.get_quote_to_amount( token_in_address, token_out_address, int(expected_amount * (10**token_out_decimals)), @@ -64,7 +64,7 @@ def get_quote( else: amount_in_integer = int(expected_amount * (10**token_in_decimals)) - quote = Lifi.get_quote_from_amount( + quote = await Lifi.get_quote_from_amount( token_in_address, token_out_address, amount_in_integer, @@ -81,6 +81,13 @@ def get_quote( raise Exception( f"Token {token_out_symbol} is not supported. Please try another one" ) + except Exception as e: + if "The from amount must be greater than zero." in str(e): + if amount_is_output: + raise Exception(f"The specified amount of {token_out_symbol} is too low") + else: + raise Exception(f"The specified amount of {token_in_symbol} is too low") + raise e if not quote: raise Exception("Quote has not been fetched") @@ -104,49 +111,6 @@ def get_quote( quote["toolDetails"]["name"], ) -async def fetch_quote_with_retries( - token_in_address: ETHAddress, - token_in_decimals: int, - token_in_symbol: str, - token_out_address: ETHAddress, - token_out_decimals: int, - token_out_symbol: str, - chain: ChainId, - amount: Decimal, - is_exact_input: bool, - _from: ETHAddress, -) -> QuoteInformation: - retries = 0 - while True: - try: - quote = get_quote( - token_in_address, - token_in_decimals, - token_in_symbol, - token_out_address, - token_out_decimals, - token_out_symbol, - chain, - amount, - not is_exact_input, - _from, - ) - return quote - except Exception as e: - if "The from amount must be greater than zero." in str(e): - if is_exact_input: - raise Exception(f"The specified amount of {token_in_symbol} is too low") - else: - raise Exception(f"The specified amount of {token_out_symbol} is too low") - - elif "No available quotes for the requested transfer" in str(e): - if retries < 5: - retries += 1 - await asyncio.sleep(1) - continue - raise e - - def build_swap_transaction( web3: Web3, amount: Decimal, @@ -195,7 +159,7 @@ async def a_build_swap_transaction( else token_out.functions.symbol().call() ) - quote = await fetch_quote_with_retries( + quote = await get_quote( token_in_address, token_in_decimals, token_in_symbol, @@ -204,7 +168,7 @@ async def a_build_swap_transaction( token_out_symbol, chain, amount, - is_exact_input, + not is_exact_input, _from, ) transactions: list[Transaction] = [] @@ -289,7 +253,7 @@ async def a_can_build_swap_transaction( else token_out.functions.symbol().call() ) - quote = await fetch_quote_with_retries( + quote = await get_quote( token_in_address, token_in_decimals, token_in_symbol, diff --git a/autotx/utils/http_requests.py b/autotx/utils/http_requests.py new file mode 100644 index 0000000..a13ed29 --- /dev/null +++ b/autotx/utils/http_requests.py @@ -0,0 +1,11 @@ +from typing import Any +import aiohttp + + +async def get(url: str, params: dict[str, Any] = {}, headers: dict[str, Any] | None = None) -> aiohttp.ClientResponse: + async with aiohttp.ClientSession() as session: + return await session.get(url, params=params, headers=headers) + +async def post(url: str, headers: dict[str, Any] | None = None, json: dict[str, Any] = {}, data: Any = None) -> aiohttp.ClientResponse: + async with aiohttp.ClientSession() as session: + return await session.post(url, headers=headers, json=json, data=data) diff --git a/poetry.lock b/poetry.lock index fff9099..a21cf8f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -760,13 +760,13 @@ websockets = ["websocket-client (>=1.3.0)"] [[package]] name = "email-validator" -version = "2.1.1" +version = "2.2.0" description = "A robust email address syntax and deliverability validation library." optional = false python-versions = ">=3.8" files = [ - {file = "email_validator-2.1.1-py3-none-any.whl", hash = "sha256:97d882d174e2a65732fb43bfce81a3a834cbc1bde8bf419e30ef5ea976370a05"}, - {file = "email_validator-2.1.1.tar.gz", hash = "sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84"}, + {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, + {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, ] [package.dependencies] @@ -928,15 +928,18 @@ test = ["eth-hash[pycryptodome]", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-typing" -version = "4.2.3" +version = "4.3.1" description = "eth-typing: Common type annotations for ethereum python packages" optional = false python-versions = "<4,>=3.8" files = [ - {file = "eth_typing-4.2.3-py3-none-any.whl", hash = "sha256:b2df49fa89d2e85f2cc3fb1c903b0cd183d524f7a045e3db8cc720cf41adcd3d"}, - {file = "eth_typing-4.2.3.tar.gz", hash = "sha256:8ee3ae7d4136d14fcb955c34f9dbef8e52170984d4dc68c0ab0d61621eab29d8"}, + {file = "eth_typing-4.3.1-py3-none-any.whl", hash = "sha256:b4d7cee912c7779da75da4b42fa61475c1089d35a4df5081a786eaa29d5f6865"}, + {file = "eth_typing-4.3.1.tar.gz", hash = "sha256:4504559c87a9f71f4b99aa5a1e0549adaa7f192cbf8e37a295acfcddb1b5412d"}, ] +[package.dependencies] +typing-extensions = ">=4.5.0" + [package.extras] dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] @@ -1147,13 +1150,13 @@ files = [ [[package]] name = "gotrue" -version = "2.4.4" +version = "2.5.4" description = "Python Client Library for Supabase Auth" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "gotrue-2.4.4-py3-none-any.whl", hash = "sha256:2eef9c962820b114d355cd0690ec6aaeb03374efe8c6c75d2265d34483e9a67e"}, - {file = "gotrue-2.4.4.tar.gz", hash = "sha256:ba4652e3adb39c341a3a4f6a93ebe56b54e25b0959c66d1b38fd40fe4d75bff5"}, + {file = "gotrue-2.5.4-py3-none-any.whl", hash = "sha256:6f45003bc73cdee612a2d0be79cffed39c91cc8ad43a7440c02c320c7ad03a8e"}, + {file = "gotrue-2.5.4.tar.gz", hash = "sha256:acf0644a2e5d1bd70f66452361bfea4ba9621a0354a13154a333671a4c751c53"}, ] [package.dependencies] @@ -1357,12 +1360,12 @@ referencing = ">=0.31.0" [[package]] name = "llama-cpp-python" -version = "0.2.78" +version = "0.2.79" description = "Python bindings for the llama.cpp library" optional = false python-versions = ">=3.8" files = [ - {file = "llama_cpp_python-0.2.78.tar.gz", hash = "sha256:3df7cfde84287faaf29675fba8939060c3ab3f0ce8db875dabf7df5d83bd8751"}, + {file = "llama_cpp_python-0.2.79.tar.gz", hash = "sha256:19406225a37d816dc2fb911ba8e3ff2a48880dd79754820c55ed85ebc8238da4"}, ] [package.dependencies] @@ -1676,38 +1679,38 @@ files = [ [[package]] name = "mypy" -version = "1.10.0" +version = "1.10.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"}, - {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"}, - {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"}, - {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"}, - {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"}, - {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"}, - {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"}, - {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"}, - {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"}, - {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"}, - {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, - {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, - {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, - {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, - {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, - {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"}, - {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"}, - {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"}, - {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"}, - {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"}, - {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"}, - {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"}, - {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"}, - {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"}, - {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"}, - {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, - {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, + {file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"}, + {file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"}, + {file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"}, + {file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"}, + {file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"}, + {file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"}, + {file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"}, + {file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"}, + {file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"}, + {file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"}, + {file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"}, + {file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"}, + {file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"}, + {file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"}, + {file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"}, + {file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"}, + {file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"}, + {file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"}, + {file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"}, ] [package.dependencies] @@ -1790,13 +1793,13 @@ files = [ [[package]] name = "openai" -version = "1.32.0" +version = "1.35.5" description = "The official Python library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.32.0-py3-none-any.whl", hash = "sha256:953d57669f309002044fd2f678aba9f07a43256d74b3b00cd04afb5b185568ea"}, - {file = "openai-1.32.0.tar.gz", hash = "sha256:a6df15a7ab9344b1bc2bc8d83639f68b7a7e2453c0f5e50c1666547eee86f0bd"}, + {file = "openai-1.35.5-py3-none-any.whl", hash = "sha256:28d92503c6e4b6a32a89277b36693023ef41f60922a4b5c8c621e8c5697ae3a6"}, + {file = "openai-1.35.5.tar.gz", hash = "sha256:67ef289ae22d350cbf9381d83ae82c4e3596d71b7ad1cc886143554ee12fe0c9"}, ] [package.dependencies] @@ -1813,68 +1816,68 @@ datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] [[package]] name = "orjson" -version = "3.10.3" +version = "3.10.5" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.10.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9fb6c3f9f5490a3eb4ddd46fc1b6eadb0d6fc16fb3f07320149c3286a1409dd8"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:252124b198662eee80428f1af8c63f7ff077c88723fe206a25df8dc57a57b1fa"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f3e87733823089a338ef9bbf363ef4de45e5c599a9bf50a7a9b82e86d0228da"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8334c0d87103bb9fbbe59b78129f1f40d1d1e8355bbed2ca71853af15fa4ed3"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1952c03439e4dce23482ac846e7961f9d4ec62086eb98ae76d97bd41d72644d7"}, - {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c0403ed9c706dcd2809f1600ed18f4aae50be263bd7112e54b50e2c2bc3ebd6d"}, - {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:382e52aa4270a037d41f325e7d1dfa395b7de0c367800b6f337d8157367bf3a7"}, - {file = "orjson-3.10.3-cp310-none-win32.whl", hash = "sha256:be2aab54313752c04f2cbaab4515291ef5af8c2256ce22abc007f89f42f49109"}, - {file = "orjson-3.10.3-cp310-none-win_amd64.whl", hash = "sha256:416b195f78ae461601893f482287cee1e3059ec49b4f99479aedf22a20b1098b"}, - {file = "orjson-3.10.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:73100d9abbbe730331f2242c1fc0bcb46a3ea3b4ae3348847e5a141265479700"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a12eee96e3ab828dbfcb4d5a0023aa971b27143a1d35dc214c176fdfb29b3"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520de5e2ef0b4ae546bea25129d6c7c74edb43fc6cf5213f511a927f2b28148b"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccaa0a401fc02e8828a5bedfd80f8cd389d24f65e5ca3954d72c6582495b4bcf"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7bc9e8bc11bac40f905640acd41cbeaa87209e7e1f57ade386da658092dc16"}, - {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3582b34b70543a1ed6944aca75e219e1192661a63da4d039d088a09c67543b08"}, - {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c23dfa91481de880890d17aa7b91d586a4746a4c2aa9a145bebdbaf233768d5"}, - {file = "orjson-3.10.3-cp311-none-win32.whl", hash = "sha256:1770e2a0eae728b050705206d84eda8b074b65ee835e7f85c919f5705b006c9b"}, - {file = "orjson-3.10.3-cp311-none-win_amd64.whl", hash = "sha256:93433b3c1f852660eb5abdc1f4dd0ced2be031ba30900433223b28ee0140cde5"}, - {file = "orjson-3.10.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a39aa73e53bec8d410875683bfa3a8edf61e5a1c7bb4014f65f81d36467ea098"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0943a96b3fa09bee1afdfccc2cb236c9c64715afa375b2af296c73d91c23eab2"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e852baafceff8da3c9defae29414cc8513a1586ad93e45f27b89a639c68e8176"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18566beb5acd76f3769c1d1a7ec06cdb81edc4d55d2765fb677e3eaa10fa99e0"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd2218d5a3aa43060efe649ec564ebedec8ce6ae0a43654b81376216d5ebd42"}, - {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf20465e74c6e17a104ecf01bf8cd3b7b252565b4ccee4548f18b012ff2f8069"}, - {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba7f67aa7f983c4345eeda16054a4677289011a478ca947cd69c0a86ea45e534"}, - {file = "orjson-3.10.3-cp312-none-win32.whl", hash = "sha256:17e0713fc159abc261eea0f4feda611d32eabc35708b74bef6ad44f6c78d5ea0"}, - {file = "orjson-3.10.3-cp312-none-win_amd64.whl", hash = "sha256:4c895383b1ec42b017dd2c75ae8a5b862fc489006afde06f14afbdd0309b2af0"}, - {file = "orjson-3.10.3-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:be2719e5041e9fb76c8c2c06b9600fe8e8584e6980061ff88dcbc2691a16d20d"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0175a5798bdc878956099f5c54b9837cb62cfbf5d0b86ba6d77e43861bcec2"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:978be58a68ade24f1af7758626806e13cff7748a677faf95fbb298359aa1e20d"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16bda83b5c61586f6f788333d3cf3ed19015e3b9019188c56983b5a299210eb5"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ad1f26bea425041e0a1adad34630c4825a9e3adec49079b1fb6ac8d36f8b754"}, - {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9e253498bee561fe85d6325ba55ff2ff08fb5e7184cd6a4d7754133bd19c9195"}, - {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a62f9968bab8a676a164263e485f30a0b748255ee2f4ae49a0224be95f4532b"}, - {file = "orjson-3.10.3-cp38-none-win32.whl", hash = "sha256:8d0b84403d287d4bfa9bf7d1dc298d5c1c5d9f444f3737929a66f2fe4fb8f134"}, - {file = "orjson-3.10.3-cp38-none-win_amd64.whl", hash = "sha256:8bc7a4df90da5d535e18157220d7915780d07198b54f4de0110eca6b6c11e290"}, - {file = "orjson-3.10.3-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9059d15c30e675a58fdcd6f95465c1522b8426e092de9fff20edebfdc15e1cb0"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d40c7f7938c9c2b934b297412c067936d0b54e4b8ab916fd1a9eb8f54c02294"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a654ec1de8fdaae1d80d55cee65893cb06494e124681ab335218be6a0691e7"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:831c6ef73f9aa53c5f40ae8f949ff7681b38eaddb6904aab89dca4d85099cb78"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99b880d7e34542db89f48d14ddecbd26f06838b12427d5a25d71baceb5ba119d"}, - {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e5e176c994ce4bd434d7aafb9ecc893c15f347d3d2bbd8e7ce0b63071c52e25"}, - {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b69a58a37dab856491bf2d3bbf259775fdce262b727f96aafbda359cb1d114d8"}, - {file = "orjson-3.10.3-cp39-none-win32.whl", hash = "sha256:b8d4d1a6868cde356f1402c8faeb50d62cee765a1f7ffcfd6de732ab0581e063"}, - {file = "orjson-3.10.3-cp39-none-win_amd64.whl", hash = "sha256:5102f50c5fc46d94f2033fe00d392588564378260d64377aec702f21a7a22912"}, - {file = "orjson-3.10.3.tar.gz", hash = "sha256:2b166507acae7ba2f7c315dcf185a9111ad5e992ac81f2d507aac39193c2c818"}, + {file = "orjson-3.10.5-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:545d493c1f560d5ccfc134803ceb8955a14c3fcb47bbb4b2fee0232646d0b932"}, + {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4324929c2dd917598212bfd554757feca3e5e0fa60da08be11b4aa8b90013c1"}, + {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c13ca5e2ddded0ce6a927ea5a9f27cae77eee4c75547b4297252cb20c4d30e6"}, + {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6c8e30adfa52c025f042a87f450a6b9ea29649d828e0fec4858ed5e6caecf63"}, + {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:338fd4f071b242f26e9ca802f443edc588fa4ab60bfa81f38beaedf42eda226c"}, + {file = "orjson-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6970ed7a3126cfed873c5d21ece1cd5d6f83ca6c9afb71bbae21a0b034588d96"}, + {file = "orjson-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:235dadefb793ad12f7fa11e98a480db1f7c6469ff9e3da5e73c7809c700d746b"}, + {file = "orjson-3.10.5-cp310-none-win32.whl", hash = "sha256:be79e2393679eda6a590638abda16d167754393f5d0850dcbca2d0c3735cebe2"}, + {file = "orjson-3.10.5-cp310-none-win_amd64.whl", hash = "sha256:c4a65310ccb5c9910c47b078ba78e2787cb3878cdded1702ac3d0da71ddc5228"}, + {file = "orjson-3.10.5-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cdf7365063e80899ae3a697def1277c17a7df7ccfc979990a403dfe77bb54d40"}, + {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b68742c469745d0e6ca5724506858f75e2f1e5b59a4315861f9e2b1df77775a"}, + {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7d10cc1b594951522e35a3463da19e899abe6ca95f3c84c69e9e901e0bd93d38"}, + {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcbe82b35d1ac43b0d84072408330fd3295c2896973112d495e7234f7e3da2e1"}, + {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c0eb7e0c75e1e486c7563fe231b40fdd658a035ae125c6ba651ca3b07936f5"}, + {file = "orjson-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:53ed1c879b10de56f35daf06dbc4a0d9a5db98f6ee853c2dbd3ee9d13e6f302f"}, + {file = "orjson-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:099e81a5975237fda3100f918839af95f42f981447ba8f47adb7b6a3cdb078fa"}, + {file = "orjson-3.10.5-cp311-none-win32.whl", hash = "sha256:1146bf85ea37ac421594107195db8bc77104f74bc83e8ee21a2e58596bfb2f04"}, + {file = "orjson-3.10.5-cp311-none-win_amd64.whl", hash = "sha256:36a10f43c5f3a55c2f680efe07aa93ef4a342d2960dd2b1b7ea2dd764fe4a37c"}, + {file = "orjson-3.10.5-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:68f85ecae7af14a585a563ac741b0547a3f291de81cd1e20903e79f25170458f"}, + {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28afa96f496474ce60d3340fe8d9a263aa93ea01201cd2bad844c45cd21f5268"}, + {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cd684927af3e11b6e754df80b9ffafd9fb6adcaa9d3e8fdd5891be5a5cad51e"}, + {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d21b9983da032505f7050795e98b5d9eee0df903258951566ecc358f6696969"}, + {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ad1de7fef79736dde8c3554e75361ec351158a906d747bd901a52a5c9c8d24b"}, + {file = "orjson-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d97531cdfe9bdd76d492e69800afd97e5930cb0da6a825646667b2c6c6c0211"}, + {file = "orjson-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d69858c32f09c3e1ce44b617b3ebba1aba030e777000ebdf72b0d8e365d0b2b3"}, + {file = "orjson-3.10.5-cp312-none-win32.whl", hash = "sha256:64c9cc089f127e5875901ac05e5c25aa13cfa5dbbbd9602bda51e5c611d6e3e2"}, + {file = "orjson-3.10.5-cp312-none-win_amd64.whl", hash = "sha256:b2efbd67feff8c1f7728937c0d7f6ca8c25ec81373dc8db4ef394c1d93d13dc5"}, + {file = "orjson-3.10.5-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:03b565c3b93f5d6e001db48b747d31ea3819b89abf041ee10ac6988886d18e01"}, + {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:584c902ec19ab7928fd5add1783c909094cc53f31ac7acfada817b0847975f26"}, + {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a35455cc0b0b3a1eaf67224035f5388591ec72b9b6136d66b49a553ce9eb1e6"}, + {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1670fe88b116c2745a3a30b0f099b699a02bb3482c2591514baf5433819e4f4d"}, + {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185c394ef45b18b9a7d8e8f333606e2e8194a50c6e3c664215aae8cf42c5385e"}, + {file = "orjson-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ca0b3a94ac8d3886c9581b9f9de3ce858263865fdaa383fbc31c310b9eac07c9"}, + {file = "orjson-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dfc91d4720d48e2a709e9c368d5125b4b5899dced34b5400c3837dadc7d6271b"}, + {file = "orjson-3.10.5-cp38-none-win32.whl", hash = "sha256:c05f16701ab2a4ca146d0bca950af254cb7c02f3c01fca8efbbad82d23b3d9d4"}, + {file = "orjson-3.10.5-cp38-none-win_amd64.whl", hash = "sha256:8a11d459338f96a9aa7f232ba95679fc0c7cedbd1b990d736467894210205c09"}, + {file = "orjson-3.10.5-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:85c89131d7b3218db1b24c4abecea92fd6c7f9fab87441cfc342d3acc725d807"}, + {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66215277a230c456f9038d5e2d84778141643207f85336ef8d2a9da26bd7ca"}, + {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51bbcdea96cdefa4a9b4461e690c75ad4e33796530d182bdd5c38980202c134a"}, + {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbead71dbe65f959b7bd8cf91e0e11d5338033eba34c114f69078d59827ee139"}, + {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df58d206e78c40da118a8c14fc189207fffdcb1f21b3b4c9c0c18e839b5a214"}, + {file = "orjson-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c4057c3b511bb8aef605616bd3f1f002a697c7e4da6adf095ca5b84c0fd43595"}, + {file = "orjson-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b39e006b00c57125ab974362e740c14a0c6a66ff695bff44615dcf4a70ce2b86"}, + {file = "orjson-3.10.5-cp39-none-win32.whl", hash = "sha256:eded5138cc565a9d618e111c6d5c2547bbdd951114eb822f7f6309e04db0fb47"}, + {file = "orjson-3.10.5-cp39-none-win_amd64.whl", hash = "sha256:cc28e90a7cae7fcba2493953cff61da5a52950e78dc2dacfe931a317ee3d8de7"}, + {file = "orjson-3.10.5.tar.gz", hash = "sha256:7a5baef8a4284405d96c90c7c62b755e9ef1ada84c2406c24a9ebec86b89f46d"}, ] [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -1925,22 +1928,22 @@ strenum = ">=0.4.9,<0.5.0" [[package]] name = "protobuf" -version = "5.27.1" +version = "5.27.2" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-5.27.1-cp310-abi3-win32.whl", hash = "sha256:3adc15ec0ff35c5b2d0992f9345b04a540c1e73bfee3ff1643db43cc1d734333"}, - {file = "protobuf-5.27.1-cp310-abi3-win_amd64.whl", hash = "sha256:25236b69ab4ce1bec413fd4b68a15ef8141794427e0b4dc173e9d5d9dffc3bcd"}, - {file = "protobuf-5.27.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4e38fc29d7df32e01a41cf118b5a968b1efd46b9c41ff515234e794011c78b17"}, - {file = "protobuf-5.27.1-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:917ed03c3eb8a2d51c3496359f5b53b4e4b7e40edfbdd3d3f34336e0eef6825a"}, - {file = "protobuf-5.27.1-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:ee52874a9e69a30271649be88ecbe69d374232e8fd0b4e4b0aaaa87f429f1631"}, - {file = "protobuf-5.27.1-cp38-cp38-win32.whl", hash = "sha256:7a97b9c5aed86b9ca289eb5148df6c208ab5bb6906930590961e08f097258107"}, - {file = "protobuf-5.27.1-cp38-cp38-win_amd64.whl", hash = "sha256:f6abd0f69968792da7460d3c2cfa7d94fd74e1c21df321eb6345b963f9ec3d8d"}, - {file = "protobuf-5.27.1-cp39-cp39-win32.whl", hash = "sha256:dfddb7537f789002cc4eb00752c92e67885badcc7005566f2c5de9d969d3282d"}, - {file = "protobuf-5.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:39309898b912ca6febb0084ea912e976482834f401be35840a008da12d189340"}, - {file = "protobuf-5.27.1-py3-none-any.whl", hash = "sha256:4ac7249a1530a2ed50e24201d6630125ced04b30619262f06224616e0030b6cf"}, - {file = "protobuf-5.27.1.tar.gz", hash = "sha256:df5e5b8e39b7d1c25b186ffdf9f44f40f810bbcc9d2b71d9d3156fee5a9adf15"}, + {file = "protobuf-5.27.2-cp310-abi3-win32.whl", hash = "sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38"}, + {file = "protobuf-5.27.2-cp310-abi3-win_amd64.whl", hash = "sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505"}, + {file = "protobuf-5.27.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5"}, + {file = "protobuf-5.27.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b"}, + {file = "protobuf-5.27.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e"}, + {file = "protobuf-5.27.2-cp38-cp38-win32.whl", hash = "sha256:4fadd8d83e1992eed0248bc50a4a6361dc31bcccc84388c54c86e530b7f58863"}, + {file = "protobuf-5.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:610e700f02469c4a997e58e328cac6f305f649826853813177e6290416e846c6"}, + {file = "protobuf-5.27.2-cp39-cp39-win32.whl", hash = "sha256:9e8f199bf7f97bd7ecebffcae45ebf9527603549b2b562df0fbc6d4d688f14ca"}, + {file = "protobuf-5.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:7fc3add9e6003e026da5fc9e59b131b8f22b428b991ccd53e2af8071687b4fce"}, + {file = "protobuf-5.27.2-py3-none-any.whl", hash = "sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470"}, + {file = "protobuf-5.27.2.tar.gz", hash = "sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714"}, ] [[package]] @@ -1997,13 +2000,13 @@ test = ["factory-boy (>=3.0.0)", "hypothesis (>=6,<7)", "pytest (>=7.0.0)", "pyt [[package]] name = "pyautogen" -version = "0.2.28" +version = "0.2.31" description = "Enabling Next-Gen LLM Applications via Multi-Agent Conversation Framework" optional = false python-versions = "<3.13,>=3.8" files = [ - {file = "pyautogen-0.2.28-py3-none-any.whl", hash = "sha256:69dffa4053096f496a50c8a252bbe23105b58fd6ffbb422fa8c043ecf3fc732b"}, - {file = "pyautogen-0.2.28.tar.gz", hash = "sha256:f74686a981f2b6046a9cf6aff5a5e61615ec60d5559a49e7474467fbdf4e077b"}, + {file = "pyautogen-0.2.31-py3-none-any.whl", hash = "sha256:f1268dbbb191756a105815e1f46a6c6786c3059784b04a3831f4c24b9429c8c7"}, + {file = "pyautogen-0.2.31.tar.gz", hash = "sha256:157a6d2c68f1fe0c8d1e07c6886f97962dc85effd8da4baad7d7804ad284cc76"}, ] [package.dependencies] @@ -2019,21 +2022,24 @@ termcolor = "*" tiktoken = "*" [package.extras] -autobuild = ["chromadb", "huggingface-hub", "sentence-transformers"] +anthropic = ["anthropic (>=0.23.1)"] +autobuild = ["chromadb", "huggingface-hub", "pysqlite3", "sentence-transformers"] blendsearch = ["flaml[blendsearch]"] cosmosdb = ["azure-cosmos (>=4.2.0)"] -gemini = ["google-generativeai (>=0.5,<1)", "pillow", "pydantic"] +gemini = ["google-auth", "google-cloud-aiplatform", "google-generativeai (>=0.5,<1)", "pillow", "pydantic"] graph = ["matplotlib", "networkx"] jupyter-executor = ["ipykernel (>=6.29.0)", "jupyter-client (>=8.6.0)", "jupyter-kernel-gateway", "requests", "websocket-client"] lmm = ["pillow", "replicate"] long-context = ["llmlingua (<0.3)"] mathchat = ["pydantic (==1.10.9)", "sympy", "wolframalpha"] +mistral = ["mistralai (>=0.2.0)"] redis = ["redis"] retrievechat = ["beautifulsoup4", "chromadb", "ipython", "markdownify", "protobuf (==4.25.3)", "pypdf", "sentence-transformers"] retrievechat-pgvector = ["beautifulsoup4", "chromadb", "ipython", "markdownify", "pgvector (>=0.2.5)", "protobuf (==4.25.3)", "psycopg (>=3.1.18)", "pypdf", "sentence-transformers"] -retrievechat-qdrant = ["beautifulsoup4", "chromadb", "ipython", "markdownify", "protobuf (==4.25.3)", "pypdf", "qdrant-client[fastembed]", "sentence-transformers"] +retrievechat-qdrant = ["beautifulsoup4", "chromadb", "ipython", "markdownify", "protobuf (==4.25.3)", "pypdf", "qdrant-client[fastembed] (<1.9.2)", "sentence-transformers"] teachable = ["chromadb"] test = ["ipykernel", "nbconvert", "nbformat", "pandas", "pre-commit", "pytest (>=6.1.1,<8)", "pytest-asyncio", "pytest-cov (>=5)"] +together = ["together (>=1.2)"] types = ["ipykernel (>=6.29.0)", "jupyter-client (>=8.6.0)", "jupyter-kernel-gateway", "mypy (==1.9.0)", "pytest (>=6.1.1,<8)", "requests", "websocket-client"] websockets = ["websockets (>=12.0,<13)"] websurfer = ["beautifulsoup4", "markdownify", "pathvalidate", "pdfminer.six"] @@ -2081,13 +2087,13 @@ files = [ [[package]] name = "pydantic" -version = "2.7.3" +version = "2.7.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.7.3-py3-none-any.whl", hash = "sha256:ea91b002777bf643bb20dd717c028ec43216b24a6001a280f83877fd2655d0b4"}, - {file = "pydantic-2.7.3.tar.gz", hash = "sha256:c46c76a40bb1296728d7a8b99aa73dd70a48c3510111ff290034f860c99c419e"}, + {file = "pydantic-2.7.4-py3-none-any.whl", hash = "sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0"}, + {file = "pydantic-2.7.4.tar.gz", hash = "sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52"}, ] [package.dependencies] @@ -2205,13 +2211,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyright" -version = "1.1.366" +version = "1.1.369" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.366-py3-none-any.whl", hash = "sha256:c09e73ccc894976bcd6d6a5784aa84d724dbd9ceb7b873b39d475ca61c2de071"}, - {file = "pyright-1.1.366.tar.gz", hash = "sha256:10e4d60be411f6d960cd39b0b58bf2ff76f2c83b9aeb102ffa9d9fda2e1303cb"}, + {file = "pyright-1.1.369-py3-none-any.whl", hash = "sha256:06d5167a8d7be62523ced0265c5d2f1e022e110caf57a25d92f50fb2d07bcda0"}, + {file = "pyright-1.1.369.tar.gz", hash = "sha256:ad290710072d021e213b98cc7a2f90ae3a48609ef5b978f749346d1a47eb9af8"}, ] [package.dependencies] @@ -2395,18 +2401,18 @@ files = [ [[package]] name = "realtime" -version = "1.0.5" +version = "1.0.6" description = "" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "realtime-1.0.5-py3-none-any.whl", hash = "sha256:93342fbcb8812ed8d81733f2782c1199376f0471e78014675420c7d31f2f327d"}, - {file = "realtime-1.0.5.tar.gz", hash = "sha256:4abbb3218b6ce8bd8d9d3b1112661d325e36ceab67a0e918673d0fd8fca04fb1"}, + {file = "realtime-1.0.6-py3-none-any.whl", hash = "sha256:c66918a106d8ef348d1821f2dbf6683d8833825580d95b2fdea9995406b42838"}, + {file = "realtime-1.0.6.tar.gz", hash = "sha256:2be0d8a6305513d423604ee319216108fc20105cb7438922d5c8958c48f40a47"}, ] [package.dependencies] python-dateutil = ">=2.8.1,<3.0.0" -typing-extensions = ">=4.11.0,<5.0.0" +typing-extensions = ">=4.12.2,<5.0.0" websockets = ">=11,<13" [[package]] @@ -2805,13 +2811,13 @@ test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"] [[package]] name = "supabase" -version = "2.5.0" +version = "2.5.1" description = "Supabase client for Python." optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "supabase-2.5.0-py3-none-any.whl", hash = "sha256:13e5ed9e9377a1a69e70ad18ed7b82997cf13ffcd28173952f7503e4d5067771"}, - {file = "supabase-2.5.0.tar.gz", hash = "sha256:133dc832dfdd617f2f90ac5b52664df96ac8a9302ac6656ee769dc3f545812f0"}, + {file = "supabase-2.5.1-py3-none-any.whl", hash = "sha256:74a1f24f04fede1967ef084b50dea688228f7b10eb2f9d73350fe2251a865188"}, + {file = "supabase-2.5.1.tar.gz", hash = "sha256:c50e0eba5b03de3abd5ac0f887957ca43558ba44c4d17bb44e73ec454b41734c"}, ] [package.dependencies] @@ -2986,13 +2992,13 @@ typing-extensions = ">=3.7.4.3" [[package]] name = "typing-extensions" -version = "4.12.1" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"}, - {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -3084,13 +3090,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -3274,13 +3280,13 @@ anyio = ">=3.0.0" [[package]] name = "web3" -version = "6.19.0" +version = "6.20.0" description = "web3.py" optional = false python-versions = ">=3.7.2" files = [ - {file = "web3-6.19.0-py3-none-any.whl", hash = "sha256:fb39683d6aa7586ce0ab0be4be392f8acb62c2503958079d61b59f2a0b883718"}, - {file = "web3-6.19.0.tar.gz", hash = "sha256:d27fbd4ac5aa70d0e0c516bd3e3b802fbe74bc159b407c34052d9301b400f757"}, + {file = "web3-6.20.0-py3-none-any.whl", hash = "sha256:ec09882d21378b688210cf29385e82b604bdc18fe5c2e238bf3b5fe2a6e6dbbc"}, + {file = "web3-6.20.0.tar.gz", hash = "sha256:b04725517502cad4f15e39356eaf7c4fcb0127c7728f83eec8cbafb5b6455f33"}, ] [package.dependencies] @@ -3572,4 +3578,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "5dfc1bc10f28a24e09c829fbdf2469dfcd0ab7dcd8c51312e7f15e8ffd02601f" +content-hash = "300ed8dedc21e53f9f0a4bcd9ffa9f98f0464befeb2ada5441b170a101b08c1a" diff --git a/pyproject.toml b/pyproject.toml index a60350c..222077e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,6 @@ python = ">=3.10,<3.13" pyright = "^1.1.350" click = "^8.1.7" eth-account = "^0.11.0" -requests = "^2.31.0" python-dotenv = "^1.0.0" coingecko = "^0.13" pyautogen = "^0.2.27" @@ -20,6 +19,7 @@ safe-eth-py = "^5.8.0" uvicorn = "^0.29.0" supabase = "^2.5.0" llama-cpp-python = "^0.2.78" +aiohttp = "^3.9.5" [tool.poetry.group.dev.dependencies] mypy = "^1.8.0" From 52c0bd1141f21da7b7b7ab91c76bfa1f9207b074 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Thu, 27 Jun 2024 20:52:26 +0200 Subject: [PATCH 11/23] lowered max rounds to 20 when testing --- autotx/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autotx/tests/conftest.py b/autotx/tests/conftest.py index a70a27b..cdcba8b 100644 --- a/autotx/tests/conftest.py +++ b/autotx/tests/conftest.py @@ -52,7 +52,7 @@ def auto_tx(smart_account): SwapTokensAgent(), DelegateResearchTokensAgent() ], - Config(verbose=True, get_llm_config=get_llm_config, logs_dir=None, log_costs=True), + Config(verbose=True, get_llm_config=get_llm_config, logs_dir=None, log_costs=True, max_rounds=20) ) @pytest.fixture() From 224fb063bf7b9fa98088c2e6e6810baebe0548f3 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Thu, 27 Jun 2024 21:38:47 +0200 Subject: [PATCH 12/23] added execution logs --- autotx/server.py | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/autotx/server.py b/autotx/server.py index 1742381..2dbf5d2 100644 --- a/autotx/server.py +++ b/autotx/server.py @@ -1,3 +1,4 @@ +from datetime import datetime import json from typing import Annotated, Any, Dict, List from eth_account import Account @@ -110,6 +111,19 @@ def stop_task_for_error(tasks: db.TasksRepository, task_id: str, error: str, use task.messages.append(user_error_message) tasks.update(task) +def log(log_type: str, obj: Any, task_id: str, tasks: db.TasksRepository) -> None: + add_task_log(models.TaskLog(type=log_type, obj=json.dumps(obj), created_at=datetime.now()), task_id, tasks) + +def add_task_log(log: models.TaskLog, task_id: str, tasks: db.TasksRepository) -> None: + task = tasks.get(task_id) + if task is None: + raise Exception("Task not found: " + task_id) + + if task.logs is None: + task.logs = [] + task.logs.append(log) + tasks.update(task) + @app_router.post("/api/v1/tasks", response_model=models.Task) async def create_task(task: models.TaskCreate, background_tasks: BackgroundTasks, authorization: Annotated[str | None, Header()] = None) -> models.Task: from autotx.AutoTx import AutoTx, Config as AutoTxConfig @@ -148,16 +162,11 @@ def on_notify_user(message: str) -> None: tasks.update(task) def on_agent_message(from_agent: str, to_agent: str, message: Any) -> None: - task = tasks.get(task_id) - if task is None: - raise Exception("Task not found: " + task_id) - - if task.logs is None: - task.logs = [] - task.logs.append( - task_logs.build_agent_message_log(from_agent, to_agent, message) + add_task_log( + task_logs.build_agent_message_log(from_agent, to_agent, message), + task_id, + tasks ) - tasks.update(task) autotx = AutoTx( app_config.web3, @@ -176,13 +185,16 @@ def on_agent_message(from_agent: str, to_agent: str, message: Any) -> None: async def run_task() -> None: try: + log("execution", "run-start", task_id, tasks) await autotx.a_run(prompt, non_interactive=True) + log("execution", "run-end", task_id, tasks) except Exception as e: error = traceback.format_exc() db.add_task_error(f"AutoTx run", app.id, app_user.id, task_id, error) stop_task_for_error(tasks, task_id, error, f"An error caused AutoTx to stop ({task_id})") raise e tasks.stop(task_id) + log("execution", "task-stop", task_id, tasks) background_tasks.add_task(run_task) @@ -365,17 +377,25 @@ def get_task_logs(task_id: str) -> list[models.TaskLog]: @app_router.get("/api/v1/tasks/{task_id}/logs/{log_type}", response_class=HTMLResponse) def get_task_logs_formatted(task_id: str, log_type: str) -> str: - if log_type != "agent-message": + if log_type != "agent-message" and log_type != "execution": raise HTTPException(status_code=400, detail="Log type not supported") logs = db.get_task_logs(task_id) if logs is None: raise HTTPException(status_code=404, detail="Task not found") + + filtered_logs = [log for log in logs if log.type == log_type] + + if len(filtered_logs) == 0: + return "
No logs found
" - agent_logs = [task_logs.format_agent_message_log(json.loads(log.obj)) for log in logs if log.type == "agent-message"] + if log_type == "execution": + return "
" + "\n".join([log.created_at.strftime("%Y-%m-%d %H:%M:%S") + f": {json.loads(log.obj)}" for log in filtered_logs]) + "
" + else: + agent_logs = [task_logs.format_agent_message_log(json.loads(log.obj)) for log in filtered_logs] - text = "\n\n".join(agent_logs) - return f"
{text}
" + text = "\n\n".join(agent_logs) + return f"
{text}
" @app_router.get("/api/v1/version", response_class=JSONResponse) async def get_version() -> Dict[str, str]: From 7649dce33cf3b341a10c41d512b3d30208e2320a Mon Sep 17 00:00:00 2001 From: nerfZael Date: Fri, 28 Jun 2024 02:40:49 +0200 Subject: [PATCH 13/23] fixed inverted bool --- autotx/utils/ethereum/lifi/swap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autotx/utils/ethereum/lifi/swap.py b/autotx/utils/ethereum/lifi/swap.py index 0967f80..979ef66 100644 --- a/autotx/utils/ethereum/lifi/swap.py +++ b/autotx/utils/ethereum/lifi/swap.py @@ -262,7 +262,7 @@ async def a_can_build_swap_transaction( token_out_symbol, chain, amount, - is_exact_input, + not is_exact_input, _from, ) if not token_in_is_native: From e95d49d7130c69cbdce10cff23e68d665a097f2b Mon Sep 17 00:00:00 2001 From: nerfZael Date: Fri, 28 Jun 2024 02:41:01 +0200 Subject: [PATCH 14/23] catching all exceptions for swap --- autotx/utils/ethereum/lifi/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/autotx/utils/ethereum/lifi/__init__.py b/autotx/utils/ethereum/lifi/__init__.py index 986c291..a921a0d 100644 --- a/autotx/utils/ethereum/lifi/__init__.py +++ b/autotx/utils/ethereum/lifi/__init__.py @@ -75,7 +75,7 @@ async def get_quote_to_amount( while True: try: return await handle_lifi_response(await http_requests.post(cls.BASE_URL + "/quote/contractCalls", json=params, headers=headers)) - except LifiApiError as e: + except Exception as e: if "No available quotes for the requested transfer" in str(e) or "Unable to find quote to match expected output" in str(e): if attempt_count < 5: attempt_count += 1 @@ -107,8 +107,8 @@ async def get_quote_from_amount( attempt_count = 0 while True: try: - return await handle_lifi_response( await http_requests.get(cls.BASE_URL + "/quote", params=params, headers=headers)) - except LifiApiError as e: + return await handle_lifi_response(await http_requests.get(cls.BASE_URL + "/quote", params=params, headers=headers)) + except Exception as e: if "No available quotes for the requested transfer" in str(e) or "Unable to find quote to match expected output" in str(e): if attempt_count < 5: attempt_count += 1 From 0fa32df2febfd3669fa5f5397347a8487d3b7d49 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Fri, 28 Jun 2024 03:02:14 +0200 Subject: [PATCH 15/23] added pytest timeout --- autotx/tests/agents/token/send/test_send.py | 7 +++++++ autotx/tests/agents/token/test_swap.py | 9 +++++++++ autotx/tests/agents/token/test_swap_and_send.py | 5 +++++ poetry.lock | 16 +++++++++++++++- pyproject.toml | 1 + 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/autotx/tests/agents/token/send/test_send.py b/autotx/tests/agents/token/send/test_send.py index fb79edc..b183c41 100644 --- a/autotx/tests/agents/token/send/test_send.py +++ b/autotx/tests/agents/token/send/test_send.py @@ -1,6 +1,9 @@ +import pytest + from autotx.utils.ethereum import get_erc20_balance from autotx.utils.ethereum.get_native_balance import get_native_balance +@pytest.mark.timeout(60) def test_send_native(smart_account, auto_tx, test_accounts): receiver = test_accounts[0] balance = get_native_balance(smart_account.web3, receiver) @@ -11,6 +14,7 @@ def test_send_native(smart_account, auto_tx, test_accounts): balance = get_native_balance(smart_account.web3, receiver) assert balance == 1 +@pytest.mark.timeout(60) def test_send_erc20(smart_account, auto_tx, usdc, test_accounts): receiver = test_accounts[0] @@ -25,6 +29,7 @@ def test_send_erc20(smart_account, auto_tx, usdc, test_accounts): assert balance + 10 == new_balance +@pytest.mark.timeout(60) def test_send_native_sequential(smart_account, auto_tx, test_accounts): receiver = test_accounts[0] @@ -38,6 +43,7 @@ def test_send_native_sequential(smart_account, auto_tx, test_accounts): balance = get_native_balance(smart_account.web3, receiver) assert balance == 1.5 +@pytest.mark.timeout(60) def test_send_erc20_parallel(smart_account, auto_tx, usdc, test_accounts): receiver_one = test_accounts[0] @@ -56,6 +62,7 @@ def test_send_erc20_parallel(smart_account, auto_tx, usdc, test_accounts): assert balance_one + 2 == new_balance_one assert balance_two + 3 == new_balance_two +@pytest.mark.timeout(60) def test_send_eth_multiple(smart_account, auto_tx, usdc, test_accounts): receiver_1 = test_accounts[0] diff --git a/autotx/tests/agents/token/test_swap.py b/autotx/tests/agents/token/test_swap.py index 2443d09..212ee36 100644 --- a/autotx/tests/agents/token/test_swap.py +++ b/autotx/tests/agents/token/test_swap.py @@ -1,9 +1,12 @@ +import pytest + from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance from autotx.utils.ethereum.networks import NetworkInfo from autotx.eth_address import ETHAddress DIFFERENCE_PERCENTAGE = 1.01 +@pytest.mark.timeout(60) def test_swap_with_non_default_token(smart_account, auto_tx): network_info = NetworkInfo(smart_account.web3.eth.chain_id) shib_address = ETHAddress(network_info.tokens["shib"]) @@ -18,6 +21,7 @@ def test_swap_with_non_default_token(smart_account, auto_tx): expected_shib_amount = 100000 assert expected_shib_amount <= new_balance <= expected_shib_amount * DIFFERENCE_PERCENTAGE +@pytest.mark.timeout(60) def test_swap_native(smart_account, auto_tx): network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) @@ -29,6 +33,7 @@ def test_swap_native(smart_account, auto_tx): expected_usdc_amount = 100 assert expected_usdc_amount <= new_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE +@pytest.mark.timeout(60) def test_swap_multiple_1(smart_account, auto_tx): network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) @@ -46,6 +51,7 @@ def test_swap_multiple_1(smart_account, auto_tx): assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount_plus_slippage - expected_usdc_amount assert wbtc_balance < get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) +@pytest.mark.timeout(60) def test_swap_multiple_2(smart_account, auto_tx): network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) @@ -61,6 +67,7 @@ def test_swap_multiple_2(smart_account, auto_tx): assert expected_amount <= usdc_balance assert wbtc_balance < get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) +@pytest.mark.timeout(60) def test_swap_triple(smart_account, auto_tx): network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) @@ -81,6 +88,7 @@ def test_swap_triple(smart_account, auto_tx): assert expected_uni_amount <= uni_balance <= expected_uni_amount * DIFFERENCE_PERCENTAGE assert expected_wbtc_amount <= wbtc_balance <= expected_wbtc_amount * DIFFERENCE_PERCENTAGE +@pytest.mark.timeout(60) def test_swap_complex_1(smart_account, auto_tx): # This one is complex because it confuses the LLM with WBTC amount network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) @@ -95,6 +103,7 @@ def test_swap_complex_1(smart_account, auto_tx): # This one is complex because i assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE assert wbtc_balance < get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) +@pytest.mark.timeout(60) def test_swap_complex_2(smart_account, auto_tx): # This one is complex because it confuses the LLM with WBTC amount network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) diff --git a/autotx/tests/agents/token/test_swap_and_send.py b/autotx/tests/agents/token/test_swap_and_send.py index adc15b9..fcdeeca 100644 --- a/autotx/tests/agents/token/test_swap_and_send.py +++ b/autotx/tests/agents/token/test_swap_and_send.py @@ -1,9 +1,11 @@ +import pytest from autotx.utils.ethereum import get_erc20_balance, get_native_balance from autotx.utils.ethereum.networks import NetworkInfo from autotx.eth_address import ETHAddress DIFFERENCE_PERCENTAGE = 1.01 +@pytest.mark.timeout(60) def test_swap_and_send_simple(smart_account, auto_tx, test_accounts): network_info = NetworkInfo(smart_account.web3.eth.chain_id) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) @@ -20,6 +22,7 @@ def test_swap_and_send_simple(smart_account, auto_tx, test_accounts): assert excepted_safe_wbtc_balance <= new_wbtc_safe_address <= new_wbtc_safe_address * DIFFERENCE_PERCENTAGE assert new_receiver_wbtc_balance == 0.01 +@pytest.mark.timeout(60) def test_swap_and_send_complex(smart_account, auto_tx, test_accounts): network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) @@ -41,6 +44,7 @@ def test_swap_and_send_complex(smart_account, auto_tx, test_accounts): assert expected_usdc_safe_balance <= new_usdc_safe_address <= expected_usdc_safe_balance * DIFFERENCE_PERCENTAGE assert new_receiver_usdc_balance == 50 +@pytest.mark.timeout(60) def test_send_and_swap_simple(smart_account, auto_tx, test_accounts): network_info = NetworkInfo(smart_account.web3.eth.chain_id) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) @@ -64,6 +68,7 @@ def test_send_and_swap_simple(smart_account, auto_tx, test_accounts): assert new_receiver_wbtc_balance == receiver_wbtc_balance assert new_receiver_native_balance == receiver_native_balance + 0.1 +@pytest.mark.timeout(60) def test_send_and_swap_complex(smart_account, auto_tx, test_accounts): network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) diff --git a/poetry.lock b/poetry.lock index a21cf8f..7831d5a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2249,6 +2249,20 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-timeout" +version = "2.3.1" +description = "pytest plugin to abort hanging tests" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-timeout-2.3.1.tar.gz", hash = "sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9"}, + {file = "pytest_timeout-2.3.1-py3-none-any.whl", hash = "sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + [[package]] name = "pytest-vcr" version = "1.0.2" @@ -3578,4 +3592,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "300ed8dedc21e53f9f0a4bcd9ffa9f98f0464befeb2ada5441b170a101b08c1a" +content-hash = "c358c59f6f591afe7264818076545c27f31d51e67663bd4b67c8227bfe8689ab" diff --git a/pyproject.toml b/pyproject.toml index 222077e..df271af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ uvicorn = "^0.29.0" supabase = "^2.5.0" llama-cpp-python = "^0.2.78" aiohttp = "^3.9.5" +pytest-timeout = "^2.3.1" [tool.poetry.group.dev.dependencies] mypy = "^1.8.0" From 02144910d6a89fdabf40f0b5b2ce4c55c4fd7d28 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Fri, 28 Jun 2024 12:32:22 +0200 Subject: [PATCH 16/23] fixes issue where benchmarks break if first test fails --- benchmarks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks.py b/benchmarks.py index a0393ac..06fd69e 100644 --- a/benchmarks.py +++ b/benchmarks.py @@ -62,7 +62,7 @@ def run_test(test_name, iterations, avg_time_across_tests, completed_tests, rema estimated_time_left = remaining_time_current_test + (estimated_avg_time_across_tests * remaining_tests) total_completion_time = datetime.now() + timedelta(seconds=estimated_time_left) - new_costs = os.listdir("costs") + new_costs = os.listdir("costs") if os.path.exists("costs") else [] # Find all new cost files that are not in old costs current_run_costs = list(set(new_costs) - set(old_costs)) for cost_file in current_run_costs: From 625acefd3e310bb91206d01871739ad6d2de921a Mon Sep 17 00:00:00 2001 From: nerfZael Date: Fri, 28 Jun 2024 12:32:41 +0200 Subject: [PATCH 17/23] added timeouts to tests --- autotx/tests/agents/token/research/test_advanced.py | 4 ++++ autotx/tests/agents/token/research/test_research.py | 8 ++++++++ .../tests/agents/token/research/test_research_and_swap.py | 4 ++++ .../agents/token/research/test_research_swap_and_send.py | 5 +++++ autotx/tests/agents/token/test_swap_and_send.py | 4 ++-- 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/autotx/tests/agents/token/research/test_advanced.py b/autotx/tests/agents/token/research/test_advanced.py index 8d01e08..6f307f3 100644 --- a/autotx/tests/agents/token/research/test_advanced.py +++ b/autotx/tests/agents/token/research/test_advanced.py @@ -1,8 +1,11 @@ +import pytest + from autotx.tests.agents.token.research.test_research import get_top_token_addresses_by_market_cap from autotx.eth_address import ETHAddress from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance from autotx.utils.ethereum.get_native_balance import get_native_balance +@pytest.mark.timeout(180) def test_research_and_swap_many_tokens_subjective_simple(smart_account, auto_tx): uni_address = ETHAddress(auto_tx.network.tokens["uni"]) @@ -40,6 +43,7 @@ def test_research_and_swap_many_tokens_subjective_simple(smart_account, auto_tx) assert ai_token_balance_in_safe > 0 assert meme_token_balance_in_safe > 0 +@pytest.mark.timeout(500) def test_research_and_swap_many_tokens_subjective_complex(smart_account, auto_tx): starting_balance = get_native_balance(smart_account.web3, smart_account.address) diff --git a/autotx/tests/agents/token/research/test_research.py b/autotx/tests/agents/token/research/test_research.py index 251cc70..afbb423 100644 --- a/autotx/tests/agents/token/research/test_research.py +++ b/autotx/tests/agents/token/research/test_research.py @@ -1,3 +1,5 @@ +import pytest + from autotx.agents.ResearchTokensAgent import ( filter_token_list_by_network, get_coingecko, @@ -6,6 +8,7 @@ from autotx.eth_address import ETHAddress from autotx.utils.ethereum.networks import ChainId +@pytest.mark.timeout(120) def get_top_token_addresses_by_market_cap(category: str, network: str, count: int, auto_tx) -> list[ETHAddress]: tokens = get_coingecko().coins.get_markets(vs_currency="usd", category=category, per_page=250) tokens_in_network = filter_token_list_by_network( @@ -14,6 +17,7 @@ def get_top_token_addresses_by_market_cap(category: str, network: str, count: in return [ETHAddress(auto_tx.network.tokens[token["symbol"].lower()]) for token in tokens_in_network[:count]] +@pytest.mark.timeout(120) def test_price_change_information(auto_tx): token_information = get_coingecko().coins.get_id( id="starknet", @@ -33,6 +37,7 @@ def test_price_change_information(auto_tx): str(price_change) in "\n".join(result.info_messages).lower() or str(price_change_rounded) in "\n".join(result.info_messages).lower() ) +@pytest.mark.timeout(120) def test_get_top_5_tokens_from_base(auto_tx): tokens = get_coingecko().coins.get_markets( vs_currency="usd", category="base-ecosystem" @@ -45,6 +50,7 @@ def test_get_top_5_tokens_from_base(auto_tx): symbol: str = token["symbol"] assert symbol.lower() in "\n".join(result.info_messages).lower() +@pytest.mark.timeout(120) def test_get_top_5_most_traded_tokens_from_l1(auto_tx): tokens = get_coingecko().coins.get_markets( vs_currency="usd", category="layer-1", order="volume_desc" @@ -57,6 +63,7 @@ def test_get_top_5_most_traded_tokens_from_l1(auto_tx): symbol: str = token["symbol"] assert symbol.lower() in "\n".join(result.info_messages).lower() +@pytest.mark.timeout(120) def test_get_top_5_memecoins(auto_tx): tokens = get_coingecko().coins.get_markets(vs_currency="usd", category="meme-token") tokens_in_network = filter_token_list_by_network( @@ -71,6 +78,7 @@ def test_get_top_5_memecoins(auto_tx): symbol: str = token["symbol"] assert symbol.lower() in "\n".join(result.info_messages).lower() +@pytest.mark.timeout(120) def test_get_top_5_memecoins_in_optimism(auto_tx): tokens = get_coingecko().coins.get_markets(vs_currency="usd", category="meme-token") prompt = "What are the top 5 meme coins on Optimism?" diff --git a/autotx/tests/agents/token/research/test_research_and_swap.py b/autotx/tests/agents/token/research/test_research_and_swap.py index 365c0fb..cbceadd 100644 --- a/autotx/tests/agents/token/research/test_research_and_swap.py +++ b/autotx/tests/agents/token/research/test_research_and_swap.py @@ -1,7 +1,10 @@ +import pytest + from autotx.tests.agents.token.research.test_research import get_top_token_addresses_by_market_cap from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance from autotx.utils.ethereum.get_native_balance import get_native_balance +@pytest.mark.timeout(120) def test_research_and_buy_one(smart_account, auto_tx): prompt = ( @@ -15,6 +18,7 @@ def test_research_and_buy_one(smart_account, auto_tx): token_balance_in_safe = get_erc20_balance(smart_account.web3, token_address, smart_account.address) assert token_balance_in_safe > 1000 +@pytest.mark.timeout(180) def test_research_and_buy_multiple(smart_account, auto_tx): old_eth_balance = get_native_balance(smart_account.web3, smart_account.address) diff --git a/autotx/tests/agents/token/research/test_research_swap_and_send.py b/autotx/tests/agents/token/research/test_research_swap_and_send.py index 81e72dd..aa44e67 100644 --- a/autotx/tests/agents/token/research/test_research_swap_and_send.py +++ b/autotx/tests/agents/token/research/test_research_swap_and_send.py @@ -1,9 +1,12 @@ +import pytest + from autotx.tests.agents.token.research.test_research import get_top_token_addresses_by_market_cap from autotx.utils.ethereum import get_erc20_balance from autotx.utils.ethereum.get_native_balance import get_native_balance DIFFERENCE_PERCENTAGE = 0.01 +@pytest.mark.timeout(180) def test_research_buy_one_send_one(smart_account, auto_tx, test_accounts): receiver = test_accounts[0] @@ -22,6 +25,7 @@ def test_research_buy_one_send_one(smart_account, auto_tx, test_accounts): assert token_balance_in_safe / receiver_balance < DIFFERENCE_PERCENTAGE +@pytest.mark.timeout(180) def test_research_buy_one_send_multiple(smart_account, auto_tx, test_accounts): receiver_1 = test_accounts[0] @@ -44,6 +48,7 @@ def test_research_buy_one_send_multiple(smart_account, auto_tx, test_accounts): assert meme_token_balance_in_safe > 10000 +@pytest.mark.timeout(180) def test_research_buy_multiple_send_multiple(smart_account, auto_tx, test_accounts): receiver_1 = test_accounts[0] diff --git a/autotx/tests/agents/token/test_swap_and_send.py b/autotx/tests/agents/token/test_swap_and_send.py index fcdeeca..333377a 100644 --- a/autotx/tests/agents/token/test_swap_and_send.py +++ b/autotx/tests/agents/token/test_swap_and_send.py @@ -22,7 +22,7 @@ def test_swap_and_send_simple(smart_account, auto_tx, test_accounts): assert excepted_safe_wbtc_balance <= new_wbtc_safe_address <= new_wbtc_safe_address * DIFFERENCE_PERCENTAGE assert new_receiver_wbtc_balance == 0.01 -@pytest.mark.timeout(60) +@pytest.mark.timeout(180) def test_swap_and_send_complex(smart_account, auto_tx, test_accounts): network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) @@ -68,7 +68,7 @@ def test_send_and_swap_simple(smart_account, auto_tx, test_accounts): assert new_receiver_wbtc_balance == receiver_wbtc_balance assert new_receiver_native_balance == receiver_native_balance + 0.1 -@pytest.mark.timeout(60) +@pytest.mark.timeout(180) def test_send_and_swap_complex(smart_account, auto_tx, test_accounts): network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) From 906aef96f48807b789d2006e57799d06a43e416e Mon Sep 17 00:00:00 2001 From: nerfZael Date: Fri, 28 Jun 2024 17:45:14 +0200 Subject: [PATCH 18/23] using aiohttp directly to not dispose of session --- autotx/load_tokens.py | 12 ++-- .../local_biconomy_smart_account.py | 58 +++++++++---------- autotx/utils/ethereum/lifi/__init__.py | 47 +++++++++------ autotx/utils/http_requests.py | 11 ---- 4 files changed, 65 insertions(+), 63 deletions(-) delete mode 100644 autotx/utils/http_requests.py diff --git a/autotx/load_tokens.py b/autotx/load_tokens.py index a6cf6db..f020a53 100644 --- a/autotx/load_tokens.py +++ b/autotx/load_tokens.py @@ -1,8 +1,7 @@ import json from textwrap import dedent from typing import Union - -from autotx.utils import http_requests +import aiohttp KLEROS_TOKENS_LIST = "https://t2crtokens.eth.link/" COINGECKO_TOKENS_LISTS = [ @@ -24,10 +23,11 @@ async def fetch_tokens_list() -> None: for token_list_url in TOKENS_LIST: try: - response = await http_requests.get(token_list_url) - result = await response.json() - tokens = result["tokens"] - loaded_tokens.extend(tokens) + async with aiohttp.ClientSession() as session: + response = await session.get(token_list_url) + result = await response.json() + tokens = result["tokens"] + loaded_tokens.extend(tokens) except: print("Error while trying to fetch list:", token_list_url) diff --git a/autotx/smart_accounts/local_biconomy_smart_account.py b/autotx/smart_accounts/local_biconomy_smart_account.py index ee64486..c5779c4 100644 --- a/autotx/smart_accounts/local_biconomy_smart_account.py +++ b/autotx/smart_accounts/local_biconomy_smart_account.py @@ -1,7 +1,7 @@ import asyncio import json from typing import cast - +import aiohttp from eth_account.signers.local import LocalAccount from web3 import Web3 @@ -9,7 +9,6 @@ from autotx.intents import Intent from autotx.transactions import TransactionBase from autotx.smart_accounts.smart_account import SmartAccount -from autotx.utils import http_requests from autotx.utils.ethereum.networks import NetworkInfo class LocalBiconomySmartAccount(SmartAccount): @@ -46,36 +45,37 @@ async def on_intents_ready(self, intents: list[Intent]) -> bool | str: return False async def get_address(self) -> str: - response = await http_requests.get( - f"http://localhost:7080/api/v1/account/address?chainId={self.web3.eth.chain_id}", - headers={"Content-Type": "application/json"}, - ) + async with aiohttp.ClientSession() as session: + response = await session.get( + f"http://localhost:7080/api/v1/account/address?chainId={self.web3.eth.chain_id}", + headers={"Content-Type": "application/json"}, + ) - if response.status != 200: - raise ValueError(f"Failed to get address: Biconomy API internal error") - - return cast(str, await response.json()) + if response.status != 200: + raise ValueError(f"Failed to get address: Biconomy API internal error") + + return cast(str, await response.json()) async def send_transaction(self, transaction: TransactionBase) -> None: - response = await http_requests.post( - f"http://localhost:7080/api/v1/account/transactions?chainId={self.web3.eth.chain_id}", - headers={"Content-Type": "application/json"}, - data=json.dumps([transaction]), - ) - - if response.status != 200: - raise ValueError(f"Transaction failed: {await response.json()}") + async with aiohttp.ClientSession() as session: + response = await session.post( + f"http://localhost:7080/api/v1/account/transactions?chainId={self.web3.eth.chain_id}", + headers={"Content-Type": "application/json"}, + data=json.dumps([transaction]), + ) + if response.status != 200: + raise ValueError(f"Transaction failed: {await response.json()}") - print(f"Transaction sent: {await response.json()}") + print(f"Transaction sent: {await response.json()}") async def send_transactions(self, transactions: list[TransactionBase]) -> None: - response = await http_requests.post( - f"http://localhost:7080/api/v1/account/transactions?chainId={self.web3.eth.chain_id}", - headers={"Content-Type": "application/json"}, - data=json.dumps(transactions) - ) - - if response.status != 200: - raise ValueError(f"Transaction failed: Biconomy API internal error") - - print(f"Transaction sent: {await response.json()}") \ No newline at end of file + async with aiohttp.ClientSession() as session: + response = await session.post( + f"http://localhost:7080/api/v1/account/transactions?chainId={self.web3.eth.chain_id}", + headers={"Content-Type": "application/json"}, + data=json.dumps(transactions) + ) + if response.status != 200: + raise ValueError(f"Transaction failed: Biconomy API internal error") + + print(f"Transaction sent: {await response.json()}") \ No newline at end of file diff --git a/autotx/utils/ethereum/lifi/__init__.py b/autotx/utils/ethereum/lifi/__init__.py index a921a0d..5cd288a 100644 --- a/autotx/utils/ethereum/lifi/__init__.py +++ b/autotx/utils/ethereum/lifi/__init__.py @@ -3,7 +3,6 @@ import re import aiohttp -from autotx.utils import http_requests from autotx.utils.constants import LIFI_API_KEY from autotx.eth_address import ETHAddress from autotx.utils.ethereum.networks import ChainId @@ -71,17 +70,24 @@ async def get_quote_to_amount( } headers = add_authorization_info_if_provided(params) - attempt_count = 0 - while True: - try: - return await handle_lifi_response(await http_requests.post(cls.BASE_URL + "/quote/contractCalls", json=params, headers=headers)) - except Exception as e: - if "No available quotes for the requested transfer" in str(e) or "Unable to find quote to match expected output" in str(e): + async with aiohttp.ClientSession() as session: + attempt_count = 0 + while True: + try: + response = await session.post(cls.BASE_URL + "/quote/contractCalls", json=params, headers=headers, timeout=10) + result = await handle_lifi_response(response) + return result + except asyncio.TimeoutError as e: if attempt_count < 5: attempt_count += 1 - await asyncio.sleep(1) + await asyncio.sleep(0.5) continue - raise e + except Exception as e: + if "No available quotes for the requested transfer" in str(e) or "Unable to find quote to match expected output" in str(e): + if attempt_count < 5: + attempt_count += 1 + await asyncio.sleep(0.5) + continue @classmethod async def get_quote_from_amount( @@ -104,14 +110,21 @@ async def get_quote_from_amount( } headers = add_authorization_info_if_provided(params) - attempt_count = 0 - while True: - try: - return await handle_lifi_response(await http_requests.get(cls.BASE_URL + "/quote", params=params, headers=headers)) - except Exception as e: - if "No available quotes for the requested transfer" in str(e) or "Unable to find quote to match expected output" in str(e): + async with aiohttp.ClientSession() as session: + attempt_count = 0 + while True: + try: + response = await session.get(cls.BASE_URL + "/quote", params=params, headers=headers, timeout=10) + result = await handle_lifi_response(response) + return result + except asyncio.TimeoutError as e: if attempt_count < 5: attempt_count += 1 - await asyncio.sleep(1) + await asyncio.sleep(0.5) continue - raise e \ No newline at end of file + except Exception as e: + if "No available quotes for the requested transfer" in str(e) or "Unable to find quote to match expected output" in str(e): + if attempt_count < 5: + attempt_count += 1 + await asyncio.sleep(0.5) + continue \ No newline at end of file diff --git a/autotx/utils/http_requests.py b/autotx/utils/http_requests.py deleted file mode 100644 index a13ed29..0000000 --- a/autotx/utils/http_requests.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import Any -import aiohttp - - -async def get(url: str, params: dict[str, Any] = {}, headers: dict[str, Any] | None = None) -> aiohttp.ClientResponse: - async with aiohttp.ClientSession() as session: - return await session.get(url, params=params, headers=headers) - -async def post(url: str, headers: dict[str, Any] | None = None, json: dict[str, Any] = {}, data: Any = None) -> aiohttp.ClientResponse: - async with aiohttp.ClientSession() as session: - return await session.post(url, headers=headers, json=json, data=data) From 305acc46ea8e51bd43d939dc0410c7fa849332ac Mon Sep 17 00:00:00 2001 From: nerfZael Date: Fri, 28 Jun 2024 17:45:35 +0200 Subject: [PATCH 19/23] added constants for test timeouts --- .../tests/agents/token/research/test_advanced.py | 5 +++-- .../tests/agents/token/research/test_research.py | 13 +++++++------ .../token/research/test_research_and_swap.py | 5 +++-- .../token/research/test_research_swap_and_send.py | 7 ++++--- autotx/tests/agents/token/send/test_send.py | 11 ++++++----- autotx/tests/agents/token/test_swap.py | 15 ++++++++------- autotx/tests/agents/token/test_swap_and_send.py | 9 +++++---- autotx/tests/conftest.py | 4 ++++ 8 files changed, 40 insertions(+), 29 deletions(-) diff --git a/autotx/tests/agents/token/research/test_advanced.py b/autotx/tests/agents/token/research/test_advanced.py index 6f307f3..22cd364 100644 --- a/autotx/tests/agents/token/research/test_advanced.py +++ b/autotx/tests/agents/token/research/test_advanced.py @@ -2,10 +2,11 @@ from autotx.tests.agents.token.research.test_research import get_top_token_addresses_by_market_cap from autotx.eth_address import ETHAddress +from autotx.tests.conftest import MAX_TEST_TIMEOUT_SEC, SLOW_TEST_TIMEOUT_SEC from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance from autotx.utils.ethereum.get_native_balance import get_native_balance -@pytest.mark.timeout(180) +@pytest.mark.timeout(SLOW_TEST_TIMEOUT_SEC) def test_research_and_swap_many_tokens_subjective_simple(smart_account, auto_tx): uni_address = ETHAddress(auto_tx.network.tokens["uni"]) @@ -43,7 +44,7 @@ def test_research_and_swap_many_tokens_subjective_simple(smart_account, auto_tx) assert ai_token_balance_in_safe > 0 assert meme_token_balance_in_safe > 0 -@pytest.mark.timeout(500) +@pytest.mark.timeout(MAX_TEST_TIMEOUT_SEC) def test_research_and_swap_many_tokens_subjective_complex(smart_account, auto_tx): starting_balance = get_native_balance(smart_account.web3, smart_account.address) diff --git a/autotx/tests/agents/token/research/test_research.py b/autotx/tests/agents/token/research/test_research.py index afbb423..e98773a 100644 --- a/autotx/tests/agents/token/research/test_research.py +++ b/autotx/tests/agents/token/research/test_research.py @@ -6,9 +6,10 @@ ) from autotx.eth_address import ETHAddress +from autotx.tests.conftest import FAST_TEST_TIMEOUT_SEC from autotx.utils.ethereum.networks import ChainId -@pytest.mark.timeout(120) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def get_top_token_addresses_by_market_cap(category: str, network: str, count: int, auto_tx) -> list[ETHAddress]: tokens = get_coingecko().coins.get_markets(vs_currency="usd", category=category, per_page=250) tokens_in_network = filter_token_list_by_network( @@ -17,7 +18,7 @@ def get_top_token_addresses_by_market_cap(category: str, network: str, count: in return [ETHAddress(auto_tx.network.tokens[token["symbol"].lower()]) for token in tokens_in_network[:count]] -@pytest.mark.timeout(120) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_price_change_information(auto_tx): token_information = get_coingecko().coins.get_id( id="starknet", @@ -37,7 +38,7 @@ def test_price_change_information(auto_tx): str(price_change) in "\n".join(result.info_messages).lower() or str(price_change_rounded) in "\n".join(result.info_messages).lower() ) -@pytest.mark.timeout(120) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_get_top_5_tokens_from_base(auto_tx): tokens = get_coingecko().coins.get_markets( vs_currency="usd", category="base-ecosystem" @@ -50,7 +51,7 @@ def test_get_top_5_tokens_from_base(auto_tx): symbol: str = token["symbol"] assert symbol.lower() in "\n".join(result.info_messages).lower() -@pytest.mark.timeout(120) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_get_top_5_most_traded_tokens_from_l1(auto_tx): tokens = get_coingecko().coins.get_markets( vs_currency="usd", category="layer-1", order="volume_desc" @@ -63,7 +64,7 @@ def test_get_top_5_most_traded_tokens_from_l1(auto_tx): symbol: str = token["symbol"] assert symbol.lower() in "\n".join(result.info_messages).lower() -@pytest.mark.timeout(120) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_get_top_5_memecoins(auto_tx): tokens = get_coingecko().coins.get_markets(vs_currency="usd", category="meme-token") tokens_in_network = filter_token_list_by_network( @@ -78,7 +79,7 @@ def test_get_top_5_memecoins(auto_tx): symbol: str = token["symbol"] assert symbol.lower() in "\n".join(result.info_messages).lower() -@pytest.mark.timeout(120) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_get_top_5_memecoins_in_optimism(auto_tx): tokens = get_coingecko().coins.get_markets(vs_currency="usd", category="meme-token") prompt = "What are the top 5 meme coins on Optimism?" diff --git a/autotx/tests/agents/token/research/test_research_and_swap.py b/autotx/tests/agents/token/research/test_research_and_swap.py index cbceadd..ad494e7 100644 --- a/autotx/tests/agents/token/research/test_research_and_swap.py +++ b/autotx/tests/agents/token/research/test_research_and_swap.py @@ -1,10 +1,11 @@ import pytest from autotx.tests.agents.token.research.test_research import get_top_token_addresses_by_market_cap +from autotx.tests.conftest import FAST_TEST_TIMEOUT_SEC, SLOW_TEST_TIMEOUT_SEC from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance from autotx.utils.ethereum.get_native_balance import get_native_balance -@pytest.mark.timeout(120) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_research_and_buy_one(smart_account, auto_tx): prompt = ( @@ -18,7 +19,7 @@ def test_research_and_buy_one(smart_account, auto_tx): token_balance_in_safe = get_erc20_balance(smart_account.web3, token_address, smart_account.address) assert token_balance_in_safe > 1000 -@pytest.mark.timeout(180) +@pytest.mark.timeout(SLOW_TEST_TIMEOUT_SEC) def test_research_and_buy_multiple(smart_account, auto_tx): old_eth_balance = get_native_balance(smart_account.web3, smart_account.address) diff --git a/autotx/tests/agents/token/research/test_research_swap_and_send.py b/autotx/tests/agents/token/research/test_research_swap_and_send.py index aa44e67..eb891a2 100644 --- a/autotx/tests/agents/token/research/test_research_swap_and_send.py +++ b/autotx/tests/agents/token/research/test_research_swap_and_send.py @@ -1,12 +1,13 @@ import pytest from autotx.tests.agents.token.research.test_research import get_top_token_addresses_by_market_cap +from autotx.tests.conftest import SLOW_TEST_TIMEOUT_SEC from autotx.utils.ethereum import get_erc20_balance from autotx.utils.ethereum.get_native_balance import get_native_balance DIFFERENCE_PERCENTAGE = 0.01 -@pytest.mark.timeout(180) +@pytest.mark.timeout(SLOW_TEST_TIMEOUT_SEC) def test_research_buy_one_send_one(smart_account, auto_tx, test_accounts): receiver = test_accounts[0] @@ -25,7 +26,7 @@ def test_research_buy_one_send_one(smart_account, auto_tx, test_accounts): assert token_balance_in_safe / receiver_balance < DIFFERENCE_PERCENTAGE -@pytest.mark.timeout(180) +@pytest.mark.timeout(SLOW_TEST_TIMEOUT_SEC) def test_research_buy_one_send_multiple(smart_account, auto_tx, test_accounts): receiver_1 = test_accounts[0] @@ -48,7 +49,7 @@ def test_research_buy_one_send_multiple(smart_account, auto_tx, test_accounts): assert meme_token_balance_in_safe > 10000 -@pytest.mark.timeout(180) +@pytest.mark.timeout(SLOW_TEST_TIMEOUT_SEC) def test_research_buy_multiple_send_multiple(smart_account, auto_tx, test_accounts): receiver_1 = test_accounts[0] diff --git a/autotx/tests/agents/token/send/test_send.py b/autotx/tests/agents/token/send/test_send.py index b183c41..263a5b8 100644 --- a/autotx/tests/agents/token/send/test_send.py +++ b/autotx/tests/agents/token/send/test_send.py @@ -1,9 +1,10 @@ import pytest +from autotx.tests.conftest import FAST_TEST_TIMEOUT_SEC from autotx.utils.ethereum import get_erc20_balance from autotx.utils.ethereum.get_native_balance import get_native_balance -@pytest.mark.timeout(60) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_send_native(smart_account, auto_tx, test_accounts): receiver = test_accounts[0] balance = get_native_balance(smart_account.web3, receiver) @@ -14,7 +15,7 @@ def test_send_native(smart_account, auto_tx, test_accounts): balance = get_native_balance(smart_account.web3, receiver) assert balance == 1 -@pytest.mark.timeout(60) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_send_erc20(smart_account, auto_tx, usdc, test_accounts): receiver = test_accounts[0] @@ -29,7 +30,7 @@ def test_send_erc20(smart_account, auto_tx, usdc, test_accounts): assert balance + 10 == new_balance -@pytest.mark.timeout(60) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_send_native_sequential(smart_account, auto_tx, test_accounts): receiver = test_accounts[0] @@ -43,7 +44,7 @@ def test_send_native_sequential(smart_account, auto_tx, test_accounts): balance = get_native_balance(smart_account.web3, receiver) assert balance == 1.5 -@pytest.mark.timeout(60) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_send_erc20_parallel(smart_account, auto_tx, usdc, test_accounts): receiver_one = test_accounts[0] @@ -62,7 +63,7 @@ def test_send_erc20_parallel(smart_account, auto_tx, usdc, test_accounts): assert balance_one + 2 == new_balance_one assert balance_two + 3 == new_balance_two -@pytest.mark.timeout(60) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_send_eth_multiple(smart_account, auto_tx, usdc, test_accounts): receiver_1 = test_accounts[0] diff --git a/autotx/tests/agents/token/test_swap.py b/autotx/tests/agents/token/test_swap.py index 212ee36..2fecdcc 100644 --- a/autotx/tests/agents/token/test_swap.py +++ b/autotx/tests/agents/token/test_swap.py @@ -1,12 +1,13 @@ import pytest +from autotx.tests.conftest import FAST_TEST_TIMEOUT_SEC from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance from autotx.utils.ethereum.networks import NetworkInfo from autotx.eth_address import ETHAddress DIFFERENCE_PERCENTAGE = 1.01 -@pytest.mark.timeout(60) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_swap_with_non_default_token(smart_account, auto_tx): network_info = NetworkInfo(smart_account.web3.eth.chain_id) shib_address = ETHAddress(network_info.tokens["shib"]) @@ -21,7 +22,7 @@ def test_swap_with_non_default_token(smart_account, auto_tx): expected_shib_amount = 100000 assert expected_shib_amount <= new_balance <= expected_shib_amount * DIFFERENCE_PERCENTAGE -@pytest.mark.timeout(60) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_swap_native(smart_account, auto_tx): network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) @@ -33,7 +34,7 @@ def test_swap_native(smart_account, auto_tx): expected_usdc_amount = 100 assert expected_usdc_amount <= new_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE -@pytest.mark.timeout(60) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_swap_multiple_1(smart_account, auto_tx): network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) @@ -51,7 +52,7 @@ def test_swap_multiple_1(smart_account, auto_tx): assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount_plus_slippage - expected_usdc_amount assert wbtc_balance < get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) -@pytest.mark.timeout(60) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_swap_multiple_2(smart_account, auto_tx): network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) @@ -67,7 +68,7 @@ def test_swap_multiple_2(smart_account, auto_tx): assert expected_amount <= usdc_balance assert wbtc_balance < get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) -@pytest.mark.timeout(60) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_swap_triple(smart_account, auto_tx): network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) @@ -88,7 +89,7 @@ def test_swap_triple(smart_account, auto_tx): assert expected_uni_amount <= uni_balance <= expected_uni_amount * DIFFERENCE_PERCENTAGE assert expected_wbtc_amount <= wbtc_balance <= expected_wbtc_amount * DIFFERENCE_PERCENTAGE -@pytest.mark.timeout(60) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_swap_complex_1(smart_account, auto_tx): # This one is complex because it confuses the LLM with WBTC amount network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) @@ -103,7 +104,7 @@ def test_swap_complex_1(smart_account, auto_tx): # This one is complex because i assert expected_usdc_amount <= usdc_balance <= expected_usdc_amount * DIFFERENCE_PERCENTAGE assert wbtc_balance < get_erc20_balance(smart_account.web3, wbtc_address, smart_account.address) -@pytest.mark.timeout(60) +@pytest.mark.timeout(FAST_TEST_TIMEOUT_SEC) def test_swap_complex_2(smart_account, auto_tx): # This one is complex because it confuses the LLM with WBTC amount network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) diff --git a/autotx/tests/agents/token/test_swap_and_send.py b/autotx/tests/agents/token/test_swap_and_send.py index 333377a..04d4e6b 100644 --- a/autotx/tests/agents/token/test_swap_and_send.py +++ b/autotx/tests/agents/token/test_swap_and_send.py @@ -1,11 +1,12 @@ import pytest +from autotx.tests.conftest import SLOW_TEST_TIMEOUT_SEC from autotx.utils.ethereum import get_erc20_balance, get_native_balance from autotx.utils.ethereum.networks import NetworkInfo from autotx.eth_address import ETHAddress DIFFERENCE_PERCENTAGE = 1.01 -@pytest.mark.timeout(60) +@pytest.mark.timeout(SLOW_TEST_TIMEOUT_SEC) def test_swap_and_send_simple(smart_account, auto_tx, test_accounts): network_info = NetworkInfo(smart_account.web3.eth.chain_id) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) @@ -22,7 +23,7 @@ def test_swap_and_send_simple(smart_account, auto_tx, test_accounts): assert excepted_safe_wbtc_balance <= new_wbtc_safe_address <= new_wbtc_safe_address * DIFFERENCE_PERCENTAGE assert new_receiver_wbtc_balance == 0.01 -@pytest.mark.timeout(180) +@pytest.mark.timeout(SLOW_TEST_TIMEOUT_SEC) def test_swap_and_send_complex(smart_account, auto_tx, test_accounts): network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) @@ -44,7 +45,7 @@ def test_swap_and_send_complex(smart_account, auto_tx, test_accounts): assert expected_usdc_safe_balance <= new_usdc_safe_address <= expected_usdc_safe_balance * DIFFERENCE_PERCENTAGE assert new_receiver_usdc_balance == 50 -@pytest.mark.timeout(60) +@pytest.mark.timeout(SLOW_TEST_TIMEOUT_SEC) def test_send_and_swap_simple(smart_account, auto_tx, test_accounts): network_info = NetworkInfo(smart_account.web3.eth.chain_id) wbtc_address = ETHAddress(network_info.tokens["wbtc"]) @@ -68,7 +69,7 @@ def test_send_and_swap_simple(smart_account, auto_tx, test_accounts): assert new_receiver_wbtc_balance == receiver_wbtc_balance assert new_receiver_native_balance == receiver_native_balance + 0.1 -@pytest.mark.timeout(180) +@pytest.mark.timeout(SLOW_TEST_TIMEOUT_SEC) def test_send_and_swap_complex(smart_account, auto_tx, test_accounts): network_info = NetworkInfo(smart_account.web3.eth.chain_id) usdc_address = ETHAddress(network_info.tokens["usdc"]) diff --git a/autotx/tests/conftest.py b/autotx/tests/conftest.py index cdcba8b..f7e6167 100644 --- a/autotx/tests/conftest.py +++ b/autotx/tests/conftest.py @@ -20,6 +20,10 @@ transfer_erc20, ) +FAST_TEST_TIMEOUT_SEC = 120 +SLOW_TEST_TIMEOUT_SEC = 200 +MAX_TEST_TIMEOUT_SEC = 500 + @pytest.fixture(autouse=True) def start_and_stop_local_fork(): start() From e7ab90c9c965effcba7b39111e0e96452ebb8ec4 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Fri, 28 Jun 2024 18:44:39 +0200 Subject: [PATCH 20/23] executing transactions one by one in safe if the multisend fails to execute --- autotx/tests/conftest.py | 2 +- autotx/utils/ethereum/SafeManager.py | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/autotx/tests/conftest.py b/autotx/tests/conftest.py index f7e6167..53cb369 100644 --- a/autotx/tests/conftest.py +++ b/autotx/tests/conftest.py @@ -20,7 +20,7 @@ transfer_erc20, ) -FAST_TEST_TIMEOUT_SEC = 120 +FAST_TEST_TIMEOUT_SEC = 100 SLOW_TEST_TIMEOUT_SEC = 200 MAX_TEST_TIMEOUT_SEC = 500 diff --git a/autotx/utils/ethereum/SafeManager.py b/autotx/utils/ethereum/SafeManager.py index 542d591..d8c013a 100644 --- a/autotx/utils/ethereum/SafeManager.py +++ b/autotx/utils/ethereum/SafeManager.py @@ -191,14 +191,22 @@ def execute_multisend_tx(self, txs: list[TxParams | dict[str, Any]], safe_nonce: safe_tx.sign(self.agent.key.hex()) - safe_tx.call(tx_sender_address=self.dev_account.address) - - tx_hash, _ = safe_tx.execute( - tx_sender_private_key=self.dev_account.key.hex() - ) + try: + safe_tx.call(tx_sender_address=self.dev_account.address) + tx_hash, _ = safe_tx.execute( + tx_sender_private_key=self.dev_account.key.hex() + ) + return tx_hash + except Exception as e: + if "revert: GS013" in str(e): + print(str(e)) + print("Executing transactions one by one to get a more detailed revert message") + nonce = self.track_nonce(safe_nonce) + for i, tx in enumerate(txs): + print(f"Executing transaction {i + 1}...") + self.execute_tx(tx, nonce + i) + - return tx_hash - def post_transaction(self, tx: TxParams | dict[str, Any], safe_nonce: Optional[int] = None) -> None: ts_api = TransactionServiceApi( self.network.chain_id, ethereum_client=self.client, base_url=self.transaction_service_url From 0749613e4ef202bf1dbd9287acb13aacd2e15c84 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Fri, 28 Jun 2024 18:51:54 +0200 Subject: [PATCH 21/23] raising exception where appropriate --- autotx/utils/ethereum/SafeManager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/autotx/utils/ethereum/SafeManager.py b/autotx/utils/ethereum/SafeManager.py index d8c013a..5319aec 100644 --- a/autotx/utils/ethereum/SafeManager.py +++ b/autotx/utils/ethereum/SafeManager.py @@ -182,7 +182,6 @@ def execute_tx(self, tx: TxParams | dict[str, Any], safe_nonce: Optional[int] = raise Exception("Unknown error executing transaction", e) - def execute_multisend_tx(self, txs: list[TxParams | dict[str, Any]], safe_nonce: Optional[int] = None) -> HexBytes: if not self.dev_account: raise ValueError("Dev account not set. This function should not be called in production.") @@ -205,7 +204,7 @@ def execute_multisend_tx(self, txs: list[TxParams | dict[str, Any]], safe_nonce: for i, tx in enumerate(txs): print(f"Executing transaction {i + 1}...") self.execute_tx(tx, nonce + i) - + raise e def post_transaction(self, tx: TxParams | dict[str, Any], safe_nonce: Optional[int] = None) -> None: ts_api = TransactionServiceApi( From 6532b069d4aba1c627b7e54c9016bbd522a7279b Mon Sep 17 00:00:00 2001 From: nerfZael Date: Fri, 28 Jun 2024 20:14:49 +0200 Subject: [PATCH 22/23] increased test timeout of research tests --- autotx/tests/agents/token/research/test_advanced.py | 4 ++-- .../tests/agents/token/research/test_research_and_swap.py | 4 ++-- .../agents/token/research/test_research_swap_and_send.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/autotx/tests/agents/token/research/test_advanced.py b/autotx/tests/agents/token/research/test_advanced.py index 22cd364..9b93c65 100644 --- a/autotx/tests/agents/token/research/test_advanced.py +++ b/autotx/tests/agents/token/research/test_advanced.py @@ -2,11 +2,11 @@ from autotx.tests.agents.token.research.test_research import get_top_token_addresses_by_market_cap from autotx.eth_address import ETHAddress -from autotx.tests.conftest import MAX_TEST_TIMEOUT_SEC, SLOW_TEST_TIMEOUT_SEC +from autotx.tests.conftest import MAX_TEST_TIMEOUT_SEC from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance from autotx.utils.ethereum.get_native_balance import get_native_balance -@pytest.mark.timeout(SLOW_TEST_TIMEOUT_SEC) +@pytest.mark.timeout(MAX_TEST_TIMEOUT_SEC) def test_research_and_swap_many_tokens_subjective_simple(smart_account, auto_tx): uni_address = ETHAddress(auto_tx.network.tokens["uni"]) diff --git a/autotx/tests/agents/token/research/test_research_and_swap.py b/autotx/tests/agents/token/research/test_research_and_swap.py index ad494e7..15f631b 100644 --- a/autotx/tests/agents/token/research/test_research_and_swap.py +++ b/autotx/tests/agents/token/research/test_research_and_swap.py @@ -1,7 +1,7 @@ import pytest from autotx.tests.agents.token.research.test_research import get_top_token_addresses_by_market_cap -from autotx.tests.conftest import FAST_TEST_TIMEOUT_SEC, SLOW_TEST_TIMEOUT_SEC +from autotx.tests.conftest import FAST_TEST_TIMEOUT_SEC, MAX_TEST_TIMEOUT_SEC, SLOW_TEST_TIMEOUT_SEC from autotx.utils.ethereum.get_erc20_balance import get_erc20_balance from autotx.utils.ethereum.get_native_balance import get_native_balance @@ -19,7 +19,7 @@ def test_research_and_buy_one(smart_account, auto_tx): token_balance_in_safe = get_erc20_balance(smart_account.web3, token_address, smart_account.address) assert token_balance_in_safe > 1000 -@pytest.mark.timeout(SLOW_TEST_TIMEOUT_SEC) +@pytest.mark.timeout(MAX_TEST_TIMEOUT_SEC) def test_research_and_buy_multiple(smart_account, auto_tx): old_eth_balance = get_native_balance(smart_account.web3, smart_account.address) diff --git a/autotx/tests/agents/token/research/test_research_swap_and_send.py b/autotx/tests/agents/token/research/test_research_swap_and_send.py index eb891a2..e6acdfb 100644 --- a/autotx/tests/agents/token/research/test_research_swap_and_send.py +++ b/autotx/tests/agents/token/research/test_research_swap_and_send.py @@ -1,13 +1,13 @@ import pytest from autotx.tests.agents.token.research.test_research import get_top_token_addresses_by_market_cap -from autotx.tests.conftest import SLOW_TEST_TIMEOUT_SEC +from autotx.tests.conftest import MAX_TEST_TIMEOUT_SEC, SLOW_TEST_TIMEOUT_SEC from autotx.utils.ethereum import get_erc20_balance from autotx.utils.ethereum.get_native_balance import get_native_balance DIFFERENCE_PERCENTAGE = 0.01 -@pytest.mark.timeout(SLOW_TEST_TIMEOUT_SEC) +@pytest.mark.timeout(MAX_TEST_TIMEOUT_SEC) def test_research_buy_one_send_one(smart_account, auto_tx, test_accounts): receiver = test_accounts[0] @@ -26,7 +26,7 @@ def test_research_buy_one_send_one(smart_account, auto_tx, test_accounts): assert token_balance_in_safe / receiver_balance < DIFFERENCE_PERCENTAGE -@pytest.mark.timeout(SLOW_TEST_TIMEOUT_SEC) +@pytest.mark.timeout(MAX_TEST_TIMEOUT_SEC) def test_research_buy_one_send_multiple(smart_account, auto_tx, test_accounts): receiver_1 = test_accounts[0] @@ -49,7 +49,7 @@ def test_research_buy_one_send_multiple(smart_account, auto_tx, test_accounts): assert meme_token_balance_in_safe > 10000 -@pytest.mark.timeout(SLOW_TEST_TIMEOUT_SEC) +@pytest.mark.timeout(MAX_TEST_TIMEOUT_SEC) def test_research_buy_multiple_send_multiple(smart_account, auto_tx, test_accounts): receiver_1 = test_accounts[0] From fd61f5cd06b6918865b3aee9478347ba9ffead10 Mon Sep 17 00:00:00 2001 From: nerfZael Date: Fri, 28 Jun 2024 22:06:49 +0200 Subject: [PATCH 23/23] updated tests with intents and using more gaming categories as valid --- .../agents/token/research/test_advanced.py | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/autotx/tests/agents/token/research/test_advanced.py b/autotx/tests/agents/token/research/test_advanced.py index 9b93c65..8b2ee51 100644 --- a/autotx/tests/agents/token/research/test_advanced.py +++ b/autotx/tests/agents/token/research/test_advanced.py @@ -21,8 +21,12 @@ def test_research_and_swap_many_tokens_subjective_simple(smart_account, auto_tx) ending_balance = get_native_balance(smart_account.web3, smart_account.address) - gaming_token_address = get_top_token_addresses_by_market_cap("gaming", "MAINNET", 1, auto_tx)[0] - gaming_token_balance_in_safe = get_erc20_balance(smart_account.web3, gaming_token_address, smart_account.address) + gaming_token_address1 = get_top_token_addresses_by_market_cap("gaming", "MAINNET", 1, auto_tx)[0] + gaming_token_address2 = get_top_token_addresses_by_market_cap("on-chain-gaming", "MAINNET", 1, auto_tx)[0] + gaming_token_address3 = get_top_token_addresses_by_market_cap("play-to-earn", "MAINNET", 1, auto_tx)[0] + gaming_token_balance_in_safe1 = get_erc20_balance(smart_account.web3, gaming_token_address1, smart_account.address) + gaming_token_balance_in_safe2 = get_erc20_balance(smart_account.web3, gaming_token_address2, smart_account.address) + gaming_token_balance_in_safe3 = get_erc20_balance(smart_account.web3, gaming_token_address3, smart_account.address) ai_token_address = get_top_token_addresses_by_market_cap("artificial-intelligence", "MAINNET", 1, auto_tx)[0] ai_token_balance_in_safe = get_erc20_balance(smart_account.web3, ai_token_address, smart_account.address) @@ -32,15 +36,15 @@ def test_research_and_swap_many_tokens_subjective_simple(smart_account, auto_tx) # Verify the balance is lower by max 3 ETH assert starting_balance - ending_balance <= 3 - # Verify there are at least 3 transactions - assert len(result.transactions) == 3 - # Verify there are only swap transactions - assert all([tx.summary.startswith("Swap") for tx in result.transactions]) + # Verify there are at exactly 3 intents + assert len(result.intents) == 3 + # Verify there are only swap intents + assert all([intent.type == "buy" or intent.type == "sell" for intent in result.intents]) # Verify the tokens are different - assert len(set([tx.summary.split(" ")[-1] for tx in result.transactions])) == 3 + assert len(set([intent.to_token.symbol for intent in result.intents])) == 3 # Verify the tokens are in the safe - assert gaming_token_balance_in_safe > 0 + assert gaming_token_balance_in_safe1 > 0 or gaming_token_balance_in_safe2 > 0 or gaming_token_balance_in_safe3 > 0 assert ai_token_balance_in_safe > 0 assert meme_token_balance_in_safe > 0 @@ -57,9 +61,9 @@ def test_research_and_swap_many_tokens_subjective_complex(smart_account, auto_tx # Verify the balance is lower by max 3 ETH assert starting_balance - ending_balance <= 3 - # Verify there are at least 5 transactions - assert len(result.transactions) == 10 - # Verify there are only swap transactions - assert all([tx.summary.startswith("Swap") for tx in result.transactions]) + # Verify there are at exactly 10 intents + assert len(result.intents) == 10 + # Verify there are only swap intents + assert all([intent.type == "buy" or intent.type == "sell" for intent in result.intents]) # Verify the tokens are different - assert len(set([tx.summary.split(" ")[-1] for tx in result.transactions])) == 10 + assert len(set([intent.to_token.symbol for intent in result.intents])) == 10