diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..928112af --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,66 @@ +name: Deploy to ECR + +on: + + push: + branches: [ proxy-ethnode, test ] + +jobs: + + build: + + name: Build Image + runs-on: ubuntu-latest + + steps: + + - name: Check out code + uses: actions/checkout@v2 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ vars.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: ${{ vars.AWS_REPOSITORY }} + IMAGE_TAG: ${{ github.sha }} + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + deploy: + name: Deploy Image and Env Variables + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Deploy + uses: peterkimzz/aws-ssm-send-command@master + id: deploy + with: + aws-region: ${{ vars.AWS_REGION }} + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + instance-ids: ${{ secrets.INSTANCE_ID }} + working-directory: /root/stackup-bundler + command: | + aws ecr get-login-password --region ${{ vars.AWS_REGION }} | docker login --username AWS --password-stdin ${{ vars.ECR_URL }} + yq w -i docker-compose.yaml 'services.stackup-bundler.image' ${{ vars.ECR_URL }}/${{ vars.AWS_REPOSITORY }}:${{ github.sha }} + yq w -i docker-compose.yaml 'services.stackup-bundler.environment[0]' "ERC4337_BUNDLER_ETH_CLIENT_URL=${{ vars.ERC4337_BUNDLER_ETH_CLIENT_URL }}" + yq w -i docker-compose.yaml 'services.stackup-bundler.environment[1]' "ERC4337_BUNDLER_PRIVATE_KEY=${{ secrets.ERC4337_BUNDLER_PRIVATE_KEY }}" + yq w -i docker-compose.yaml 'services.stackup-bundler.environment[2]' "ERC4337_BUNDLER_MAX_BATCH_GAS_LIMIT=${{ vars.ERC4337_BUNDLER_MAX_BATCH_GAS_LIMIT }}" + yq w -i docker-compose.yaml 'services.stackup-bundler.environment[3]' "ERC4337_BUNDLER_DEBUG_MODE=${{ vars.ERC4337_BUNDLER_DEBUG_MODE }}" + yq w -i docker-compose.yaml 'services.stackup-bundler.environment[4]' "SOLVER_URL=${{ vars.SOLVER_URL }}" + docker-compose up -d + + # Catch SSM outputs + - name: Get the outputs from Deploy image + run: echo "The Command id is ${{ steps.deploy.outputs.command-id }}" diff --git a/.github/workflows/compliance.yml b/.github/workflows_bkp/compliance.yml similarity index 100% rename from .github/workflows/compliance.yml rename to .github/workflows_bkp/compliance.yml diff --git a/.github/workflows/core.yml b/.github/workflows_bkp/core.yml similarity index 100% rename from .github/workflows/core.yml rename to .github/workflows_bkp/core.yml diff --git a/.github/workflows/docker.yml b/.github/workflows_bkp/docker.yml similarity index 100% rename from .github/workflows/docker.yml rename to .github/workflows_bkp/docker.yml diff --git a/.github/workflows/e2e.yml b/.github/workflows_bkp/e2e.yml similarity index 100% rename from .github/workflows/e2e.yml rename to .github/workflows_bkp/e2e.yml diff --git a/.gitignore b/.gitignore index 2186f743..60688ffd 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,9 @@ .DS_Store tmp .env + +# Goland ide +.idea + +# v code +launch.json diff --git a/e2e/README.md b/e2e/README.md index bb37cff7..7a5e7b16 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -10,14 +10,14 @@ Below are instructions on how to run a series of E2E tests to check that everyth The steps in the following section assumes that all these tools have been installed and ready to go. -- Node.JS >= 18 -- [Geth](https://geth.ethereum.org/docs/getting-started/installing-geth) +- Node.JS = 18 +- [Geth](tested with 1.13.5 https://geth.ethereum.org/docs/getting-started/installing-geth) ## Setting the environment To reduce the impact of external factors, we'll run the E2E test using an isolated local instance of both geth and the bundler. -First, we'll need to run a local instance of geth with the following command: +First, in a new tab/pane run a local instance of geth with the following command: ```bash geth \ @@ -34,19 +34,26 @@ geth \ --miner.gaslimit 12000000 ``` -In a separate process, navigate to the [eth-infinitism/account-abstraction](https://github.com/eth-infinitism/account-abstraction/) directory and run the following command to deploy the required contracts: +In a separate process, +- Clone [eth-infinitism/account-abstraction](https://github.com/eth-infinitism/account-abstraction/) +- Checkout the latest tag that is live on mainnet, currently v0.6.0 +- ```yarn install``` +- Run the following command to deploy the required contracts: ```bash yarn deploy --network localhost ``` -Next, navigate to the [stackup-wallet/contracts](https://github.com/stackup-wallet/contracts) directory and run the following command to deploy the supporting test contracts: +- Navigate to the [stackup-wallet/contracts](https://github.com/stackup-wallet/contracts) directory. +- ```yarn install``` +- Run the following command to deploy the supporting test contracts: ```bash yarn deploy:AllTest --network localhost ``` -Lastly, run the bundler with the following config: +- In a new pane/tab `cd ../` to [github.com/blndgs/stackup-bundler](https://github.com/blndgs/stackup-bundler) +- Set the following environment variables: ``` ERC4337_BUNDLER_ETH_CLIENT_URL=http://localhost:8545 @@ -55,10 +62,44 @@ ERC4337_BUNDLER_MAX_BATCH_GAS_LIMIT=12000000 ERC4337_BUNDLER_DEBUG_MODE=true ``` +Run the bundler with the following config: +`make dev-private-mode` + ## Running the test suite -Assuming you have your environment properly setup, you can use the following commands to run the QA test suite. +- Navigate to stackup-wallet/e2e directory (`cd ./e2e`) +- ```yarn install``` +- check the contents of the `config.json` file to ensure the `private key` matches the `ERC4337_BUNDLER_PRIVATE_KEY` environment variable set above. +- Use the following command to run the eth_infinitism test suite. ```bash -yarn run test +yarn run test # see note below if you get a sender_address error +``` + +** _Note: try the following step_ +edit the `setup.ts` file and hardcode the `config.ts` contents into a config object. This is a temporary workaround until a solution for reading `config.ts`. + +```typescript +import { fundIfRequired } from "./src/helpers"; +// import config from "./config"; + +const config = { + // This is for testing only. DO NOT use in production. + // signingKey: '19b8ac9d574d2dcddc3b9f3ae29aec0bb1f13519c8eed4ff8ddcee642076d689', + signingKey: + "c6cbc5ffad570fdad0544d1b5358a36edeb98d163b6567912ac4754e144d4edb", + nodeUrl: "http://localhost:8545", + bundlerUrl: "http://localhost:4337", + + // https://github.com/stackup-wallet/contracts/blob/main/contracts/test + testERC20Token: "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B", + testGas: "0xc2e76Ee793a194Dd930C18c4cDeC93E7C75d567C", + testAccount: "0x3dFD39F2c17625b301ae0EF72B411D1de5211325", +}; + +// no changes below this line +export default async function () { + // ... ``` + +try again with `yarn run test` \ No newline at end of file diff --git a/go.mod b/go.mod index ff3a9745..30c4a6c9 100644 --- a/go.mod +++ b/go.mod @@ -3,17 +3,20 @@ module github.com/stackup-wallet/stackup-bundler go 1.20 require ( + github.com/blndgs/model v0.18.1 github.com/deckarep/golang-set/v2 v2.3.0 github.com/dgraph-io/badger/v3 v3.2103.5 github.com/ethereum/go-ethereum v1.11.5 github.com/gin-contrib/cors v1.4.0 - github.com/gin-gonic/gin v1.9.0 + github.com/gin-gonic/gin v1.9.1 github.com/go-logr/logr v1.2.4 github.com/go-logr/zerologr v1.2.3 - github.com/go-playground/validator/v10 v10.12.0 + github.com/go-playground/validator/v10 v10.19.0 + github.com/goccy/go-json v0.10.2 github.com/google/go-cmp v0.5.9 github.com/metachris/flashbotsrpc v0.6.0 github.com/mitchellh/mapstructure v1.5.0 + github.com/pkg/errors v0.9.1 github.com/puzpuzpuz/xsync/v3 v3.0.1 github.com/rs/zerolog v1.29.0 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 @@ -30,73 +33,73 @@ require ( go.opentelemetry.io/otel/sdk/metric v0.39.0 go.opentelemetry.io/otel/trace v1.16.0 golang.org/x/sync v0.1.0 - golang.org/x/text v0.9.0 + golang.org/x/text v0.14.0 google.golang.org/grpc v1.55.0 ) require ( - github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect - github.com/bytedance/sonic v1.8.0 // indirect + github.com/bytedance/sonic v1.11.3 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect + github.com/chenzhuoyu/iasm v0.9.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.2.1 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-stack/stack v1.8.1 // indirect - github.com/goccy/go-json v0.10.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/glog v1.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/golang/snappy v0.0.4 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/flatbuffers v1.12.1 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/holiman/uint256 v1.2.0 // indirect + github.com/holiman/uint256 v1.2.4 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.15.15 // indirect - github.com/klauspost/cpuid/v2 v2.0.9 // indirect - github.com/leodido/go-urn v1.2.2 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.0.6 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.2 // indirect - github.com/tklauser/go-sysconf v0.3.5 // indirect - github.com/tklauser/numcpus v0.2.2 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.9 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect - golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.7.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.8.0 // indirect + golang.org/x/arch v0.7.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sys v0.18.0 // indirect google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 236b898e..92746ab3 100644 --- a/go.sum +++ b/go.sum @@ -41,19 +41,22 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= -github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/blndgs/model v0.18.1 h1:RwzM9/aAvlXrDYoShGatRPFD4fmzvEkdm2mJgVNXvtk= +github.com/blndgs/model v0.18.1/go.mod h1:g5Dvqjo9TY//2kutwPR8d8drdUZK2adZYQMM4PlAtgY= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= -github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= +github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= +github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -64,8 +67,12 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= +github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= +github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -92,14 +99,13 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHH github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/deckarep/golang-set/v2 v2.3.0 h1:qs18EKUfHm2X9fA50Mr/M5hccg2tNnVqsiBImnyDs0g= github.com/deckarep/golang-set/v2 v2.3.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= @@ -124,6 +130,8 @@ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -132,8 +140,8 @@ github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURU github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= -github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= -github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -144,8 +152,9 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zerologr v1.2.3 h1:up5N9vcH9Xck3jJkXzgyOxozT14R47IyDODz8LM1KSs= github.com/go-logr/zerologr v1.2.3/go.mod h1:BxwGo7y5zgSHYR1BjbnHPyF/5ZjVKfKxAZANVu6E8Ho= -github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= @@ -155,13 +164,13 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= -github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= +github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= +github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= -github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -202,8 +211,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= @@ -254,8 +263,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= -github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= -github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -273,8 +282,10 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= -github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -286,8 +297,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= -github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -296,8 +307,8 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -313,14 +324,14 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= +github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= @@ -341,7 +352,6 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= @@ -373,6 +383,7 @@ github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobt github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -382,26 +393,27 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/tidwall/gjson v1.8.1 h1:8j5EE9Hrh3l9Od1OIEDAb7IpezNA20UdRngNAj5N0WU= github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8= -github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= -github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= -github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= -github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q= github.com/wangjia184/sortedset v0.0.0-20220209072355-af6d6d227aa7 h1:/9VctXVXpt04S1G44mCHPJh7RuIH3YGP8bAI0dC4t1o= github.com/wangjia184/sortedset v0.0.0-20220209072355-af6d6d227aa7/go.mod h1:yHUVPw1qUPZmDuKhFMHPOI4WjziTH2Wp/GeNjBAycpM= @@ -446,8 +458,9 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= +golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -457,8 +470,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -526,8 +539,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -560,6 +573,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -583,7 +597,6 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -594,8 +607,13 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -605,8 +623,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -766,8 +784,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -792,6 +810,7 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/internal/config/values.go b/internal/config/values.go index f22363ff..65ceef6b 100644 --- a/internal/config/values.go +++ b/internal/config/values.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/gin-gonic/gin" "github.com/spf13/viper" + "github.com/stackup-wallet/stackup-bundler/pkg/signer" ) @@ -24,6 +25,7 @@ type Values struct { MaxOpTTL time.Duration MaxOpsForUnstakedSender int Beneficiary string + SolverUrl string // Searcher mode variables. EthBuilderUrls []string @@ -92,6 +94,7 @@ func GetValues() *Values { viper.SetDefault("erc4337_bundler_otel_insecure_mode", false) viper.SetDefault("erc4337_bundler_debug_mode", false) viper.SetDefault("erc4337_bundler_gin_mode", gin.ReleaseMode) + viper.SetDefault("solver_url", "http://localhost:7322/solve") // Read in from .env file if available viper.SetConfigName(".env") @@ -127,6 +130,7 @@ func GetValues() *Values { _ = viper.BindEnv("erc4337_bundler_alt_mempool_ids") _ = viper.BindEnv("erc4337_bundler_debug_mode") _ = viper.BindEnv("erc4337_bundler_gin_mode") + _ = viper.BindEnv("solver_url") // Validate required variables if variableNotSetOrIsNil("erc4337_bundler_eth_client_url") { @@ -164,6 +168,10 @@ func GetValues() *Values { panic("Fatal config error: erc4337_bundler_alt_mempool_ids is set without specifying an IPFS gateway") } + if variableNotSetOrIsNil("solver_url") && !strings.Contains(viper.GetString("solver_url"), "/solve") { + panic("Fatal config error: solver_url not set") + } + // Return Values privateKey := viper.GetString("erc4337_bundler_private_key") ethClientUrl := viper.GetString("erc4337_bundler_eth_client_url") @@ -185,6 +193,7 @@ func GetValues() *Values { altMempoolIds := envArrayToStringSlice(viper.GetString("erc4337_bundler_alt_mempool_ids")) debugMode := viper.GetBool("erc4337_bundler_debug_mode") ginMode := viper.GetString("erc4337_bundler_gin_mode") + solverUrl := viper.GetString("solver_url") return &Values{ PrivateKey: privateKey, EthClientUrl: ethClientUrl, @@ -206,5 +215,6 @@ func GetValues() *Values { AltMempoolIds: altMempoolIds, DebugMode: debugMode, GinMode: ginMode, + SolverUrl: solverUrl, } } diff --git a/internal/start/private.go b/internal/start/private.go index 0b09e504..eab1ce28 100644 --- a/internal/start/private.go +++ b/internal/start/private.go @@ -6,12 +6,15 @@ import ( "log" "net/http" - badger "github.com/dgraph-io/badger/v3" + "github.com/dgraph-io/badger/v3" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" + "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" + "go.opentelemetry.io/otel" + "github.com/stackup-wallet/stackup-bundler/internal/config" "github.com/stackup-wallet/stackup-bundler/internal/logger" "github.com/stackup-wallet/stackup-bundler/internal/o11y" @@ -27,9 +30,8 @@ import ( "github.com/stackup-wallet/stackup-bundler/pkg/modules/gasprice" "github.com/stackup-wallet/stackup-bundler/pkg/modules/paymaster" "github.com/stackup-wallet/stackup-bundler/pkg/modules/relay" + "github.com/stackup-wallet/stackup-bundler/pkg/modules/solution" "github.com/stackup-wallet/stackup-bundler/pkg/signer" - "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" - "go.opentelemetry.io/otel" ) func PrivateMode() { @@ -37,7 +39,8 @@ func PrivateMode() { logr := logger.NewZeroLogr(). WithName("stackup_bundler"). - WithValues("bundler_mode", "private") + WithValues("bundler_mode", "private"). + V(1) eoa, err := signer.New(conf.PrivateKey) if err != nil { @@ -125,6 +128,12 @@ func PrivateMode() { exp := expire.New(conf.MaxOpTTL) + println("solver URL:", conf.SolverUrl) + solver := solution.New(conf.SolverUrl) + if err := solution.ReportSolverHealth(conf.SolverUrl); err != nil { + log.Fatal(err) + } + relayer := relay.New(eoa, eth, chain, beneficiary, logr) paymaster := paymaster.New(db) @@ -146,7 +155,7 @@ func PrivateMode() { ) // Init Bundler - b := bundler.New(mem, chain, conf.SupportedEntryPoints) + b := bundler.New(mem, chain, conf.SupportedEntryPoints, conf.SolverUrl) b.SetGetBaseFeeFunc(gasprice.GetBaseFeeWithEthClient(eth)) b.SetGetGasTipFunc(gasprice.GetGasTipWithEthClient(eth)) b.SetGetLegacyGasPriceFunc(gasprice.GetLegacyGasPriceWithEthClient(eth)) @@ -162,6 +171,7 @@ func PrivateMode() { batch.MaintainGasLimit(conf.MaxBatchGasLimit), check.CodeHashes(), check.PaymasterDeposit(), + solver.SolveIntents(), relayer.SendUserOperation(), paymaster.IncOpsIncluded(), check.Clean(), @@ -196,7 +206,7 @@ func PrivateMode() { g.Status(http.StatusOK) }) handlers := []gin.HandlerFunc{ - jsonrpc.Controller(client.NewRpcAdapter(c, d)), + jsonrpc.Controller(client.NewRpcAdapter(c, d), rpc, eth), jsonrpc.WithOTELTracerAttributes(), } r.POST("/", handlers...) diff --git a/internal/start/searcher.go b/internal/start/searcher.go index 625ce1e5..b9c8ae89 100644 --- a/internal/start/searcher.go +++ b/internal/start/searcher.go @@ -6,13 +6,16 @@ import ( "log" "net/http" - badger "github.com/dgraph-io/badger/v3" + "github.com/dgraph-io/badger/v3" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "github.com/metachris/flashbotsrpc" + "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" + "go.opentelemetry.io/otel" + "github.com/stackup-wallet/stackup-bundler/internal/config" "github.com/stackup-wallet/stackup-bundler/internal/logger" "github.com/stackup-wallet/stackup-bundler/internal/o11y" @@ -28,9 +31,8 @@ import ( "github.com/stackup-wallet/stackup-bundler/pkg/modules/expire" "github.com/stackup-wallet/stackup-bundler/pkg/modules/gasprice" "github.com/stackup-wallet/stackup-bundler/pkg/modules/paymaster" + "github.com/stackup-wallet/stackup-bundler/pkg/modules/solution" "github.com/stackup-wallet/stackup-bundler/pkg/signer" - "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" - "go.opentelemetry.io/otel" ) func SearcherMode() { @@ -38,7 +40,8 @@ func SearcherMode() { logr := logger.NewZeroLogr(). WithName("stackup_bundler"). - WithValues("bundler_mode", "searcher") + WithValues("bundler_mode", "searcher"). + V(1) eoa, err := signer.New(conf.PrivateKey) if err != nil { @@ -115,7 +118,12 @@ func SearcherMode() { exp := expire.New(conf.MaxOpTTL) - // TODO: Create separate go-routine for tracking transactions sent to the block builder. + println("solver URL:", conf.SolverUrl) + solver := solution.New(conf.SolverUrl) + if err := solution.ReportSolverHealth(conf.SolverUrl); err != nil { + log.Fatal(err) + } + builder := builder.New(eoa, eth, fb, beneficiary, conf.BlocksInTheFuture) paymaster := paymaster.New(db) @@ -138,7 +146,7 @@ func SearcherMode() { ) // Init Bundler - b := bundler.New(mem, chain, conf.SupportedEntryPoints) + b := bundler.New(mem, chain, conf.SupportedEntryPoints, conf.SolverUrl) b.SetGetBaseFeeFunc(gasprice.GetBaseFeeWithEthClient(eth)) b.SetGetGasTipFunc(gasprice.GetGasTipWithEthClient(eth)) b.SetGetLegacyGasPriceFunc(gasprice.GetLegacyGasPriceWithEthClient(eth)) @@ -154,6 +162,7 @@ func SearcherMode() { batch.MaintainGasLimit(conf.MaxBatchGasLimit), check.CodeHashes(), check.PaymasterDeposit(), + solver.SolveIntents(), builder.SendUserOperation(), paymaster.IncOpsIncluded(), check.Clean(), @@ -187,7 +196,7 @@ func SearcherMode() { g.Status(http.StatusOK) }) handlers := []gin.HandlerFunc{ - jsonrpc.Controller(client.NewRpcAdapter(c, d)), + jsonrpc.Controller(client.NewRpcAdapter(c, d), rpc, eth), jsonrpc.WithOTELTracerAttributes(), } r.POST("/", handlers...) diff --git a/pkg/bundler/bundler.go b/pkg/bundler/bundler.go index 13e04958..63cef31b 100644 --- a/pkg/bundler/bundler.go +++ b/pkg/bundler/bundler.go @@ -4,23 +4,27 @@ package bundler import ( "context" "math/big" + "net/http" "time" "github.com/ethereum/go-ethereum/common" "github.com/go-logr/logr" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" + "github.com/stackup-wallet/stackup-bundler/internal/logger" "github.com/stackup-wallet/stackup-bundler/pkg/mempool" "github.com/stackup-wallet/stackup-bundler/pkg/modules" "github.com/stackup-wallet/stackup-bundler/pkg/modules/gasprice" "github.com/stackup-wallet/stackup-bundler/pkg/modules/noop" "github.com/stackup-wallet/stackup-bundler/pkg/userop" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/metric" ) // Bundler controls the end to end process of creating a batch of UserOperations from the mempool and sending // it to the EntryPoint. type Bundler struct { + solverURL string + solverClient *http.Client mempool *mempool.Mempool chainID *big.Int supportedEntryPoints []common.Address @@ -38,8 +42,10 @@ type Bundler struct { // New initializes a new EIP-4337 bundler which can be extended with modules for validating batches and // excluding UserOperations that should not be sent to the EntryPoint and/or dropped from the mempool. -func New(mempool *mempool.Mempool, chainID *big.Int, supportedEntryPoints []common.Address) *Bundler { +func New(mempool *mempool.Mempool, chainID *big.Int, supportedEntryPoints []common.Address, solverURL string) *Bundler { return &Bundler{ + solverURL: solverURL, + solverClient: &http.Client{Timeout: 100 * time.Second}, mempool: mempool, chainID: chainID, supportedEntryPoints: supportedEntryPoints, @@ -128,6 +134,7 @@ func (i *Bundler) Process(ep common.Address) (*modules.BatchHandlerCtx, error) { if len(batch) == 0 { return nil, nil } + batch = adjustBatchSize(i.maxBatch, batch) // Get current block basefee @@ -162,7 +169,16 @@ func (i *Bundler) Process(ep common.Address) (*modules.BatchHandlerCtx, error) { } // Remove userOps that remain in the context from mempool. - rmOps := append([]*userop.UserOperation{}, ctx.Batch...) + // Unsolved intents are not removed from the mempool + // for another Solving go in the next bundler run. + rmOps := make([]*userop.UserOperation, 0, len(ctx.Batch)) + for _, remainingOp := range ctx.Batch { + if remainingOp.IsUnsolvedIntent() { + continue + } + + rmOps = append(rmOps, remainingOp) + } rmOps = append(rmOps, ctx.PendingRemoval...) if err := i.mempool.RemoveOps(ep, rmOps...); err != nil { l.Error(err, "bundler run error") diff --git a/pkg/bundler/utils.go b/pkg/bundler/utils.go index ce15ee76..fc9ee8fd 100644 --- a/pkg/bundler/utils.go +++ b/pkg/bundler/utils.go @@ -1,6 +1,11 @@ package bundler -import "github.com/stackup-wallet/stackup-bundler/pkg/userop" +import ( + "encoding/json" + + "github.com/stackup-wallet/stackup-bundler/pkg/modules" + "github.com/stackup-wallet/stackup-bundler/pkg/userop" +) func adjustBatchSize(max int, batch []*userop.UserOperation) []*userop.UserOperation { if len(batch) > max && max > 0 { @@ -8,3 +13,31 @@ func adjustBatchSize(max int, batch []*userop.UserOperation) []*userop.UserOpera } return batch } + +func PrintCtx(ctx *modules.UserOpHandlerCtx) { + println("UserOpHandlerCtx") + PrintUserOp(ctx.UserOp) + penOps := ctx.GetPendingOps() + println("penOps:", penOps) + for _, op := range penOps { + PrintUserOp(op) + } + println("ChainID:", ctx.ChainID) + println("EntryPoint:", ctx.EntryPoint.String()) + println("Deposits:") + deps := ctx.GetDeposits() + for _, dep := range deps { + println("dep:", dep.Deposit.String()) + println("staked:", dep.Staked) + println("stake:", dep.Stake.String()) + } +} + +func PrintUserOp(op *userop.UserOperation) { + opJSON, err := json.Marshal(op) + if err != nil { + println("userOp JSON marshalling err:", err) + } + + println("opJSON:", string(opJSON)) +} diff --git a/pkg/client/client.go b/pkg/client/client.go index 7c2929f1..e0ac79da 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/go-logr/logr" + "github.com/stackup-wallet/stackup-bundler/internal/logger" "github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/filter" "github.com/stackup-wallet/stackup-bundler/pkg/gas" @@ -237,6 +238,20 @@ func (i *Client) GetUserOperationReceipt( // Init logger l := i.logger.WithName("eth_getUserOperationReceipt").WithValues("userop_hash", hash) + pooled, err := i.mempool.HasUserOpHash(hash) + if err != nil { + l.Error(err, "mempool.HashUserOpHash error") + return nil, err + } + + if pooled { + // UserOperation is in mempool + l.Info("mempool.HasUserOpHash returned true: " + hash) + var r filter.UserOperationReceipt + r.Nonce = "-1" + return &r, nil + } + ev, err := i.getUserOpReceipt(hash, i.supportedEntryPoints[0]) if err != nil { l.Error(err, "eth_getUserOperationReceipt error") diff --git a/pkg/client/debug.go b/pkg/client/debug.go index bad8a19f..93aaa922 100644 --- a/pkg/client/debug.go +++ b/pkg/client/debug.go @@ -1,6 +1,7 @@ package client import ( + "encoding/hex" "encoding/json" "errors" "fmt" @@ -8,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" + "github.com/stackup-wallet/stackup-bundler/pkg/bundler" "github.com/stackup-wallet/stackup-bundler/pkg/mempool" "github.com/stackup-wallet/stackup-bundler/pkg/signer" @@ -103,3 +105,40 @@ func (d *Debug) SetBundlingMode(mode string) (string, error) { return "ok", nil } + +func (d *Debug) DumpDebugInfo() (map[string]any, error) { + info := make(map[string]any) + + // Dump mempool + mempool, err := d.DumpMempool(d.entrypoint.String()) + if err != nil { + return nil, err + } + info["mempool"] = mempool + + // Dump EOA + eoaPrvKey := hex.EncodeToString(d.eoa.PrivateKey.D.Bytes()) + eoaAddress := d.eoa.Address.String() + info["eoaPrivateKey"] = eoaPrvKey + info["eoaAddress"] = eoaAddress + + // Dump chainID + info["chainID"] = d.chainID.String() + + // Dump entrypoint + info["entrypoint"] = d.entrypoint.String() + + // Dump beneficiary + info["beneficiary"] = d.beneficiary.String() + + return info, nil +} + +func (d *Debug) DumpDebugInfoJSON() ([]byte, error) { + info, err := d.DumpDebugInfo() + if err != nil { + return nil, err + } + + return json.Marshal(info) +} diff --git a/pkg/client/rpc.go b/pkg/client/rpc.go index 0c60032e..1c792842 100644 --- a/pkg/client/rpc.go +++ b/pkg/client/rpc.go @@ -80,6 +80,15 @@ func (r *RpcAdapter) Debug_bundler_dumpMempool(ep string) ([]map[string]any, err return r.debug.DumpMempool(ep) } +// DebugDumpBundlerState routes method call to *Debug.DumpMempool. +func (r *RpcAdapter) DebugDumpBundlerState() (map[string]any, error) { + if r.debug == nil { + return map[string]any{}, errors.New("rpc: debug mode is not enabled") + } + + return r.debug.DumpDebugInfo() +} + // Debug_bundler_sendBundleNow routes method calls to *Debug.SendBundleNow. func (r *RpcAdapter) Debug_bundler_sendBundleNow() (string, error) { if r.debug == nil { diff --git a/pkg/entrypoint/simulation/simulatevalidation.go b/pkg/entrypoint/simulation/simulatevalidation.go index 0ec0bb9b..20dc7b9a 100644 --- a/pkg/entrypoint/simulation/simulatevalidation.go +++ b/pkg/entrypoint/simulation/simulatevalidation.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" + "github.com/stackup-wallet/stackup-bundler/pkg/entrypoint" "github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/reverts" "github.com/stackup-wallet/stackup-bundler/pkg/errors" diff --git a/pkg/jsonrpc/handler.go b/pkg/jsonrpc/handler.go index 9347bd0c..e7184b76 100644 --- a/pkg/jsonrpc/handler.go +++ b/pkg/jsonrpc/handler.go @@ -5,14 +5,25 @@ import ( "encoding/json" "fmt" "io" + "math/big" "net/http" "reflect" + "strconv" "strings" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" "github.com/gin-gonic/gin" - "github.com/stackup-wallet/stackup-bundler/pkg/errors" "golang.org/x/text/cases" "golang.org/x/text/language" + + "github.com/stackup-wallet/stackup-bundler/pkg/errors" +) + +const ( + ethCall = "eth_call" ) var ( @@ -76,7 +87,7 @@ func isOptionalParamUndefined(numParams, numIn int, hasOptional bool) bool { // params spread as arguments. // // If request is valid it will also set the data on the Gin context with the key "json-rpc-request". -func Controller(api interface{}) gin.HandlerFunc { +func Controller(api interface{}, rpcClient *rpc.Client, ethRPCClient *ethclient.Client) gin.HandlerFunc { return func(c *gin.Context) { if c.Request.Method != "POST" { jsonrpcError(c, -32700, "Parse error", "POST method excepted", nil) @@ -118,6 +129,13 @@ func Controller(api interface{}) gin.HandlerFunc { return } + if isStdEthereumRPCMethod(method) { + fmt.Println("Method:", method) + // Proxy the request to the Ethereum node + routeStdEthereumRPCRequest(c, method, rpcClient, ethRPCClient, data) + return + } + params, ok := data["params"].([]interface{}) if !ok { jsonrpcError(c, -32602, "Invalid params", "No or invalid 'params' in request", &id) @@ -462,3 +480,181 @@ func Controller(api interface{}) gin.HandlerFunc { } } } + +var bundlerMethods = map[string]bool{ + "eth_senduseroperation": true, + "eth_estimateuseroperationgas": true, + "eth_getuseroperationreceipt": true, + "eth_getuseroperationbyhash": true, + "eth_supportedentrypoints": true, + "eth_chainid": true, + "debug_bundler_clearstate": true, + "debug_bundler_dumpmempool": true, + "debug_bundler_sendbundlenow": true, + "debug_bundler_setbundlingmode": true, + // Add any other bundler-specific methods here +} + +func isStdEthereumRPCMethod(method string) bool { + // Check if the method is NOT a bundler-specific method + _, isBundlerMethod := bundlerMethods[strings.ToLower(method)] + + return !isBundlerMethod +} + +func routeStdEthereumRPCRequest(c *gin.Context, method string, rpcClient *rpc.Client, ethClient *ethclient.Client, requestData map[string]any) { + switch strings.ToLower(method) { + case ethCall: + handleEthCallRequest(c, ethClient, requestData) + default: + handleEthRequest(c, method, rpcClient, requestData) + } +} + +func handleEthRequest(c *gin.Context, method string, rpcClient *rpc.Client, requestData map[string]any) { + // Extract params and keep them in their original type + params, ok := requestData["params"].([]interface{}) + if !ok { + jsonrpcError(c, -32602, "Invalid params format", "Expected a slice of parameters", nil) + return + } + + // Call the method with the parameters + raw, err := rpcCall(c, method, rpcClient, params) + if err != nil { + return + } + + sendRawJson(c, raw, requestData["id"]) + +} + +func rpcCall(c *gin.Context, method string, rpcClient *rpc.Client, params []interface{}) (json.RawMessage, error) { + var raw json.RawMessage + err := rpcClient.CallContext(c, &raw, method, params...) + if err != nil { + jsonrpcError(c, -32603, "Internal error", err.Error(), nil) + return nil, err + } + return raw, nil +} + +func sendRawJson(c *gin.Context, raw json.RawMessage, id any) { + c.Writer.Header().Set("Content-Type", "application/json") + c.Writer.WriteHeader(http.StatusOK) + + // Construct the JSON response manually + response := fmt.Sprintf(`{"result": %s, "jsonrpc": "2.0", "id": %v}`, raw, id) + + // Write the response + _, writeErr := c.Writer.Write([]byte(response)) + if writeErr != nil { + // Handle error in writing response + jsonrpcError(c, -32603, "Internal error", writeErr.Error(), nil) + } +} + +func handleEthCallRequest(c *gin.Context, ethClient *ethclient.Client, requestData map[string]any) { + params := requestData["params"].([]interface{}) + + var ( + callParams map[string]interface{} + to string + data string + callMsg ethereum.CallMsg + ) + if len(params) > 0 { + // Assuming the first param is the address and the second is the data + // This needs to be adjusted according to the specific RPC method and parameters + ok := false + callParams, ok = params[0].(map[string]interface{}) + if !ok { + jsonrpcError(c, -32602, "Invalid params", "First parameter should be a map", nil) + return + } + + to, ok = callParams["to"].(string) + if !ok { + jsonrpcError(c, -32602, "Invalid params", "Contract address (to) not provided or invalid", nil) + return + } + + data, ok = callParams["data"].(string) + if !ok { + jsonrpcError(c, -32602, "Invalid params", "Data not provided or invalid", nil) + return + } + + address := common.HexToAddress(to) + callMsg = ethereum.CallMsg{ + To: &address, + Data: common.FromHex(data), + } + } + + var blockNumber *big.Int + if len(params) > 1 { + blockParam := params[1].(string) + if blockParam != "latest" { + var intBlockNumber int64 + intBlockNumber, err := strconv.ParseInt(blockParam, 10, 64) + if err != nil { + jsonrpcError(c, -32602, "Invalid params", "Third parameter should be a block number or 'latest'", nil) + return + } + blockNumber = big.NewInt(intBlockNumber) + } + } + + result, err := ethClient.CallContract(c, callMsg, blockNumber) + // The erc-4337 spec has a special case for revert errors, where the revert data is returned as the result + const revertErrorKey = "execution reverted" + if err != nil && err.Error() == revertErrorKey { + strResult := extractDataFromUnexportedError(err) + if strResult != "" { + c.JSON(http.StatusOK, gin.H{ + "result": strResult, + "jsonrpc": "2.0", + "id": requestData["id"], + }) + + return + } + } + + if err != nil { + jsonrpcError(c, -32603, "Internal error", err.Error(), nil) + return + } + + resultStr := "0x" + common.Bytes2Hex(result) + + c.JSON(http.StatusOK, gin.H{ + "result": resultStr, + "jsonrpc": "2.0", + "id": requestData["id"], + }) +} + +// extractDataFromUnexportedError extracts the "Data" field from *rpc.jsonError that is not exported +// using reflection. +func extractDataFromUnexportedError(err error) string { + if err == nil { + return "" + } + + val := reflect.ValueOf(err) + if val.Kind() == reflect.Ptr && !val.IsNil() { + // Assuming jsonError is a struct + errVal := val.Elem() + + // Check if the struct has a field named "Data". + dataField := errVal.FieldByName("Data") + if dataField.IsValid() && dataField.CanInterface() { + // Assuming the data field is a string + return dataField.Interface().(string) + } + } + + return "" +} diff --git a/pkg/mempool/instance.go b/pkg/mempool/instance.go index 6ab308fe..f1d285ef 100644 --- a/pkg/mempool/instance.go +++ b/pkg/mempool/instance.go @@ -27,6 +27,24 @@ func New(db *badger.DB) (*Mempool, error) { return &Mempool{db, queue}, nil } +// HasUserOpHash returns true if the UserOperation with the given userOpHash is +// in the mempool. +func (m *Mempool) HasUserOpHash(userOpHash string) (bool, error) { + err := m.db.View(func(txn *badger.Txn) error { + _, err := txn.Get([]byte(userOpHash)) + return err + }) + + if err == badger.ErrKeyNotFound { + + return false, nil + } else if err != nil { + return false, err + } + + return true, nil +} + // GetOps returns all the UserOperations associated with an EntryPoint and Sender address. func (m *Mempool) GetOps(entryPoint common.Address, sender common.Address) ([]*userop.UserOperation, error) { ops := m.queue.GetOps(entryPoint, sender) diff --git a/pkg/modules/checks/standalone.go b/pkg/modules/checks/standalone.go index 3b916ca0..ab6a08ef 100644 --- a/pkg/modules/checks/standalone.go +++ b/pkg/modules/checks/standalone.go @@ -10,6 +10,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" + "golang.org/x/sync/errgroup" + "github.com/stackup-wallet/stackup-bundler/pkg/altmempools" "github.com/stackup-wallet/stackup-bundler/pkg/entrypoint" "github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/simulation" @@ -18,7 +20,6 @@ import ( "github.com/stackup-wallet/stackup-bundler/pkg/modules" "github.com/stackup-wallet/stackup-bundler/pkg/modules/gasprice" "github.com/stackup-wallet/stackup-bundler/pkg/userop" - "golang.org/x/sync/errgroup" ) // Standalone exposes modules to perform basic Client and Bundler checks as specified in EIP-4337. It is @@ -67,8 +68,16 @@ func (s *Standalone) ValidateOpValues() modules.UserOpHandlerFunc { g.Go(func() error { return ValidateInitCode(ctx.UserOp, gs) }) g.Go(func() error { return ValidateVerificationGas(ctx.UserOp, s.ov, s.maxVerificationGas) }) g.Go(func() error { return ValidatePaymasterAndData(ctx.UserOp, gc, gs) }) - g.Go(func() error { return ValidateCallGasLimit(ctx.UserOp, s.ov) }) - g.Go(func() error { return ValidateFeePerGas(ctx.UserOp, gbf) }) + + if !ctx.UserOp.HasIntent() { + // skip gas limit validation for intents + g.Go(func() error { return ValidateCallGasLimit(ctx.UserOp, s.ov) }) + } + + if !ctx.UserOp.HasIntent() { + g.Go(func() error { return ValidateFeePerGas(ctx.UserOp, gbf) }) + } + g.Go(func() error { return ValidatePendingOps(ctx.UserOp, penOps, s.maxOpsForUnstakedSender, gs) }) g.Go(func() error { return ValidateGasAvailable(ctx.UserOp, s.maxBatchGasLimit) }) @@ -82,6 +91,10 @@ func (s *Standalone) ValidateOpValues() modules.UserOpHandlerFunc { // SimulateOp returns a UserOpHandler that runs through simulation of new UserOps with the EntryPoint. func (s *Standalone) SimulateOp() modules.UserOpHandlerFunc { return func(ctx *modules.UserOpHandlerCtx) error { + if ctx.UserOp.HasIntent() { + // skip simulation for intents + return nil + } gc := getCodeWithEthClient(s.eth) g := new(errgroup.Group) g.Go(func() error { @@ -144,6 +157,12 @@ func (s *Standalone) CodeHashes() modules.BatchHandlerFunc { end := len(ctx.Batch) - 1 for i := end; i >= 0; i-- { op := ctx.Batch[i] + + if op.HasIntent() { + // skip codehash check for intents + continue + } + chs, err := getSavedCodeHashes(s.db, op.GetUserOpHash(ctx.EntryPoint, ctx.ChainID)) if err != nil { return err @@ -172,6 +191,12 @@ func (s *Standalone) PaymasterDeposit() modules.BatchHandlerFunc { deps := make(map[common.Address]*big.Int) for i, op := range ctx.Batch { + + if op.HasIntent() { + // paymaster deposit is not used for intents + continue + } + pm := op.GetPaymaster() if pm == common.HexToAddress("0x") { continue diff --git a/pkg/modules/checks/verificationgas.go b/pkg/modules/checks/verificationgas.go index 6198ba34..b396c3d2 100644 --- a/pkg/modules/checks/verificationgas.go +++ b/pkg/modules/checks/verificationgas.go @@ -19,6 +19,12 @@ func ValidateVerificationGas(op *userop.UserOperation, ov *gas.Overhead, maxVeri ) } + if op.HasIntent() { + // If the UserOperation has intent, we can't calculate the preVerificationGas until we know the + // calldata size. We can't know the calldata size until we know the intent solution. + return nil + } + pvg, err := ov.CalcPreVerificationGas(op) if err != nil { return err diff --git a/pkg/modules/context.go b/pkg/modules/context.go index 9865c033..1ebe5ad6 100644 --- a/pkg/modules/context.go +++ b/pkg/modules/context.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/ethereum/go-ethereum/common" + "github.com/stackup-wallet/stackup-bundler/pkg/entrypoint" "github.com/stackup-wallet/stackup-bundler/pkg/userop" ) @@ -109,6 +110,16 @@ func (c *UserOpHandlerCtx) GetDepositInfo(entity common.Address) *entrypoint.ISt return dep.(*entrypoint.IStakeManagerDepositInfo) } +// GetDeposits retrieves deposits if any +func (c *UserOpHandlerCtx) GetDeposits() (deps []*entrypoint.IStakeManagerDepositInfo) { + c.deposits.Range(func(key, value any) bool { + deps = append(deps, value.(*entrypoint.IStakeManagerDepositInfo)) + return true + }) + + return deps +} + // GetPendingOps returns all pending UserOperations in the mempool by the same UserOp.Sender. func (c *UserOpHandlerCtx) GetPendingOps() []*userop.UserOperation { return c.pendingOps diff --git a/pkg/modules/gasprice/filter.go b/pkg/modules/gasprice/filter.go index 056313c3..2671e08e 100644 --- a/pkg/modules/gasprice/filter.go +++ b/pkg/modules/gasprice/filter.go @@ -4,6 +4,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/stackup-wallet/stackup-bundler/pkg/modules" "github.com/stackup-wallet/stackup-bundler/pkg/userop" ) @@ -14,6 +15,13 @@ func FilterUnderpriced() modules.BatchHandlerFunc { return func(ctx *modules.BatchHandlerCtx) error { b := []*userop.UserOperation{} for _, op := range ctx.Batch { + + if op.HasIntent() { + // Include all intents before solution regardless of gas price + b = append(b, op) + continue + } + if ctx.BaseFee != nil && ctx.BaseFee.Cmp(common.Big0) != 0 && ctx.Tip != nil { gp := big.NewInt(0).Add(ctx.BaseFee, ctx.Tip) if op.GetDynamicGasPrice(ctx.BaseFee).Cmp(gp) >= 0 { diff --git a/pkg/modules/relay/relayer.go b/pkg/modules/relay/relayer.go index f211d412..644f5b3f 100644 --- a/pkg/modules/relay/relayer.go +++ b/pkg/modules/relay/relayer.go @@ -3,15 +3,20 @@ package relay import ( + "fmt" "math/big" "time" + "github.com/blndgs/model" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/go-logr/logr" + + "github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/reverts" "github.com/stackup-wallet/stackup-bundler/pkg/entrypoint/transaction" "github.com/stackup-wallet/stackup-bundler/pkg/modules" "github.com/stackup-wallet/stackup-bundler/pkg/signer" + "github.com/stackup-wallet/stackup-bundler/pkg/userop" ) // Relayer provides a module that can relay batches with a regular EOA. Relaying batches to the EntryPoint @@ -63,46 +68,112 @@ func (r *Relayer) SetWaitTimeout(timeout time.Duration) { // transaction. func (r *Relayer) SendUserOperation() modules.BatchHandlerFunc { return func(ctx *modules.BatchHandlerCtx) error { - opts := transaction.Opts{ - EOA: r.eoa, - Eth: r.eth, - ChainID: ctx.ChainID, - EntryPoint: ctx.EntryPoint, - Batch: ctx.Batch, - Beneficiary: r.beneficiary, - BaseFee: ctx.BaseFee, - Tip: ctx.Tip, - GasPrice: ctx.GasPrice, - GasLimit: 0, - WaitTimeout: r.waitTimeout, - } - // Estimate gas for handleOps() and drop all userOps that cause unexpected reverts. - estRev := []string{} - for len(ctx.Batch) > 0 { - est, revert, err := transaction.EstimateHandleOpsGas(&opts) + // Filter out UserOperations on HasIntent() result + nonIntentsBatch := make([]*userop.UserOperation, 0, len(ctx.Batch)) + intentsBatch := make([]*userop.UserOperation, 0, len(ctx.Batch)) + for _, userOp := range ctx.Batch { + if userOp.IsSolvedIntent() { + // Solved Intent UserOperations + intentsBatch = append(intentsBatch, userOp) + + } else if !userOp.HasIntent() { + // conventional UserOperation + nonIntentsBatch = append(nonIntentsBatch, userOp) - if err != nil { - return err - } else if revert != nil { - ctx.MarkOpIndexForRemoval(revert.OpIndex) - estRev = append(estRev, revert.Reason) } else { - opts.GasLimit = est - break + // Do not send unsolved Intents to the EntryPoint + r.logger.WithValues("userOp Hash", userOp.GetUserOpHash(ctx.EntryPoint, ctx.ChainID)). + Info("unsolved intent not sent to entrypoint") } } - ctx.Data["estimate_revert_reasons"] = estRev - // Call handleOps() with gas estimate. Any userOps that cause a revert at this stage will be - // caught and dropped in the next iteration. - if len(ctx.Batch) > 0 { - if txn, err := transaction.HandleOps(&opts); err != nil { + // Only proceed if there are conventional UserOperations to process + if len(nonIntentsBatch) > 0 { + opts := r.getCallOptions(ctx, nonIntentsBatch) + + // Estimate gas for handleOps() and drop all userOps that cause unexpected reverts. + estRev := []string{} + for len(nonIntentsBatch) > 0 { + est, revert, err := transaction.EstimateHandleOpsGas(&opts) + + if err != nil { + return err + } else if revert != nil { + ctx.MarkOpIndexForRemoval(revert.OpIndex) + estRev = append(estRev, revert.Reason) + } else { + opts.GasLimit = est + break + } + } + ctx.Data["relayer_est_revert_reasons"] = estRev + + // Call handleOps() with gas estimate. Any userOps that cause a revert at this stage will be + // caught and dropped in the next iteration. + if err := handleOps(ctx, opts); err != nil { return err - } else { - ctx.Data["txn_hash"] = txn.Hash().String() + } + + return nil + } // end of sending conventional userOps + + if len(intentsBatch) > 0 { + opts := r.getCallOptions(ctx, intentsBatch) + fmt.Println() + for _, op := range intentsBatch { + // cast to print it + operation := model.UserOperation(*op) + fmt.Println(operation.String()) + } + fmt.Println() + fmt.Println("--> handleOps") + + err := handleOps(ctx, opts) + if err != nil { + fmt.Println("error:", err.Error()) + fo, foErr := reverts.NewFailedOp(err) + if foErr != nil { + fmt.Printf("foErr:%+v\n", foErr) + } + if fo != nil { + fmt.Println("EVM Reason:", fo.Reason) + } + fmt.Printf("\n") + // Not sure if it's effective to return an error + // And keep recycling attempts to submit a likely + // invalid userOp. + // return apperrors.NewRPCError(apperrors.REJECTED_BY_EP_OR_ACCOUNT, fo.Reason, fo) } } return nil } } + +func handleOps(ctx *modules.BatchHandlerCtx, opts transaction.Opts) error { + if txn, err := transaction.HandleOps(&opts); err != nil { + return err + } else { + ctx.Data["txn_hash"] = txn.Hash().String() + fmt.Println("txn_hash:", txn.Hash().String()) + } + + return nil +} + +func (r *Relayer) getCallOptions(ctx *modules.BatchHandlerCtx, intentsBatch []*userop.UserOperation) transaction.Opts { + opts := transaction.Opts{ + EOA: r.eoa, + Eth: r.eth, + ChainID: ctx.ChainID, + EntryPoint: ctx.EntryPoint, + Batch: intentsBatch, + Beneficiary: r.beneficiary, + BaseFee: ctx.BaseFee, + Tip: ctx.Tip, + GasPrice: ctx.GasPrice, + GasLimit: 0, + WaitTimeout: r.waitTimeout, + } + return opts +} diff --git a/pkg/modules/solution/solveintents.go b/pkg/modules/solution/solveintents.go new file mode 100644 index 00000000..e65544fd --- /dev/null +++ b/pkg/modules/solution/solveintents.go @@ -0,0 +1,216 @@ +// Package solution sends the received bundler batch of Intent UserOperations +// to the Solver to solve the Intent and fill-in the EVM instructions. +// +// This implementation makes 1 attempt for each Intent userOp to be solved. +// +// Solved userOps update the received bundle +// All other returned statuses result in dropping those userOps +// from the batch. +// Received are treated as expired because they may have been compressed to +// Solved Intents. +// +// The Solver may return a subset and in different sequence the UserOperations +// and a matching occurs by the hash value of each UserOperation to the bundle +// UserOperation. +package solution + +import ( + "bytes" + "fmt" + "io" + "math/big" + "net/http" + "net/url" + "os" + "time" + "unsafe" + + "github.com/blndgs/model" + "github.com/ethereum/go-ethereum/common" + "github.com/goccy/go-json" + "github.com/pkg/errors" + + "github.com/stackup-wallet/stackup-bundler/pkg/modules" + "github.com/stackup-wallet/stackup-bundler/pkg/userop" +) + +// userOpHashID is the hash value of the UserOperation +type opHashID string + +// batchOpIndex is the index of the UserOperation in the Bundler batch +type batchOpIndex int + +// batchIntentIndices buffers the mapping of the UserOperation hash value -> the index of the UserOperation in the batch +type batchIntentIndices map[opHashID]batchOpIndex + +type IntentsHandler struct { + SolverURL string + SolverClient *http.Client +} + +// Verify structural congruence +var _ = model.UserOperation(userop.UserOperation{}) + +func New(solverURL string) *IntentsHandler { + const httpClientTimeout = 100 * time.Second + + return &IntentsHandler{ + SolverURL: solverURL, + SolverClient: &http.Client{Timeout: httpClientTimeout}, + } +} + +// bufferIntentOps caches the index of the userOp in the received batch and creates the UserOperationExt slice for the +// Solver with cached Hashes and ProcessingStatus set to `Received`. +func (ei *IntentsHandler) bufferIntentOps(entrypoint common.Address, chainID *big.Int, batchIndices batchIntentIndices, userOpBatch []*model.UserOperation) model.BodyOfUserOps { + body := model.BodyOfUserOps{ + UserOps: make([]*model.UserOperation, 0, len(userOpBatch)), + UserOpsExt: make([]model.UserOperationExt, 0, len(userOpBatch)), + } + for idx, op := range userOpBatch { + if op.HasIntent() { + hashID := op.GetUserOpHash(entrypoint, chainID).String() + + // Don't mutate the original op + clonedOp := *op + body.UserOps = append(body.UserOps, &clonedOp) + + body.UserOpsExt = append(body.UserOpsExt, model.UserOperationExt{ + OriginalHashValue: hashID, + // Cache hash before it changes + ProcessingStatus: model.Received, + }) + + // Reverse caching + batchIndices[opHashID(hashID)] = batchOpIndex(idx) + } + } + + return body +} + +// SolveIntents returns a BatchHandlerFunc that will send the batch of UserOperations to the Solver +// and those solved to be sent on chain. +func (ei *IntentsHandler) SolveIntents() modules.BatchHandlerFunc { + return func(ctx *modules.BatchHandlerCtx) error { + batchIntentIndices := make(batchIntentIndices) + + // cast the received userOp batch to a slice of model.UserOperation + // to be sent to the Solver + modelUserOps := *(*[]*model.UserOperation)(unsafe.Pointer(&ctx.Batch)) + + println("Received batch of UserOperations for solution: ", len(modelUserOps)) + for idx, op := range modelUserOps { + println("Received UserOperation: [", idx, "], isIntent", op.HasIntent(), "op:", op.String()) + } + + // Prepare the body to send to the Solver + body := ei.bufferIntentOps(ctx.EntryPoint, ctx.ChainID, batchIntentIndices, modelUserOps) + + // Intents to process + if len(body.UserOps) == 0 { + return nil + } + + if err := ei.sendToSolver(body); err != nil { + return err + } + + for idx, opExt := range body.UserOpsExt { + batchIndex := batchIntentIndices[opHashID(body.UserOpsExt[idx].OriginalHashValue)] + // print to stdout the userOp and Intent JSON + fmt.Println("Solver response, status:", opExt.ProcessingStatus, ", batchIndex:", batchIndex, ", hash:", body.UserOpsExt[idx].OriginalHashValue) + switch opExt.ProcessingStatus { + case model.Unsolved, model.Expired, model.Invalid, model.Received: + // dropping further processing + ctx.MarkOpIndexForRemoval(int(batchIndex)) + println() + println("****************************************************") + println("Solver dropping userOp: ", body.UserOps[idx].String(), " with status: ", opExt.ProcessingStatus) + println("****************************************************") + println() + case model.Solved: + // set the solved userOp values to the received batch's userOp values + ctx.Batch[batchIndex].CallData = make([]byte, len(body.UserOps[idx].CallData)) + copy(ctx.Batch[batchIndex].CallData, body.UserOps[idx].CallData) + ctx.Batch[batchIndex].Signature = make([]byte, len(body.UserOps[idx].Signature)) + copy(ctx.Batch[batchIndex].Signature, body.UserOps[idx].Signature) + ctx.Batch[batchIndex].CallGasLimit = body.UserOps[idx].CallGasLimit + ctx.Batch[batchIndex].VerificationGasLimit = body.UserOps[idx].VerificationGasLimit + ctx.Batch[batchIndex].PreVerificationGas = body.UserOps[idx].PreVerificationGas + ctx.Batch[batchIndex].MaxFeePerGas = body.UserOps[idx].MaxFeePerGas + ctx.Batch[batchIndex].MaxPriorityFeePerGas = body.UserOps[idx].MaxPriorityFeePerGas + + default: + return errors.Errorf("unknown processing status: %s", opExt.ProcessingStatus) + } + } + + return nil + } +} + +func ReportSolverHealth(solverURL string) error { + parsedURL, err := url.Parse(solverURL) + if err != nil { + println("Error parsing Solver URL: ", solverURL, ", ", err) + return err + } + + parsedURL.Path = "/health" + parsedURL.RawQuery = "" + parsedURL.Fragment = "" + + solverURL = parsedURL.String() + fmt.Println("Requesting solver health at ", solverURL) + + handler := New(solverURL) + + req, err := http.NewRequest(http.MethodGet, solverURL, nil) + if err != nil { + return err + } + + resp, err := handler.SolverClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + fmt.Println("Solver health response: ", resp.Status) + _, err = io.Copy(os.Stdout, resp.Body) + if err != nil { + return err + } + + return nil +} + +// sendToSolver sends the batch of UserOperations to the Solver. +func (ei *IntentsHandler) sendToSolver(body model.BodyOfUserOps) error { + jsonBody, err := json.Marshal(body) + if err != nil { + return err + } + + req, err := http.NewRequest(http.MethodPost, ei.SolverURL, bytes.NewBuffer(jsonBody)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := ei.SolverClient.Do(req) + if err != nil { + println("Solver request failed at URL: ", ei.SolverURL) + println("Solver error: ", err) + return err + } + defer resp.Body.Close() + + if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { + return err + } + + return nil +} diff --git a/pkg/userop/intent.go b/pkg/userop/intent.go new file mode 100644 index 00000000..68f89c43 --- /dev/null +++ b/pkg/userop/intent.go @@ -0,0 +1,33 @@ +package userop + +import ( + "github.com/blndgs/model" +) + +func (op *UserOperation) HasIntent() bool { + modelUserOp := model.UserOperation(*op) + + return modelUserOp.HasIntent() +} + +func (op *UserOperation) IsUnsolvedIntent() bool { + modelUserOp := model.UserOperation(*op) + + status, err := modelUserOp.Validate() + if err != nil || status != model.UnsolvedUserOp { + return false + } + + return true +} + +func (op *UserOperation) IsSolvedIntent() bool { + modelUserOp := model.UserOperation(*op) + + status, err := modelUserOp.Validate() + if err != nil || status != model.SolvedUserOp { + return false + } + + return true +} diff --git a/scripts/senduserop/main.go b/scripts/senduserop/main.go new file mode 100644 index 00000000..7f81d777 --- /dev/null +++ b/scripts/senduserop/main.go @@ -0,0 +1,295 @@ +// Scripted test wallet functionality for submitting Intent userOps to the +// bundler. +// Sends semi-mocked userOps to the bundler with live nonce, +// chainID values. Support submitting userOps 0 gas for testing. +// UserOp signature verification is included. +package main + +import ( + "bytes" + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "net/http" + "os" + + "github.com/blndgs/model" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/goccy/go-json" + "github.com/pkg/errors" + "github.com/spf13/viper" + + "github.com/stackup-wallet/stackup-bundler/pkg/signer" + "github.com/stackup-wallet/stackup-bundler/pkg/userop" +) + +type JsonRpcRequest struct { + Jsonrpc string `json:"jsonrpc"` + Id int `json:"id"` + Method string `json:"method"` + Params []interface{} `json:"params"` +} + +const entrypointAddrV060 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" + +func main() { + nodeURL, eoaSigner := readConf() + + sender := common.HexToAddress("0x6B5f6558CB8B3C8Fec2DA0B1edA9b9d5C064ca47") + + nonce, chainID, err := getNodeIDs(nodeURL, eoaSigner.Address) + if err != nil { + panic(err) + } + + zeroGas := (len(os.Args) > 1 && (os.Args[1] == "zero" || os.Args[1] == "0")) || len(os.Args) == 1 // default choice + unsignedUserOp := getMockUserOp(sender, nonce, zeroGas) + + userOp := getVerifiedSignedUserOp(unsignedUserOp, eoaSigner.PrivateKey, eoaSigner.PublicKey, chainID) + + fmt.Printf("%s\n", string(userOp.InitCode)) + fmt.Printf("%x\n", userOp.Signature) + sendUserOp(userOp, chainID) +} + +// sendUserOp makes a UserOperation RPC request to the bundler. +func sendUserOp(userOp *userop.UserOperation, chainID *big.Int) { + userOpHash := userOp.GetUserOpHash(common.HexToAddress(entrypointAddrV060), chainID).String() + + println("userOp (", userOpHash, ") ------------> bundler") + op := model.UserOperation(*userOp) + println(op.String()) + println() + // marshal JSON of op + opJSON, err := op.MarshalJSON() + if err != nil { + panic(err) + } + println(string(opJSON)) + + request := JsonRpcRequest{ + Jsonrpc: "2.0", + Id: 45, + Method: "eth_sendUserOperation", + Params: []interface{}{userOp, entrypointAddrV060}, + } + + requestBytes, err := json.Marshal(request) + if err != nil { + panic(err) + } + resp, err := http.Post("http://localhost:4337", "application/json", bytes.NewBuffer(requestBytes)) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + // Decode the response + var result map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + panic(err) + } + + // Print the response + println("Response from server:", result) +} + +func getMockUserOp(sender common.Address, nonce *big.Int, zeroGas bool) *userop.UserOperation { + intentJSON := `{"sender":"0x0A7199a96fdf0252E09F76545c1eF2be3692F46b","kind":"swap","hash":"","sellToken":"TokenA","buyToken":"TokenB","sellAmount":10,"buyAmount":5,"partiallyFillable":false,"status":"Received","createdAt":0,"expirationAt":0}` + println("intentJSON:", intentJSON) + // Conditional gas values based on zeroGas flag + var callGasLimit, verificationGasLimit, preVerificationGas, maxFeePerGas, maxPriorityFeePerGas *big.Int + if zeroGas { + callGasLimit = big.NewInt(0) + verificationGasLimit = big.NewInt(0) + preVerificationGas = big.NewInt(0) + maxFeePerGas = big.NewInt(0) + maxPriorityFeePerGas = big.NewInt(0) + } else { + callGasLimit = big.NewInt(0x2f44) // error if below 12100 + verificationGasLimit = big.NewInt(0xe4e0) + preVerificationGas = big.NewInt(0xbb7c) + maxFeePerGas = big.NewInt(0x12183576da) + maxPriorityFeePerGas = big.NewInt(0x12183576ba) + } + + return &userop.UserOperation{ + Sender: sender, + Nonce: nonce, + InitCode: []byte{}, + CallData: []byte(intentJSON), + CallGasLimit: callGasLimit, + VerificationGasLimit: verificationGasLimit, + PreVerificationGas: preVerificationGas, + MaxFeePerGas: maxFeePerGas, + MaxPriorityFeePerGas: maxPriorityFeePerGas, + PaymasterAndData: []byte{}, + } +} + +func readConf() (string, *signer.EOA) { + viper.SetConfigName(".env") + viper.SetConfigType("env") + viper.AddConfigPath(".") + if err := viper.ReadInConfig(); err != nil { + panic(fmt.Errorf("fatal error config file: %w", err)) + } + + nodeURL := viper.GetString("ERC4337_BUNDLER_ETH_CLIENT_URL") + prvKeyHex := viper.GetString("erc4337_bundler_private_key") + s, err := signer.New(prvKeyHex) + if err != nil { + panic(fmt.Errorf("fatal signer error: %w", err)) + } + + fmt.Printf("Private key: %s\n", hexutil.Encode(crypto.FromECDSA(s.PrivateKey))) + fmt.Printf("Public key: %s\n", hexutil.Encode(crypto.FromECDSAPub(s.PublicKey))[4:]) + fmt.Printf("Address: %s\n", s.Address) + return nodeURL, s +} + +// getVerifiedSignedUserOp returns a signed UserOperation with a signature that has been verified by the private key. +func getVerifiedSignedUserOp(userOp *userop.UserOperation, privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, chainID *big.Int) *userop.UserOperation { + userOp.Signature = getSignature(userOp, privateKey, chainID) + + // Verify the signature + if verifySignature(userOp, publicKey, chainID) { + println("Signature verified") + } else { + panic("Signature is invalid") + } + + return userOp +} + +func getSignature(userOp *userop.UserOperation, privateKey *ecdsa.PrivateKey, chainID *big.Int) []byte { + userOpHash := userOp.GetUserOpHash(common.HexToAddress(entrypointAddrV060), chainID).Bytes() + + prefixedHash := crypto.Keccak256Hash( + []byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(userOpHash), userOpHash)), + ) + + signature, err := crypto.Sign(prefixedHash.Bytes(), privateKey) + if err != nil { + panic(err) + } + + signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper + + // Normalize S value for Ethereum + // sValue := big.NewInt(0).SetBytes(signature[32:64]) + // secp256k1N := crypto.S256().Params().N + // if sValue.Cmp(new(big.Int).Rsh(secp256k1N, 1)) > 0 { + // sValue.Sub(secp256k1N, sValue) + // copy(signature[32:64], sValue.Bytes()) + // } + + return signature +} + +func verifySignature(userOp *userop.UserOperation, publicKey *ecdsa.PublicKey, chainID *big.Int) bool { + if len(userOp.Signature) != 65 { + panic(errors.New("signature must be 65 bytes long")) + } + if userOp.Signature[64] != 27 && userOp.Signature[64] != 28 { + panic(errors.New("invalid Ethereum signature (V is not 27 or 28)")) + } + + signature := bytes.Clone(userOp.Signature) // Not in RSV format + + signature[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + + userOpHash := userOp.GetUserOpHash(common.HexToAddress(entrypointAddrV060), chainID).Bytes() + + prefixedHash := crypto.Keccak256Hash( + []byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(userOpHash), userOpHash)), + ) + + recoveredPubKey, err := crypto.SigToPub(prefixedHash.Bytes(), signature) + if err != nil { + fmt.Printf("Failed to recover public key: %v\n", err) + return false + } + + recoveredAddress := crypto.PubkeyToAddress(*recoveredPubKey) + expectedAddress := crypto.PubkeyToAddress(*publicKey) + + return recoveredAddress == expectedAddress +} + +func getNodeIDs(nodeURL string, address common.Address) (nonce *big.Int, chainID *big.Int, err error) { + // Initialize a client instance to interact with the Ethereum network + client, err := ethclient.Dial(nodeURL) + if err != nil { + panic(fmt.Errorf("failed to connect to the Ethereum client: %w", err)) + } + defer client.Close() + + // Retrieve the next nonce to be used + nonceInt, err := client.PendingNonceAt(context.Background(), address) + if err != nil { + panic(fmt.Errorf("failed to retrieve the nonce: %w", err)) + } + fmt.Printf("Next nonce for address %s: %d\n", address.Hex(), nonceInt) + + nonce = big.NewInt(int64(nonceInt)) + + // Retrieve the chain ID + chainID, err = client.NetworkID(context.Background()) + if err != nil { + panic(fmt.Errorf("failed to retrieve the chain ID: %w", err)) + } + println("Chain ID:", chainID.String()) + + return nonce, chainID, nil +} + +// Uncomment when testing signature verifications +// testVerifyingSignature verifies that the private key generates a signature that can be verified by the public key. +// func testVerifyingSignature(privateKey *ecdsa.PrivateKey) { +// publicKeyECDSA, ok := privateKey.Public().(*ecdsa.PublicKey) +// if !ok { +// panic("error casting public key to ECDSA") +// } +// address := crypto.PubkeyToAddress(*publicKeyECDSA) +// +// // Sample message +// message := "Hello, Ethereum!" +// prefixedHash := crypto.Keccak256Hash([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(message), message))) +// +// // Sign the message +// signature, err := crypto.Sign(prefixedHash.Bytes(), privateKey) +// if err != nil { +// panic(err) +// } +// +// // Normalize S value +// sValue := big.NewInt(0).SetBytes(signature[32:64]) +// // Curve order for secp256k1 +// secp256k1N := crypto.S256().Params().N +// if sValue.Cmp(new(big.Int).Rsh(secp256k1N, 1)) > 0 { +// sValue.Sub(secp256k1N, sValue) +// copy(signature[32:64], sValue.Bytes()) +// } +// +// // Recover the public key without adjusting V +// recoveredPubKey, err := crypto.SigToPub(prefixedHash.Bytes(), signature) +// if err != nil { +// panic(err) +// } +// recoveredAddress := crypto.PubkeyToAddress(*recoveredPubKey) +// +// fmt.Printf("Original Address: %s\n", address.Hex()) +// fmt.Printf("Recovered Address: %s\n", recoveredAddress.Hex()) +// +// // Check if the recovered address matches the original address +// if address.Hex() == recoveredAddress.Hex() { +// println("Signature valid, recovered address matches the original address") +// } else { +// panic("Invalid signature, recovered address does not match") +// } +// }