From a4d936c0f01dd3810690af5a7d4670456d8700ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Augusto?= Date: Fri, 4 Jul 2025 12:34:52 +0100 Subject: [PATCH] feat: add extraction of transaction inputs beyond tx.value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * changed Mayan Finance cctx construction with intermediary token values * added Mayan Finance post-processing to use tx input data * changed logic to extract tokens being sent in Solana in DEXes Signed-off-by: André Augusto --- .../workflows/{tests.yml => across-tests.yml} | 5 +- .github/workflows/ccip-tests.yml | 49 +++ .github/workflows/cctp-tests.yml | 49 +++ .github/workflows/debridge-tests.yml | 49 +++ .github/workflows/mayan-tests.yml | 49 +++ .github/workflows/omnibridge-tests.yml | 49 +++ .github/workflows/polygon-tests.yml | 49 +++ .github/workflows/ronin-tests.yml | 49 +++ .vscode/launch.json | 11 + cli/cli.py | 4 + config/rpcs_base_config.yaml | 5 +- docker-compose.yaml | 2 +- extractor/base_handler.py | 63 ++- extractor/ccip/handler.py | 2 +- extractor/db_out.txt | 35 ++ extractor/evm_extractor.py | 21 +- extractor/extractor.py | 24 ++ extractor/mayan/constants.py | 30 +- extractor/mayan/handler.py | 377 ++++++++++++++++-- extractor/solana_extractor.py | 17 +- generator/common/price_generator.py | 12 +- generator/mayan/generator.py | 264 ++++++++++-- repository/base.py | 8 + repository/common/models.py | 3 + repository/mayan/models.py | 167 +++++--- repository/mayan/repository.py | 18 + rpcs/alchemy_client.py | 6 + rpcs/evm_rpc_client.py | 1 + rpcs/rpc_client.py | 12 +- tests/mayan/test_data_extraction.py | 4 +- tests/mayan/test_resolve_swaps.py | 313 +++++++++++++++ tests/polygon/test_data_extraction.py | 6 +- 32 files changed, 1548 insertions(+), 205 deletions(-) rename .github/workflows/{tests.yml => across-tests.yml} (89%) create mode 100644 .github/workflows/ccip-tests.yml create mode 100644 .github/workflows/cctp-tests.yml create mode 100644 .github/workflows/debridge-tests.yml create mode 100644 .github/workflows/mayan-tests.yml create mode 100644 .github/workflows/omnibridge-tests.yml create mode 100644 .github/workflows/polygon-tests.yml create mode 100644 .github/workflows/ronin-tests.yml create mode 100644 extractor/db_out.txt create mode 100644 tests/mayan/test_resolve_swaps.py diff --git a/.github/workflows/tests.yml b/.github/workflows/across-tests.yml similarity index 89% rename from .github/workflows/tests.yml rename to .github/workflows/across-tests.yml index 4bc0e62..2f57920 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/across-tests.yml @@ -1,4 +1,4 @@ -name: Python Tests +name: Python Tests Across on: pull_request: @@ -46,5 +46,4 @@ jobs: - name: Run tests with pytest run: | - python -m pytest tests/** -vvv -s --tb=short --maxfail=1 - continue-on-error: true + python -m pytest tests/across/** -vvv -s --tb=short --maxfail=1 diff --git a/.github/workflows/ccip-tests.yml b/.github/workflows/ccip-tests.yml new file mode 100644 index 0000000..dda7969 --- /dev/null +++ b/.github/workflows/ccip-tests.yml @@ -0,0 +1,49 @@ +name: Python Tests CCIP + +on: + pull_request: + branches: + - main + paths: + - "**.py" + - "tests/**" + +jobs: + test: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:14 + env: + POSTGRES_USER: testuser + POSTGRES_PASSWORD: testpass + POSTGRES_DB: testdb + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U testuser" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + env: + DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests with pytest + run: | + python -m pytest tests/ccip/** -vvv -s --tb=short --maxfail=1 diff --git a/.github/workflows/cctp-tests.yml b/.github/workflows/cctp-tests.yml new file mode 100644 index 0000000..c944dc8 --- /dev/null +++ b/.github/workflows/cctp-tests.yml @@ -0,0 +1,49 @@ +name: Python Tests CCTP + +on: + pull_request: + branches: + - main + paths: + - "**.py" + - "tests/**" + +jobs: + test: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:14 + env: + POSTGRES_USER: testuser + POSTGRES_PASSWORD: testpass + POSTGRES_DB: testdb + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U testuser" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + env: + DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests with pytest + run: | + python -m pytest tests/cctp/** -vvv -s --tb=short --maxfail=1 diff --git a/.github/workflows/debridge-tests.yml b/.github/workflows/debridge-tests.yml new file mode 100644 index 0000000..79e3c2a --- /dev/null +++ b/.github/workflows/debridge-tests.yml @@ -0,0 +1,49 @@ +name: Python Tests deBridge + +on: + pull_request: + branches: + - main + paths: + - "**.py" + - "tests/**" + +jobs: + test: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:14 + env: + POSTGRES_USER: testuser + POSTGRES_PASSWORD: testpass + POSTGRES_DB: testdb + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U testuser" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + env: + DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests with pytest + run: | + python -m pytest tests/debridge/** -vvv -s --tb=short --maxfail=1 diff --git a/.github/workflows/mayan-tests.yml b/.github/workflows/mayan-tests.yml new file mode 100644 index 0000000..77cfcf5 --- /dev/null +++ b/.github/workflows/mayan-tests.yml @@ -0,0 +1,49 @@ +name: Python Tests Mayan + +on: + pull_request: + branches: + - main + paths: + - "**.py" + - "tests/**" + +jobs: + test: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:14 + env: + POSTGRES_USER: testuser + POSTGRES_PASSWORD: testpass + POSTGRES_DB: testdb + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U testuser" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + env: + DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests with pytest + run: | + python -m pytest tests/mayan/** -vvv -s --tb=short --maxfail=1 diff --git a/.github/workflows/omnibridge-tests.yml b/.github/workflows/omnibridge-tests.yml new file mode 100644 index 0000000..5128a3e --- /dev/null +++ b/.github/workflows/omnibridge-tests.yml @@ -0,0 +1,49 @@ +name: Python Tests Omnibridge + +on: + pull_request: + branches: + - main + paths: + - "**.py" + - "tests/**" + +jobs: + test: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:14 + env: + POSTGRES_USER: testuser + POSTGRES_PASSWORD: testpass + POSTGRES_DB: testdb + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U testuser" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + env: + DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests with pytest + run: | + python -m pytest tests/omnibridge/** -vvv -s --tb=short --maxfail=1 diff --git a/.github/workflows/polygon-tests.yml b/.github/workflows/polygon-tests.yml new file mode 100644 index 0000000..6d6ab4a --- /dev/null +++ b/.github/workflows/polygon-tests.yml @@ -0,0 +1,49 @@ +name: Python Tests Polygon + +on: + pull_request: + branches: + - main + paths: + - "**.py" + - "tests/**" + +jobs: + test: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:14 + env: + POSTGRES_USER: testuser + POSTGRES_PASSWORD: testpass + POSTGRES_DB: testdb + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U testuser" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + env: + DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests with pytest + run: | + python -m pytest tests/polygon/** -vvv -s --tb=short --maxfail=1 diff --git a/.github/workflows/ronin-tests.yml b/.github/workflows/ronin-tests.yml new file mode 100644 index 0000000..aa1b9cf --- /dev/null +++ b/.github/workflows/ronin-tests.yml @@ -0,0 +1,49 @@ +name: Python Tests Ronin + +on: + pull_request: + branches: + - main + paths: + - "**.py" + - "tests/**" + +jobs: + test: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:14 + env: + POSTGRES_USER: testuser + POSTGRES_PASSWORD: testpass + POSTGRES_DB: testdb + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U testuser" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + env: + DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests with pytest + run: | + python -m pytest tests/ronin/** -vvv -s --tb=short --maxfail=1 diff --git a/.vscode/launch.json b/.vscode/launch.json index 9dd0b63..b208aff 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -218,5 +218,16 @@ "python": "${workspaceFolder}/.xchaindata/bin/python", "env": { "PYTHONPATH": "${workspaceFolder}", "DATABASE_URL": "postgresql://admin:pwd@localhost/mayan" } }, + { + "name": "[Mayan Swift] generate cross-chain transactions", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/__init__.py", + "args": ["generate", "--bridge", "mayan"], + "console": "integratedTerminal", + "justMyCode": true, + "python": "${workspaceFolder}/.xchaindata/bin/python", + "env": { "PYTHONPATH": "${workspaceFolder}", "DATABASE_URL": "postgresql://admin:pwd@localhost/mayan" } + }, ] } diff --git a/cli/cli.py b/cli/cli.py index bf4d9bd..7d1d12e 100644 --- a/cli/cli.py +++ b/cli/cli.py @@ -80,6 +80,10 @@ def extract_evm_data(idx, bridge, blockchain, start_block, end_block, blockchain ) ) extractor = EvmExtractor(bridge, blockchain, blockchains) + + if idx == len(blockchains) - 1: + extractor.post_processing() + except Exception as e: log_to_cli( build_log_message_2( diff --git a/config/rpcs_base_config.yaml b/config/rpcs_base_config.yaml index ba5f8af..3b84928 100644 --- a/config/rpcs_base_config.yaml +++ b/config/rpcs_base_config.yaml @@ -101,6 +101,9 @@ blockchains: - "https://mainnet.base.org" - "https://base-mainnet.public.blastapi.io" - "https://base-rpc.publicnode.com" + - "https://base.drpc.org" + - "https://base.api.onfinality.io/public" + - "https://base.lava.build" - name: bnb contract: "0xBA5Fe23f8a3a24BEd3236F05F2FcF35fd0BF0B5C" @@ -145,13 +148,11 @@ blockchains: end_block: "0xAE9A3E" rpcs: - "https://scroll.drpc.org" - - "https://1rpc.io/scroll" - "https://scroll-rpc.publicnode.com" - "https://rpc.scroll.io" - "https://scroll-mainnet.public.blastapi.io" - "https://scroll-mainnet-public.unifra.io" - "https://endpoints.omniatech.io/v1/scroll/mainnet/public" - - "https://scroll-mainnet.chainstacklabs.com" - "https://rpc.ankr.com/scroll" - name: linea diff --git a/docker-compose.yaml b/docker-compose.yaml index 1ce7860..fef4d46 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -38,7 +38,7 @@ services: - default_nw solana-decoder: - image: aaugusto11/solana-tx-decoder:efdee2b + image: aaugusto11/solana-tx-decoder:cb04734 container_name: solana_decoder ports: - "3000:3000" diff --git a/extractor/base_handler.py b/extractor/base_handler.py index 4db46e4..8054eb6 100644 --- a/extractor/base_handler.py +++ b/extractor/base_handler.py @@ -62,48 +62,66 @@ def bind_db_to_repos(self) -> None: """ pass - @abstractmethod def handle_transactions(self, transactions: List[Dict[str, Any]]) -> None: - pass + func_name = "handle_transactions" + try: + self.blockchain_transaction_repo.create_all(transactions) + except Exception as e: + raise CustomException( + self.CLASS_NAME, + func_name, + f"Error writing transactions to database: {e}", + ) from e + + def handle_transaction(self, transaction: Dict[str, Any]) -> None: + func_name = "handle_transaction" + try: + self.blockchain_transaction_repo.create(transaction) + except Exception as e: + raise CustomException( + self.CLASS_NAME, + func_name, + f"Error writing transaction to database: {e}", + ) from e def create_transaction_object( - self, blockchain: str, tx_receipt: Dict[str, Any], timestamp: int + self, blockchain: str, tx: Dict[str, Any], timestamp: int ) -> None: func_name = "create_transaction_object" try: if blockchain == "solana": return { "blockchain": blockchain, - "transaction_hash": tx_receipt["transaction"]["signatures"][0], - "block_number": tx_receipt["slot"], + "transaction_hash": tx["transaction"]["signatures"][0], + "block_number": tx["slot"], "timestamp": timestamp, "from_address": None, "to_address": None, - "status": 1 if tx_receipt["meta"]["err"] is not None else 0, + "status": 1 if tx["meta"]["err"] is not None else 0, + "input_data": None, "value": None, - "fee": tx_receipt["meta"]["fee"], + "fee": tx["meta"]["fee"], } else: return { "blockchain": blockchain, - "transaction_hash": tx_receipt["transactionHash"], - "block_number": int(tx_receipt["blockNumber"], 0), + "transaction_hash": tx["transactionHash"], + "block_number": int(tx["blockNumber"], 0), "timestamp": int(timestamp, 16), - "from_address": tx_receipt["from"], - "to_address": tx_receipt["to"], - "status": int(tx_receipt["status"], 16), - "value": int(tx_receipt["value"], 16) if "value" in tx_receipt else None, - "fee": str( - int(tx_receipt["gasUsed"], 0) * int(tx_receipt["effectiveGasPrice"], 0) - ), + "from_address": tx["from"], + "to_address": tx["to"], + "status": int(tx["status"], 16), + "value": int(tx["value"], 16) if "value" in tx else None, + "input_data": tx["input"] if "input" in tx else None, + "fee": str(int(tx["gasUsed"], 0) * int(tx["effectiveGasPrice"], 0)), } except Exception as e: raise CustomException( self.CLASS_NAME, func_name, - f"Tx Hash: {tx_receipt['transactionHash']}: {e}" - if "transactionHash" in tx_receipt - else f"Tx Signature: {tx_receipt['signature']}: {e}", + f"Tx Hash: {tx['transactionHash']}: {e}" + if "transactionHash" in tx + else f"Tx Signature: {tx['signature']}: {e}", ) from e @abstractmethod @@ -142,3 +160,10 @@ def convert_id_to_blockchain_name(self, id: str, blockchain_ids=BLOCKCHAIN_IDS) CustomException(self.CLASS_NAME, func_name, f"Blockchain with ID {id} not included") # log_to_file(e, "data/out_of_scope_blockchains.log") return None + + def post_processing(self) -> None: + """ + Placeholder for post-processing tasks. + This method can be overridden in subclasses to implement specific post-processing logic. + """ + return None diff --git a/extractor/ccip/handler.py b/extractor/ccip/handler.py index b8db534..4ff7095 100644 --- a/extractor/ccip/handler.py +++ b/extractor/ccip/handler.py @@ -79,7 +79,7 @@ def handle_events( if ( event["topic"] == "0xd0c3c799bf9e2639de44391e7f524d229b2b55f5b1ea94b2bf7da42f7243dddd" - ): # DepositForBurn + ): # CCIPSendRequested event = self.handle_send_requested(blockchain, event) elif ( event["topic"] diff --git a/extractor/db_out.txt b/extractor/db_out.txt new file mode 100644 index 0000000..d7d704f --- /dev/null +++ b/extractor/db_out.txt @@ -0,0 +1,35 @@ +3EVcb5GmSMhhkSoiz5UBB5vxHccyoQbih16MFNqBWqWjq2ad3zDxP1WEKNbafARHTwfuLvDYdHKoLrZLmSyonxKD +4G5TabyaJiV4hPir6z8m6fHvLRfQzcJE6rxJ8dYREp7QHE6uJPJjHXeeBfw4HfbnJ6yiEm2r6pFsE9QLp1qz3xgL +5v37nnYw4bCAcMPNsTcoSJSeDGub1XpmDBRvRAo1qzwE5pRBJ8jCtGScUj8SrDb7o7Yp3bXwzPNgvBYTxuXK1xDt +mNpWFAEPg2P4atsTGxJD6UaodeX2zZuKEJCHJP34vyJ6LM9iZgwny3ZZDd1dNADhjeCsLJx2YkAHYjq1Y54B1mH +2LvKzyM9USny8VWY6raZ1ncHpoBV8nLkxTHtMtdvBPP5vQCRVr1pJp36g7mJZwrT6xCNfvDz4qddz9dYtKmjEKi7 +36aLQeoivCdyHGq7nAZuYb56rNKuzsRx2JDosQPmQoMbghod4GvVmwSBgij6s8GZPsSe6FifpFTvEhqHjp3QPwsu +5NUYVL1RTaBa6KFsriw1TpW9Ys4LQ9swTKttNosH5Pax9amSnxTCY1YwpfmfR2gRKzrhuBcAg7pmmTmTK72ozUQV +3MNxfAcGhxqf1stte3HoYgWV7V81hAx9v9ttwTNdiVrb73B3VqRFewuD9auaBtT8R7FU6tMgcirRhSeJG3WDc2aM +4Q48aiZz9Eba22C77MbCkYg7g725itmM6b33nojS3GtGecq2mbhxAFmbxcnALJTA7TRUUHK7Thz5ZrvBky8rYsYk +5JcgqyeN4hZnPBBomPehUBHiY5XZWitqUeGiawLhoTUXeiWco7gfH2FzKyr6StJ95UEmfvDFMUGL6qYiEqXqqTCU +5wV6dAJpYzbTj2xxDbTZVyCmeKXqacXjiGs8wvwm54xoY5dA7TA4v1z3NWXD3e97Sve4Eboqt16JHLehtJqPvXBj +tKmdiVn6YuPKyoQzJsMCzqR9y9AQ3TNPN88wF3jnhqHzf2yHDBJhKDmF771gmCVDLvhDRLXzXVw2Yog3jYZvoVM +4GRQ75kDN8FKhjHWXkVMcWoWKKC77Fh6m7Ey8swA2GSfvyBoY76Bouq6orFfw3RJTFPj47mkB8cBX2fpoUv5BmPs +D9DdkBqytr84SL3a8V5VT2DWj4kiW9rimhkSryAA1SqL36ZMRiJEC6QNB5yjLUgkm41BYArRhFXf9bZABxHbGnB +3cLiY1khXg2EcdC4GZBUT4CXVcTQLxc5W5DbbZDZ4izVvGkdVFhS2thN6cBomNfkXS533cZA56Jay4x6AK3d4jrC +5SZuQhsiFAk1DBtCLAuDEhEuJ6nfmRHM1VDprpJsQ6ikEPrKzrqG1so7LQYruZ3BuJi3ysUJQipqykK3AzmZ8unn +kEbVGAMBUgCcFME98hw7dxZF37NdGvV1wtoTKUWya8zEpz8gqWNaNddMCvQbdS7uWwwc5w6fZLo9vBFb336AZA1 +3EVcb5GmSMhhkSoiz5UBB5vxHccyoQbih16MFNqBWqWjq2ad3zDxP1WEKNbafARHTwfuLvDYdHKoLrZLmSyonxKD +4G5TabyaJiV4hPir6z8m6fHvLRfQzcJE6rxJ8dYREp7QHE6uJPJjHXeeBfw4HfbnJ6yiEm2r6pFsE9QLp1qz3xgL +3WcZopoVcNdVS76jXUfbd9hTwy1JmPiid5tJ5rwTYAzSkkMRxsGKf8UF97a91cYrjr4ChVgkmWmopxHPFqj54y1h +2LvKzyM9USny8VWY6raZ1ncHpoBV8nLkxTHtMtdvBPP5vQCRVr1pJp36g7mJZwrT6xCNfvDz4qddz9dYtKmjEKi7 +5v37nnYw4bCAcMPNsTcoSJSeDGub1XpmDBRvRAo1qzwE5pRBJ8jCtGScUj8SrDb7o7Yp3bXwzPNgvBYTxuXK1xDt +mNpWFAEPg2P4atsTGxJD6UaodeX2zZuKEJCHJP34vyJ6LM9iZgwny3ZZDd1dNADhjeCsLJx2YkAHYjq1Y54B1mH +36aLQeoivCdyHGq7nAZuYb56rNKuzsRx2JDosQPmQoMbghod4GvVmwSBgij6s8GZPsSe6FifpFTvEhqHjp3QPwsu +4GRQ75kDN8FKhjHWXkVMcWoWKKC77Fh6m7Ey8swA2GSfvyBoY76Bouq6orFfw3RJTFPj47mkB8cBX2fpoUv5BmPs +D9DdkBqytr84SL3a8V5VT2DWj4kiW9rimhkSryAA1SqL36ZMRiJEC6QNB5yjLUgkm41BYArRhFXf9bZABxHbGnB +tKmdiVn6YuPKyoQzJsMCzqR9y9AQ3TNPN88wF3jnhqHzf2yHDBJhKDmF771gmCVDLvhDRLXzXVw2Yog3jYZvoVM +5NUYVL1RTaBa6KFsriw1TpW9Ys4LQ9swTKttNosH5Pax9amSnxTCY1YwpfmfR2gRKzrhuBcAg7pmmTmTK72ozUQV +3MNxfAcGhxqf1stte3HoYgWV7V81hAx9v9ttwTNdiVrb73B3VqRFewuD9auaBtT8R7FU6tMgcirRhSeJG3WDc2aM +4Q48aiZz9Eba22C77MbCkYg7g725itmM6b33nojS3GtGecq2mbhxAFmbxcnALJTA7TRUUHK7Thz5ZrvBky8rYsYk +5JcgqyeN4hZnPBBomPehUBHiY5XZWitqUeGiawLhoTUXeiWco7gfH2FzKyr6StJ95UEmfvDFMUGL6qYiEqXqqTCU +5wV6dAJpYzbTj2xxDbTZVyCmeKXqacXjiGs8wvwm54xoY5dA7TA4v1z3NWXD3e97Sve4Eboqt16JHLehtJqPvXBj +3cLiY1khXg2EcdC4GZBUT4CXVcTQLxc5W5DbbZDZ4izVvGkdVFhS2thN6cBomNfkXS533cZA56Jay4x6AK3d4jrC +5SZuQhsiFAk1DBtCLAuDEhEuJ6nfmRHM1VDprpJsQ6ikEPrKzrqG1so7LQYruZ3BuJi3ysUJQipqykK3AzmZ8unn +kEbVGAMBUgCcFME98hw7dxZF37NdGvV1wtoTKUWya8zEpz8gqWNaNddMCvQbdS7uWwwc5w6fZLo9vBFb336AZA1 \ No newline at end of file diff --git a/extractor/evm_extractor.py b/extractor/evm_extractor.py index baa12b3..64d8efd 100644 --- a/extractor/evm_extractor.py +++ b/extractor/evm_extractor.py @@ -97,15 +97,15 @@ def work( if self.handler.does_transaction_exist_by_hash(tx_hash): continue - tx_receipt, block = self.rpc_client.process_transaction( + tx, block = self.rpc_client.process_transaction( self.blockchain, log["transaction_hash"], log["block_number"] ) - if tx_receipt is None or block is None: + if tx is None or block is None: raise Exception(tx_hash) txs[tx_hash] = self.handler.create_transaction_object( - self.blockchain, tx_receipt, block["timestamp"] + self.blockchain, tx, block["timestamp"] ) except CustomException as e: @@ -116,7 +116,20 @@ def work( log_error(self.bridge, request_desc) if len(txs) > 0: - self.handler.handle_transactions(txs.values()) + try: + self.handler.handle_transactions(txs.values()) + except CustomException: + # if there is an error while handling transactions in batch, we handle them one + # by one to avoid the entire batch failing + for tx in txs.values(): + try: + self.handler.handle_transaction(tx) + except CustomException as e: + request_desc = ( + f"Error processing transaction: {self.blockchain}, " + f"{tx['transaction_hash']}. Error: {e}" + ) + log_error(self.bridge, request_desc) def extract_data(self, start_block: int, end_block: int): """Main extraction logic.""" diff --git a/extractor/extractor.py b/extractor/extractor.py index f99e021..72623c3 100644 --- a/extractor/extractor.py +++ b/extractor/extractor.py @@ -1,11 +1,15 @@ +import time from abc import ABC, abstractmethod from queue import Queue from urllib.request import BaseHandler from config.constants import Bridge from utils.utils import ( + CliColor, CustomException, + build_log_message, load_module, + log_to_cli, ) @@ -97,3 +101,23 @@ def work(self, start_block: int, end_block: int): def extract_data(self, start_block: int, end_block: int): """Main extraction logic.""" pass + + def post_processing(self): + """Post-processing logic after extraction.""" + start_time = time.time() + + self.handler.post_processing() + + end_time = time.time() + + log_to_cli( + build_log_message( + None, + None, + None, + self.bridge, + self.blockchain, + f"Token prices fetched in {end_time - start_time} seconds.", + ), + CliColor.SUCCESS, + ) diff --git a/extractor/mayan/constants.py b/extractor/mayan/constants.py index 64a038f..e4773ea 100644 --- a/extractor/mayan/constants.py +++ b/extractor/mayan/constants.py @@ -300,6 +300,8 @@ } } +MAIN_BRIDGE_CONTRACT = "0xc38e4e6a15593f908255214653d3d947ca1c2338" # Mayan Swift + BLOCKCHAIN_IDS = { "30": { @@ -340,34 +342,6 @@ }, } - -WETH_CONTRACT_ADDRESSES = { - "base": { - "contract_address": "0x4200000000000000000000000000000000000006", - }, - "optimism": { - "contract_address": "0x4200000000000000000000000000000000000006", - }, - "arbitrum": { - "contract_address": "0x82af49447d8a07e3bd95bd0d56f35241523fbab1", - }, - "polygon": { - "contract_address": "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619", - }, - "ethereum": { - "contract_address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - }, - "linea": { - "contract_address": "0xe5d7c2a44ffddf6b295a15c148167daaaf5cf34f", - }, - "bnb": { - "contract_address": "0x4DB5a66E937A9F4473fA95b1cAF1d1E1D62E29EA", - }, - "avalanche": { - "contract_address": "0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB", - }, -} - SOLANA_PROGRAM_ADDRESSES = [ "BLZRi6frs4X4DNLw56V4EXai1b6QVESN1BhHBTYM9VcY", # Mayan Swift "9w1D9okTM8xNE7Ntb7LpaAaoLc6LfU9nHFs2h2KTpX1H", # Auction diff --git a/extractor/mayan/handler.py b/extractor/mayan/handler.py index 3ead7c0..4987e0f 100644 --- a/extractor/mayan/handler.py +++ b/extractor/mayan/handler.py @@ -9,6 +9,7 @@ ) from extractor.mayan.utils.OrderHash import reconstruct_order_hash_from_params from repository.database import DBSession +from repository.mayan.models import MayanBlockchainTransaction, MayanOrderFulfilled from repository.mayan.repository import ( MayanAuctionBidRepository, MayanAuctionCloseRepository, @@ -27,10 +28,14 @@ ) from rpcs.evm_rpc_client import EvmRPCClient from utils.utils import ( + CliColor, CustomException, + build_log_message_generator, convert_32_byte_array_to_evm_address, convert_32_byte_array_to_solana_address, log_error, + log_to_cli, + unpad_address, ) @@ -70,17 +75,6 @@ def bind_db_to_repos(self): self.auction_bid_repo = MayanAuctionBidRepository(DBSession) self.auction_close_repo = MayanAuctionCloseRepository(DBSession) - def handle_transactions(self, transactions: List[Dict[str, Any]]) -> None: - func_name = "handle_transactions" - try: - self.blockchain_transaction_repo.create_all(transactions) - except Exception as e: - raise CustomException( - self.CLASS_NAME, - func_name, - f"Error writing transactions to database: {e}", - ) from e - def does_transaction_exist_by_hash(self, transaction_hash: str) -> Any: func_name = "does_transaction_exist_by_hash" """ @@ -164,7 +158,7 @@ def handle_events( def handle_swap_and_forwarded_eth(self, blockchain, event): func_name = "handle_swap_and_forwarded_eth" - event["tokenIn"] = self.populate_weth_token() + event["tokenIn"] = self.populate_native_token() try: return self.handle_swap_and_forwarded(blockchain, event) @@ -257,7 +251,7 @@ def handle_swap_and_forwarded(self, blockchain, event): def handle_forwarded_eth(self, blockchain, event): func_name = "handle_forwarded_eth" - event["token"] = self.populate_weth_token() + event["token"] = self.populate_native_token() try: return self.handle_forwarded(blockchain, event) @@ -380,6 +374,8 @@ def handle_order_fulfilled(self, blockchain, event): "key": event["key"], "sequence": event["sequence"], "net_amount": event["netAmount"], + "middle_dst_token": None, + "middle_dst_amount": None, } ) return event @@ -412,7 +408,7 @@ def handle_order_unlocked(self, blockchain, event): f"{blockchain} -- Tx Hash: {event['transaction_hash']}. Error writing to DB: {e}", ) from e - def populate_weth_token(self) -> str: + def populate_native_token(self) -> str: return "0x0000000000000000000000000000000000000000" ### LOGIC FOR SOLANA ### @@ -449,6 +445,13 @@ def handle_solana_events( if instruction["name"] == "initOrder": transfer_instruction = None + swap_instructions = [ + instr + for instr in transaction_instructions + if instr["name"] == "SwapEvent" + ] + + swap_instruction = MayanHandler.resolve_swaps(signature, swap_instructions) if transaction_instructions[idx - 1]["name"] == "transfer": transfer_instruction = transaction_instructions[idx - 1] @@ -456,7 +459,7 @@ def handle_solana_events( transfer_instruction = transaction_instructions[idx - 2] included = self.handle_init_order( - signature, transfer_instruction, instruction + signature, transfer_instruction, instruction, swap_instruction ) elif instruction["name"] == "unlockBatch": included = self.handle_unlock( @@ -471,12 +474,23 @@ def handle_solana_events( instruction, ) elif instruction["name"] == "fulfill": + transfer_instruction = None + swap_instructions = [ + instr + for instr in transaction_instructions + if instr["name"] == "SwapEvent" + ] + + swap_instruction = MayanHandler.resolve_swaps(signature, swap_instructions) + if transaction_instructions[idx - 2]["name"] == "transferChecked": transfer_instruction = transaction_instructions[idx - 2] elif transaction_instructions[idx - 1]["name"] == "transfer": transfer_instruction = transaction_instructions[idx - 1] - included = self.handle_fulfill(signature, transfer_instruction, instruction) + included = self.handle_fulfill( + signature, transfer_instruction, instruction, swap_instruction + ) elif instruction["name"] == "settle": included = self.handle_settle( signature, @@ -533,7 +547,11 @@ def extract_accounts_from_instruction( return accounts def handle_init_order( - self, signature: str, transfer_instruction: Dict[str, Any], instruction: Dict[str, Any] + self, + signature: str, + transfer_instruction: Dict[str, Any], + instruction: Dict[str, Any], + swap_event: Dict[str, Any], ): func_name = "handle_init_order" @@ -574,6 +592,21 @@ def handle_init_order( amount_in = int(transfer_instruction["args"]["amount"], 16) + if swap_event: + original_src_token = swap_event["args"]["input_mint"] + original_src_amount = int(swap_event["args"]["input_amount"], 16) + amm = None + + middle_src_token = swap_event["args"]["output_mint"] + middle_src_amount = int(swap_event["args"]["output_amount"], 16) + else: + original_src_token = account_data["mintFrom"] + original_src_amount = amount_in + amm = None + + middle_src_amount = None + middle_src_token = None + self.init_order_repo.create( { "order_hash": order_hash, @@ -583,12 +616,12 @@ def handle_init_order( "state": account_data["state"], "state_from_acc": account_data["stateFromAcc"], "relayer_fee_acc": account_data["relayerFeeAcc"], - "mint_from": account_data["mintFrom"], + "middle_src_token": middle_src_token, "fee_manager_program": account_data["feeManagerProgram"], "token_program": account_data["tokenProgram"], "system_program": account_data["systemProgram"], - "amount_in_min": int(params["amountInMin"], 16), - "amount_in": amount_in, + "middle_src_amount_min": int(params["amountInMin"], 16), + "middle_src_amount": middle_src_amount, "native_input": params["nativeInput"], "fee_submit": int(params["feeSubmit"], 16), "addr_dest": convert_32_byte_array_to_evm_address(params["addrDest"]), @@ -604,6 +637,9 @@ def handle_init_order( "fee_rate_mayan": params["feeRateMayan"], "auction_mode": params["auctionMode"], "key_rnd": bytes(params["keyRnd"]).hex(), + "original_src_token": original_src_token, + "original_src_amount": original_src_amount, + "amm": amm, } ) @@ -661,7 +697,11 @@ def handle_unlock( ) from e def handle_fulfill( - self, signature: str, transfer_instruction: Dict[str, Any], instruction: Dict[str, Any] + self, + signature: str, + transfer_instruction: Dict[str, Any], + instruction: Dict[str, Any], + swap_event: Dict[str, Any], ): func_name = "handle_fulfill" @@ -673,19 +713,32 @@ def handle_fulfill( if self.fulfill_repo.event_exists(signature): return None - # we need to extract the amount being sent to the order - # by fetching the transfer instruction before the initOrder instruction - if ( - transfer_instruction["name"] != "transfer" - and transfer_instruction["name"] != "transferChecked" - ): - raise CustomException( - self.CLASS_NAME, - func_name, - f"Expected transfer instruction, got {transfer_instruction['name']}", - ) + if swap_event: + middle_dst_token = swap_event["args"]["input_mint"] + middle_dst_amount = int(swap_event["args"]["input_amount"], 16) + amm = None - amount_in = int(transfer_instruction["args"]["amount"], 16) + final_amount = int(swap_event["args"]["output_amount"], 16) + else: + middle_dst_token = None + middle_dst_amount = None + amm = None + + # we need to extract the amount being sent to the order + # by fetching the transfer instruction before the initOrder instruction + if ( + transfer_instruction["name"] != "transfer" + and transfer_instruction["name"] != "transferChecked" + ): + raise CustomException( + self.CLASS_NAME, + func_name, + f"Expected transfer instruction, got {transfer_instruction['name']}", + ) + + amount_in = int(transfer_instruction["args"]["amount"], 16) + + final_amount = amount_in self.fulfill_repo.create( { @@ -697,7 +750,10 @@ def handle_fulfill( "dest": account_data["dest"], "system_program": account_data["systemProgram"], "addr_unlocker": addr_unlocker, - "amount": amount_in, + "amount": final_amount, + "middle_dst_token": middle_dst_token, + "middle_dst_amount": middle_dst_amount, + "amm": amm, } ) @@ -896,7 +952,7 @@ def handle_auction_bid(self, signature: str, instruction: Dict[str, Any]): params=params, ) - if self.auction_bid_repo.event_exists(order_hash): + if self.auction_bid_repo.event_exists(signature): return None self.auction_bid_repo.create( @@ -961,3 +1017,254 @@ def handle_auction_close(self, signature: str, instruction: Dict[str, Any]): func_name, f"{'solana'} -- Tx Hash: {signature}. Error writing to DB: {e}", ) from e + + def post_processing(self): + """ + Post-process fulfill transactions in EVM to extract middle token and amount from input data. + These are needed when swaps occur and are not emitted in the event. + """ + func_name = "post_processing" + + try: + # Get all fulfill orders + their input data in a single query + with self.order_fulfilled_repo.get_session() as session: + results = ( + session.query( + MayanOrderFulfilled.key, + MayanOrderFulfilled.transaction_hash, + MayanBlockchainTransaction.input_data, + ) + .join( + MayanBlockchainTransaction, + MayanBlockchainTransaction.transaction_hash + == MayanOrderFulfilled.transaction_hash, + ) + .all() + ) + + updates = [] + for key, tx_hash, input_data in results: + log_to_cli( + build_log_message_generator( + self.bridge, + ( + f"Post-processing fulfill order: {key} -- Tx Hash: {tx_hash} " + f" {len(updates) / len(results) * 100:.2f}% done...", + ), + ), + CliColor.INFO, + ) + + if not input_data: + continue + + try: + function_selector = input_data[:10] + + if function_selector == "0xbc127b88": # fulfillWithERC20 + token_in = unpad_address(input_data[10:74]) + amount_in = int(input_data[74:138], 16) + + updates.append((key, token_in, amount_in)) + + elif function_selector == "0x1c5cf072": # fulfillWithETH + token_in = self.populate_native_token() + amount_in = int(input_data[10:74], 16) + + updates.append((key, token_in, amount_in)) + + elif ( + function_selector == "0x488c3591" or function_selector == "0x6befa3a5" + ): # fulfillOrder or directFulfill + # we skip because these functions do not have middle token or amount + continue + + else: + err = CustomException( + self.CLASS_NAME, + func_name, + ( + f"{self.bridge} -- Tx Hash: {tx_hash}. Unknown function ", + f"selector: {function_selector}", + ), + ) + log_error(self.bridge, str(err)) + + except Exception as decode_err: + err = CustomException( + self.CLASS_NAME, + func_name, + ( + f"{self.bridge} -- Tx Hash: {tx_hash}. Error decoding input ", + f"data: {decode_err}", + ), + ) + log_error(self.bridge, str(err)) + continue + + # Batch update + for key, middle_token, middle_amount in updates: + self.order_fulfilled_repo.update_middle_info_order_fulfilled( + key, + middle_token, + middle_amount, + ) + + except Exception as e: + raise CustomException( + self.CLASS_NAME, + func_name, + f"{self.bridge} -- Error during post-processing: {e}", + ) from e + + @staticmethod + def resolve_swaps(signature, swap): + func_name = "resolve_swaps" + + if not swap or not isinstance(swap, list): + return None + + while True: + aggregated = MayanHandler.aggregate_swap_instructions(swap) + + resolved = MayanHandler.resolve_swap_chain(aggregated) + + if len(resolved) == 1: + # If there's only one swap, we can stop resolving + break + else: + swap = resolved + + if len(resolved) == 1: + item = resolved[0] + return { + "args": { + "input_mint": item["args"]["input_mint"], + "output_mint": item["args"]["output_mint"], + "input_amount": item["args"]["input_amount"], + "output_amount": item["args"]["output_amount"], + } + } + else: + err = CustomException( + MayanHandler.CLASS_NAME, + func_name, + f"Expected one aggregated swap, got {len(aggregated)} swaps in {signature}", + ) + log_error( + MayanHandler.CLASS_NAME, + str(err), + ) + raise err + + @staticmethod + def aggregate_swap_instructions(swap): + func_name = "aggregate_swap_instructions" + + if not swap or not isinstance(swap, list): + return None + + if len(swap) == 1: + # If there's only one swap, return it as is + return swap + + try: + # Step 2: aggregate same (input_mint, output_mint) swaps + aggregated = {} + for s in swap: + input_mint = s["args"]["input_mint"] + output_mint = s["args"]["output_mint"] + input_amount = int(s["args"]["input_amount"], 16) + output_amount = int(s["args"]["output_amount"], 16) + + key = (input_mint, output_mint) + if key not in aggregated: + aggregated[key] = {"input_amount": input_amount, "output_amount": output_amount} + else: + aggregated[key]["input_amount"] += input_amount + aggregated[key]["output_amount"] += output_amount + + # return a list of aggregated swaps + return [ + { + "args": { + "input_mint": key[0], + "output_mint": key[1], + "input_amount": hex(value["input_amount"]), + "output_amount": hex(value["output_amount"]), + } + } + for key, value in aggregated.items() + ] + + except Exception as e: + raise CustomException( + MayanHandler.CLASS_NAME, + func_name, + f"Error aggregating swap instructions: {e}", + ) from e + + @staticmethod + def resolve_swap_chain(swap): + func_name = "resolve_swap_chain" + try: + if not swap or not isinstance(swap, list): + return [] + + if len(swap) == 1: + # If there's only one swap, return it as is + return swap + + # Build map from (input_mint, input_amount) → swap + input_map = {} + for s in swap: + key = (s["args"]["input_mint"], int(s["args"]["input_amount"], 16)) + input_map[key] = s + + used = set() + chains = [] + + for s in swap: + if id(s) in used: + continue + + chain = [s] + used.add(id(s)) + current_swap = s + + while True: + output_mint = current_swap["args"]["output_mint"] + output_amount = int(current_swap["args"]["output_amount"], 16) + next_key = (output_mint, output_amount) + next_swap = input_map.get(next_key) + if next_swap and id(next_swap) not in used: + chain.append(next_swap) + used.add(id(next_swap)) + current_swap = next_swap + else: + break + + # Reduce this chain + input_mint = chain[0]["args"]["input_mint"] + output_mint = chain[-1]["args"]["output_mint"] + input_amount = int(chain[0]["args"]["input_amount"], 16) + output_amount = int(chain[-1]["args"]["output_amount"], 16) + + chains.append( + { + "args": { + "input_mint": input_mint, + "output_mint": output_mint, + "input_amount": hex(input_amount), + "output_amount": hex(output_amount), + } + } + ) + + return chains + except Exception as e: + raise CustomException( + MayanHandler.CLASS_NAME, + func_name, + f"Error resolving swap chain: {e}", + ) from e diff --git a/extractor/solana_extractor.py b/extractor/solana_extractor.py index 75bc808..77da819 100644 --- a/extractor/solana_extractor.py +++ b/extractor/solana_extractor.py @@ -59,9 +59,6 @@ def work( decoded_instructions = [] for signature in signatures: try: - if self.handler.does_transaction_exist_by_hash(signature): - continue - decoded_tx = self.rpc_client.parseTransactionByHash(signature) decoded_instructions.append(decoded_tx) @@ -80,6 +77,11 @@ def work( transactions = [] for decoded_tx in included_txs: + if self.handler.does_transaction_exist_by_hash( + decoded_tx["transaction"]["transaction"]["signatures"][0] + ): + continue + transactions.append( self.handler.create_transaction_object( self.blockchain, @@ -115,8 +117,13 @@ def extract_data(self, signature_ranges: dict): ) # if we already have signatures fetched, we can skip fetching them again - # with open("fetched_signatures.json", "r") as f: - # all_signatures = json.load(f) + # all_signatures = [] + # with open("extractor/db_out.txt", "r") as f: + # # each line is a tx_hash / signature + # for line in f: + # tx_hash = line.strip() + # if tx_hash: + # all_signatures.append(tx_hash) all_signatures = list(map(lambda x: x["signature"], all_signatures)) diff --git a/generator/common/price_generator.py b/generator/common/price_generator.py index 66586cf..3a3e1eb 100644 --- a/generator/common/price_generator.py +++ b/generator/common/price_generator.py @@ -319,6 +319,9 @@ def fetch_and_store_token_metadata( contract_address: str, ): try: + if blockchain == "solana": + return None # Alchemy does not support Solana + if self.has_tried_metadata_fetching_for_contract(blockchain, token_contract): return None @@ -363,6 +366,12 @@ def fetch_and_store_token_prices( blockchain: str = None, token_address: str = None, ): + if blockchain == "solana": + return None # Alchemy does not support Solana + + if symbol is None or name is None: + return + log_to_cli( build_log_message_generator( bridge, @@ -371,9 +380,6 @@ def fetch_and_store_token_prices( CliColor.INFO, ) - if symbol is None or name is None: - return - if "usd" in symbol.lower() or "dai" in symbol.lower() or "frax" in symbol.lower(): rows = [] current_ts = start_ts diff --git a/generator/mayan/generator.py b/generator/mayan/generator.py index 18d371a..8c80e2c 100644 --- a/generator/mayan/generator.py +++ b/generator/mayan/generator.py @@ -1,6 +1,6 @@ import time -from sqlalchemy import case, literal_column, update +from sqlalchemy import case, func, literal, literal_column, update from sqlalchemy.orm import aliased from config.constants import Bridge @@ -14,6 +14,7 @@ ) from repository.database import DBSession from repository.mayan.models import ( + MayanAuctionBid, MayanBlockchainTransaction, MayanCrossChainTransaction, MayanForwarded, @@ -114,6 +115,26 @@ def generate_cross_chain_data(self): "src_timestamp", "refund_amount_usd", ) + PriceGenerator.calculate_cctx_usd_values( + self.bridge, + self.cross_chain_transactions_repo, + "mayan_cross_chain_transactions", + "middle_src_amount", + "src_blockchain", + "middle_src_token", + "src_timestamp", + "middle_src_amount_usd", + ) + PriceGenerator.calculate_cctx_usd_values( + self.bridge, + self.cross_chain_transactions_repo, + "mayan_cross_chain_transactions", + "middle_dst_amount", + "dst_blockchain", + "middle_dst_token", + "dst_timestamp", + "middle_dst_amount_usd", + ) PriceGenerator.calculate_cctx_native_usd_values( self.bridge, self.cross_chain_transactions_repo, @@ -165,6 +186,8 @@ def match_sol_to_evm(self): try: results = [] + auction_data = self.get_auction_data() + SrcTx = aliased(MayanBlockchainTransaction) DstTx = aliased(MayanBlockchainTransaction) RefundTx = aliased(MayanBlockchainTransaction) @@ -172,61 +195,110 @@ def match_sol_to_evm(self): with self.cross_chain_transactions_repo.get_session() as session: results = ( session.query( - MayanInitOrder, - MayanOrderFulfilled, - MayanUnlock, - SrcTx, - DstTx, - RefundTx, + MayanInitOrder.trader.label("src_from_address"), + MayanInitOrder.trader.label("depositor"), + MayanInitOrder.addr_dest.label("recipient"), + MayanInitOrder.order_hash.label("order_hash"), + MayanInitOrder.token_out.label("token_out"), + MayanInitOrder.original_src_token.label("original_src_token"), + MayanInitOrder.original_src_amount.label("original_src_amount"), + MayanInitOrder.middle_src_token.label("middle_src_token"), + MayanInitOrder.middle_src_amount.label("middle_src_amount"), + MayanOrderFulfilled.net_amount.label("output_amount"), + MayanOrderFulfilled.middle_dst_token.label("middle_dst_token"), + MayanOrderFulfilled.middle_dst_amount.label("middle_dst_amount"), + MayanUnlock.signature.label("refund_transaction_hash"), + MayanUnlock.driver_acc.label("refund_from_address"), + MayanUnlock.amount.label("refund_amount"), + MayanUnlock.mint_from.label("refund_token"), + SrcTx.blockchain.label("src_blockchain"), + SrcTx.transaction_hash.label("src_transaction_hash"), + SrcTx.fee.label("src_fee"), + SrcTx.value.label("src_value"), + SrcTx.timestamp.label("src_timestamp"), + DstTx.transaction_hash.label("dst_transaction_hash"), + DstTx.blockchain.label("dst_blockchain"), + DstTx.from_address.label("dst_from_address"), + DstTx.to_address.label("dst_to_address"), + DstTx.fee.label("dst_fee"), + DstTx.value.label("dst_value"), + DstTx.timestamp.label("dst_timestamp"), + RefundTx.blockchain.label("refund_blockchain"), + RefundTx.fee.label("refund_fee"), + RefundTx.value.label("refund_value"), + RefundTx.timestamp.label("refund_timestamp"), + auction_data.c.auction_id.label("auction_id"), + auction_data.c.auction_first_bid_timestamp.label( + "auction_first_bid_timestamp" + ), + auction_data.c.auction_last_bid_timestamp.label( + "auction_last_bid_timestamp" + ), + auction_data.c.auction_number_of_bids.label("auction_number_of_bids"), ) .join(MayanOrderFulfilled, MayanInitOrder.order_hash == MayanOrderFulfilled.key) - .join(MayanUnlock, MayanInitOrder.state == MayanUnlock.state) - .outerjoin(SrcTx, SrcTx.transaction_hash == MayanInitOrder.signature) + .join(SrcTx, SrcTx.transaction_hash == MayanInitOrder.signature) + .join(DstTx, DstTx.transaction_hash == MayanOrderFulfilled.transaction_hash) + .outerjoin(MayanUnlock, MayanInitOrder.state == MayanUnlock.state) + .outerjoin(RefundTx, RefundTx.transaction_hash == MayanUnlock.signature) .outerjoin( - DstTx, DstTx.transaction_hash == MayanOrderFulfilled.transaction_hash + auction_data, + auction_data.c.order_hash == MayanInitOrder.order_hash, ) - .outerjoin(RefundTx, RefundTx.transaction_hash == MayanUnlock.signature) .all() ) cctxs = [] - for init, fulfilled, unlock, src_tx, dst_tx, refund_tx in results: + for row in results: cctxs.append( MayanCrossChainTransaction( - src_blockchain="solana", - src_transaction_hash=src_tx.transaction_hash, - src_from_address=init.trader, + src_blockchain=row.src_blockchain, + src_transaction_hash=row.src_transaction_hash, + src_from_address=row.src_from_address, src_to_address="BLZRi6frs4X4DNLw56V4EXai1b6QVESN1BhHBTYM9VcY", - src_fee=src_tx.fee, + src_fee=row.src_fee, + src_value=row.src_value, src_fee_usd=None, - src_timestamp=src_tx.timestamp, - dst_blockchain=dst_tx.blockchain, - dst_transaction_hash=dst_tx.transaction_hash, - dst_from_address=dst_tx.from_address, - dst_to_address=dst_tx.to_address, - dst_fee=dst_tx.fee, + src_timestamp=row.src_timestamp, + dst_blockchain=row.dst_blockchain, + dst_transaction_hash=row.dst_transaction_hash, + dst_from_address=row.dst_from_address, + dst_to_address=row.dst_to_address, + dst_fee=row.dst_fee, + dst_value=row.dst_value, dst_fee_usd=None, - dst_timestamp=dst_tx.timestamp, - refund_blockchain="solana", - refund_transaction_hash=unlock.signature, - refund_from_address=unlock.driver_acc, + dst_timestamp=row.dst_timestamp, + refund_blockchain=row.refund_blockchain, + refund_transaction_hash=row.refund_transaction_hash, + refund_from_address=row.refund_from_address, refund_to_address="9w1D9okTM8xNE7Ntb7LpaAaoLc6LfU9nHFs2h2KTpX1H", - refund_fee=refund_tx.fee, + refund_fee=row.refund_fee, + refund_value=row.refund_value, refund_fee_usd=None, - refund_timestamp=refund_tx.timestamp, - intent_id=init.order_hash, - depositor=init.trader, - recipient=init.addr_dest, - src_contract_address=init.mint_from, - dst_contract_address=init.token_out, - input_amount=init.amount_in, + refund_timestamp=row.refund_timestamp, + intent_id=row.order_hash, + depositor=row.depositor, + recipient=row.recipient, + src_contract_address=row.original_src_token, + dst_contract_address=row.token_out, + input_amount=row.original_src_amount, input_amount_usd=None, - output_amount=fulfilled.net_amount, + middle_src_token=row.middle_src_token, + middle_src_amount=row.middle_src_amount, + middle_src_amount_usd=None, + middle_dst_token=row.middle_dst_token, + middle_dst_amount=row.middle_dst_amount, + middle_dst_amount_usd=None, + output_amount=row.output_amount, output_amount_usd=None, - refund_amount=unlock.amount, + refund_amount=row.refund_amount, refund_amount_usd=None, - refund_token=unlock.mint_from, + refund_token=row.refund_token, + auction_id=row.auction_id, + auction_first_bid_timestamp=row.auction_first_bid_timestamp, + auction_last_bid_timestamp=row.auction_last_bid_timestamp, + auction_number_of_bids=row.auction_number_of_bids, ) ) @@ -263,6 +335,8 @@ def match_evm_to_sol(self): try: results = [] + auction_data = self.get_auction_data() + SrcTx = aliased(MayanBlockchainTransaction) DstTx = aliased(MayanBlockchainTransaction) RefundTx = aliased(MayanBlockchainTransaction) @@ -277,6 +351,8 @@ def match_evm_to_sol(self): MayanForwarded.token_out.label("token_out"), MayanForwarded.dst_addr.label("dst_addr"), MayanForwarded.amount.label("amount"), + literal(None).label("middle_src_token"), + literal(None).label("middle_src_amount"), literal_column("'forwarded'").label("entry_type"), ) @@ -290,6 +366,8 @@ def match_evm_to_sol(self): MayanSwapAndForwarded.token_out.label("token_out"), MayanSwapAndForwarded.dst_addr.label("dst_addr"), MayanSwapAndForwarded.amount_in.label("amount"), + MayanSwapAndForwarded.middle_token.label("middle_src_token"), + MayanSwapAndForwarded.middle_amount.label("middle_src_amount"), literal_column("'swap_and_forwarded'").label("entry_type"), ) @@ -304,17 +382,22 @@ def match_evm_to_sol(self): Fwd.c.trader.label("src_from_address"), Fwd.c.mayan_protocol.label("src_to_address"), SrcTx.fee.label("src_fee"), + SrcTx.value.label("src_value"), SrcTx.timestamp.label("src_timestamp"), MayanFulfillOrder.signature.label("dst_transaction_hash"), MayanFulfillOrder.driver.label("dst_from_address"), MayanFulfillOrder.dest.label("dst_to_address"), + MayanFulfillOrder.middle_dst_token.label("middle_dst_token"), + MayanFulfillOrder.middle_dst_amount.label("middle_dst_amount"), DstTx.fee.label("dst_fee"), + DstTx.value.label("dst_value"), DstTx.timestamp.label("dst_timestamp"), RefundTx.blockchain.label("refund_blockchain"), RefundTx.transaction_hash.label("refund_transaction_hash"), RefundTx.from_address.label("refund_from_address"), RefundTx.to_address.label("refund_to_address"), RefundTx.fee.label("refund_fee"), + RefundTx.value.label("refund_value"), RefundTx.timestamp.label("refund_timestamp"), MayanOrderCreated.key.label("intent_id"), Fwd.c.trader.label("depositor"), @@ -326,6 +409,16 @@ def match_evm_to_sol(self): else_=SrcTx.value, ).label("input_amount"), MayanFulfillOrder.amount.label("output_amount"), + auction_data.c.auction_id.label("auction_id"), + auction_data.c.auction_first_bid_timestamp.label( + "auction_first_bid_timestamp" + ), + auction_data.c.auction_last_bid_timestamp.label( + "auction_last_bid_timestamp" + ), + auction_data.c.auction_number_of_bids.label("auction_number_of_bids"), + Fwd.c.middle_src_token.label("middle_src_token"), + Fwd.c.middle_src_amount.label("middle_src_amount"), ) .join( MayanOrderCreated, @@ -335,12 +428,16 @@ def match_evm_to_sol(self): MayanRegisterOrder, MayanRegisterOrder.order_hash == MayanOrderCreated.key ) .join(MayanFulfillOrder, MayanFulfillOrder.state == MayanRegisterOrder.state) - .join(MayanOrderUnlocked, MayanOrderUnlocked.key == MayanOrderCreated.key) .join(SrcTx, SrcTx.transaction_hash == Fwd.c.transaction_hash) .join(DstTx, DstTx.transaction_hash == MayanFulfillOrder.signature) - .join( + .outerjoin(MayanOrderUnlocked, MayanOrderUnlocked.key == MayanOrderCreated.key) + .outerjoin( RefundTx, RefundTx.transaction_hash == MayanOrderUnlocked.transaction_hash ) + .outerjoin( + auction_data, + auction_data.c.order_hash == MayanRegisterOrder.order_hash, + ) ) cctxs = [] @@ -353,6 +450,7 @@ def match_evm_to_sol(self): src_from_address=row.src_from_address, src_to_address=row.src_to_address, src_fee=row.src_fee, + src_value=row.src_value, src_fee_usd=None, src_timestamp=row.src_timestamp, dst_blockchain="solana", @@ -360,6 +458,7 @@ def match_evm_to_sol(self): dst_from_address=row.dst_from_address, dst_to_address=row.dst_to_address, dst_fee=row.dst_fee, + dst_value=row.dst_value, dst_fee_usd=None, dst_timestamp=row.dst_timestamp, refund_blockchain=row.refund_blockchain, @@ -367,6 +466,7 @@ def match_evm_to_sol(self): refund_from_address=row.refund_from_address, refund_to_address=row.refund_to_address, refund_fee=row.refund_fee, + refund_value=row.refund_value, refund_fee_usd=None, refund_timestamp=row.refund_timestamp, intent_id=row.intent_id, @@ -376,6 +476,12 @@ def match_evm_to_sol(self): dst_contract_address=row.dst_contract_address, input_amount=row.input_amount, input_amount_usd=None, + middle_src_token=row.middle_src_token, + middle_src_amount=row.middle_src_amount, + middle_src_amount_usd=None, + middle_dst_token=row.middle_dst_token, + middle_dst_amount=row.middle_dst_amount, + middle_dst_amount_usd=None, output_amount=row.output_amount, output_amount_usd=None, refund_amount=row.input_amount, # in the case of usage of intermediary @@ -384,6 +490,10 @@ def match_evm_to_sol(self): # parse internal transactions and match to the unlock events. refund_amount_usd=None, refund_token="0x0000000000000000000000000000000000000000", + auction_id=row.auction_id, + auction_first_bid_timestamp=row.auction_first_bid_timestamp, + auction_last_bid_timestamp=row.auction_last_bid_timestamp, + auction_number_of_bids=row.auction_number_of_bids, ) ) @@ -420,6 +530,8 @@ def match_evm_to_evm(self): try: results = [] + auction_data = self.get_auction_data() + SrcTx = aliased(MayanBlockchainTransaction) DstTx = aliased(MayanBlockchainTransaction) RefundTx = aliased(MayanBlockchainTransaction) @@ -434,6 +546,8 @@ def match_evm_to_evm(self): MayanForwarded.token_out.label("token_out"), MayanForwarded.dst_addr.label("dst_addr"), MayanForwarded.amount.label("amount"), + literal(None).label("middle_src_token"), + literal(None).label("middle_src_amount"), literal_column("'forwarded'").label("entry_type"), ) @@ -446,6 +560,8 @@ def match_evm_to_evm(self): MayanSwapAndForwarded.token_out.label("token_out"), MayanSwapAndForwarded.dst_addr.label("dst_addr"), MayanSwapAndForwarded.amount_in.label("amount"), + MayanSwapAndForwarded.middle_token.label("middle_src_token"), + MayanSwapAndForwarded.middle_amount.label("middle_src_amount"), literal_column("'swap_and_forwarded'").label("entry_type"), ) @@ -463,41 +579,60 @@ def match_evm_to_evm(self): Fwd.c.trader.label("src_from_address"), Fwd.c.mayan_protocol.label("src_to_address"), SrcTx.fee.label("src_fee"), + SrcTx.value.label("src_value"), SrcTx.timestamp.label("src_timestamp"), MayanOrderFulfilled.transaction_hash.label("dst_transaction_hash"), MayanOrderFulfilled.blockchain.label("dst_blockchain"), MayanOrderFulfilled.net_amount.label("output_amount"), + MayanOrderFulfilled.middle_dst_token.label("middle_dst_token"), + MayanOrderFulfilled.middle_dst_amount.label("middle_dst_amount"), DstTx.from_address.label("dst_from_address"), DstTx.to_address.label("dst_to_address"), DstTx.fee.label("dst_fee"), + DstTx.value.label("dst_value"), DstTx.timestamp.label("dst_timestamp"), MayanOrderUnlocked.blockchain.label("refund_blockchain"), MayanOrderUnlocked.transaction_hash.label("refund_transaction_hash"), RefundTx.from_address.label("refund_from_address"), RefundTx.to_address.label("refund_to_address"), RefundTx.fee.label("refund_fee"), + RefundTx.value.label("refund_value"), RefundTx.timestamp.label("refund_timestamp"), MayanOrderCreated.key.label("intent_id"), Fwd.c.trader.label("depositor"), Fwd.c.dst_addr.label("recipient"), Fwd.c.token.label("src_contract_address"), Fwd.c.token_out.label("dst_contract_address"), + Fwd.c.middle_src_token.label("middle_src_token"), + Fwd.c.middle_src_amount.label("middle_src_amount"), case( (Fwd.c.amount != None, Fwd.c.amount), # noqa: E711 DO NOT REPLACE != WITH 'IS NOT' else_=SrcTx.value, ).label("input_amount"), + auction_data.c.auction_id.label("auction_id"), + auction_data.c.auction_first_bid_timestamp.label( + "auction_first_bid_timestamp" + ), + auction_data.c.auction_last_bid_timestamp.label( + "auction_last_bid_timestamp" + ), + auction_data.c.auction_number_of_bids.label("auction_number_of_bids"), ) .join( MayanOrderCreated, MayanOrderCreated.transaction_hash == Fwd.c.transaction_hash, ) .join(MayanOrderFulfilled, MayanOrderFulfilled.key == MayanOrderCreated.key) - .join(MayanOrderUnlocked, MayanOrderUnlocked.key == MayanOrderCreated.key) .join(SrcTx, SrcTx.transaction_hash == Fwd.c.transaction_hash) .join(DstTx, DstTx.transaction_hash == MayanOrderFulfilled.transaction_hash) - .join( + .outerjoin(MayanOrderUnlocked, MayanOrderCreated.key == MayanOrderUnlocked.key) + .outerjoin( RefundTx, RefundTx.transaction_hash == MayanOrderUnlocked.transaction_hash ) + .outerjoin( + auction_data, + auction_data.c.order_hash == MayanOrderFulfilled.key, + ) .all() ) @@ -511,6 +646,7 @@ def match_evm_to_evm(self): src_from_address=row.src_from_address, src_to_address=row.src_to_address, src_fee=row.src_fee, + src_value=row.src_value, src_fee_usd=None, src_timestamp=row.src_timestamp, dst_blockchain=row.dst_blockchain, @@ -518,6 +654,7 @@ def match_evm_to_evm(self): dst_from_address=row.dst_from_address, dst_to_address=row.dst_to_address, dst_fee=row.dst_fee, + dst_value=row.dst_value, dst_fee_usd=None, dst_timestamp=row.dst_timestamp, refund_blockchain=row.refund_blockchain, @@ -525,6 +662,7 @@ def match_evm_to_evm(self): refund_from_address=row.refund_from_address, refund_to_address=row.refund_to_address, refund_fee=row.refund_fee, + refund_value=row.refund_value, refund_fee_usd=None, refund_timestamp=row.refund_timestamp, intent_id=row.intent_id, @@ -534,11 +672,21 @@ def match_evm_to_evm(self): dst_contract_address=row.dst_contract_address, input_amount=row.input_amount, input_amount_usd=None, + middle_src_token=row.middle_src_token, + middle_src_amount=row.middle_src_amount, + middle_src_amount_usd=None, + middle_dst_token=row.middle_dst_token, + middle_dst_amount=row.middle_dst_amount, + middle_dst_amount_usd=None, output_amount=row.output_amount, output_amount_usd=None, refund_amount=row.input_amount, refund_amount_usd=None, refund_token="0x0000000000000000000000000000000000000000", + auction_id=row.auction_id, + auction_first_bid_timestamp=row.auction_first_bid_timestamp, + auction_last_bid_timestamp=row.auction_last_bid_timestamp, + auction_number_of_bids=row.auction_number_of_bids, ) ) @@ -564,6 +712,38 @@ def match_evm_to_evm(self): f"Error processing token transfers. Error: {e}", ) from e + def get_auction_data(self): + """ + Returns a subquery to gather the auction data (id, open timestamp and number of bids) + """ + func_name = "get_auction_data" + + try: + results = [] + + with self.cross_chain_transactions_repo.get_session() as session: + bid_tx = aliased(MayanBlockchainTransaction) + + return ( + session.query( + MayanAuctionBid.auction_state.label("auction_id"), + MayanAuctionBid.order_hash.label("order_hash"), + func.min(bid_tx.timestamp).label("auction_first_bid_timestamp"), + func.max(bid_tx.timestamp).label("auction_last_bid_timestamp"), + func.count(bid_tx.transaction_hash).label("auction_number_of_bids"), + ) + .join(bid_tx, MayanAuctionBid.signature == bid_tx.transaction_hash) + .group_by(MayanAuctionBid.auction_state, MayanAuctionBid.order_hash) + .subquery() + ) + + return results + + except Exception as e: + raise CustomException( + self.CLASS_NAME, func_name, f"Error fetching auction data. Error: {e}" + ) from e + def populate_token_info_tables(self, cctxs, start_ts, end_ts): start_time = time.time() log_to_cli(build_log_message_generator(self.bridge, "Fetching token prices...")) @@ -667,7 +847,7 @@ def fetch_solana_data(self, start_ts, end_ts): .where( TokenMetadata.blockchain == "bnb", TokenMetadata.symbol == "DONKEY", - TokenMetadata.address == "0xa49fa5e8106e2d6d6a69e78df9b6a20aab9c4444", + TokenMetadata.address == "0xA49fA5E8106E2d6d6a69E78df9B6A20AaB9c4444", ) .execution_options(synchronize_session=False) ) diff --git a/repository/base.py b/repository/base.py index c970942..bfaaca0 100644 --- a/repository/base.py +++ b/repository/base.py @@ -74,6 +74,14 @@ def execute(self, query): with self.get_session() as session: session.execute(query) + def has_records(self) -> bool: + """ + Check if the table has any records. + Returns True if records exist, False otherwise. + """ + with self.get_session() as session: + return session.query(self.model).count() > 0 + class CrossChainRepository(BaseRepository): @abstractmethod diff --git a/repository/common/models.py b/repository/common/models.py index 11a1289..dae9db8 100644 --- a/repository/common/models.py +++ b/repository/common/models.py @@ -74,6 +74,7 @@ class BlockchainTransaction(Base): to_address = Column(String(42), nullable=False) status = Column(Integer, nullable=False) value = Column(Numeric(30, 0), nullable=True) + input_data = Column(String(35000), nullable=True) fee = Column(Numeric(30, 0), nullable=False) def __init__( @@ -86,6 +87,7 @@ def __init__( to_address, status, value, + input_data, fee, ): self.blockchain = blockchain @@ -96,4 +98,5 @@ def __init__( self.to_address = to_address self.status = status self.value = value + self.input_data = input_data self.fee = fee diff --git a/repository/mayan/models.py b/repository/mayan/models.py index b92eaa7..2466ed0 100644 --- a/repository/mayan/models.py +++ b/repository/mayan/models.py @@ -1,6 +1,7 @@ # ruff: noqa: E501 from sqlalchemy import BigInteger, Boolean, Column, Float, Integer, Numeric, String +from repository.common.models import BlockchainTransaction from repository.database import Base @@ -216,13 +217,26 @@ class MayanOrderFulfilled(Base): net_amount = Column(Numeric(30, 0), nullable=False) blockchain = Column(String(10), nullable=False) transaction_hash = Column(String(66), nullable=False) + middle_dst_token = Column(String(44), nullable=True) + middle_dst_amount = Column(Numeric(30, 0), nullable=True) - def __init__(self, key, blockchain, transaction_hash, sequence, net_amount): + def __init__( + self, + key, + blockchain, + transaction_hash, + sequence, + net_amount, + middle_dst_token, + middle_dst_amount, + ): self.key = key self.blockchain = blockchain self.transaction_hash = transaction_hash self.sequence = sequence self.net_amount = net_amount + self.middle_dst_token = middle_dst_token + self.middle_dst_amount = middle_dst_amount def __repr__(self): return ( @@ -230,7 +244,9 @@ def __repr__(self): f"blockchain={self.blockchain}, " f"transaction_hash={self.transaction_hash}, " f"sequence={self.sequence}, " - f"net_amount={self.net_amount})>" + f"net_amount={self.net_amount}, " + f"middle_dst_token={self.middle_dst_token}, " + f"middle_dst_amount={self.middle_dst_amount}>)" ) @@ -267,12 +283,12 @@ class MayanInitOrder(Base): state = Column(String(44), nullable=False) state_from_acc = Column(String(44), nullable=False) relayer_fee_acc = Column(String(44), nullable=False) - mint_from = Column(String(44), nullable=False) + middle_src_token = Column(String(44), nullable=True) fee_manager_program = Column(String(44), nullable=False) token_program = Column(String(44), nullable=False) system_program = Column(String(44), nullable=False) - amount_in_min = Column(Numeric(30, 0), nullable=False) - amount_in = Column(Numeric(30, 0), nullable=False) + middle_src_amount_min = Column(Numeric(30, 0), nullable=True) + middle_src_amount = Column(Numeric(30, 0), nullable=True) native_input = Column(Boolean, nullable=False) fee_submit = Column(Numeric(30, 0), nullable=False) addr_dest = Column(String(128), nullable=False) @@ -288,6 +304,9 @@ class MayanInitOrder(Base): fee_rate_mayan = Column(Integer, nullable=False) auction_mode = Column(Integer, nullable=False) key_rnd = Column(String(128), nullable=False) + original_src_token = Column(String(44), nullable=False) + original_src_amount = Column(Numeric(30, 0), nullable=False) + amm = Column(String(44), nullable=True) def __init__( self, @@ -298,12 +317,12 @@ def __init__( state, state_from_acc, relayer_fee_acc, - mint_from, + middle_src_token, fee_manager_program, token_program, system_program, - amount_in_min, - amount_in, + middle_src_amount_min, + middle_src_amount, native_input, fee_submit, addr_dest, @@ -319,6 +338,9 @@ def __init__( fee_rate_mayan, auction_mode, key_rnd, + original_src_token, + original_src_amount, + amm, ): self.trader = trader self.order_hash = order_hash @@ -327,12 +349,12 @@ def __init__( self.state = state self.state_from_acc = state_from_acc self.relayer_fee_acc = relayer_fee_acc - self.mint_from = mint_from + self.middle_src_token = middle_src_token self.fee_manager_program = fee_manager_program self.token_program = token_program self.system_program = system_program - self.amount_in_min = amount_in_min - self.amount_in = amount_in + self.middle_src_amount_min = middle_src_amount_min + self.middle_src_amount = middle_src_amount self.native_input = native_input self.fee_submit = fee_submit self.addr_dest = addr_dest @@ -348,21 +370,26 @@ def __init__( self.fee_rate_mayan = fee_rate_mayan self.auction_mode = auction_mode self.key_rnd = key_rnd + self.original_src_token = original_src_token + self.original_src_amount = original_src_amount + self.amm = amm def __repr__(self): return ( f"" + f"key_rnd={self.key_rnd}, order_hash={self.order_hash}, " + f"signature={self.signature}, original_src_token={self.original_src_token}, " + f"original_src_amount={self.original_src_amount}, amm={self.amm}>)" ) @@ -427,6 +454,9 @@ class MayanFulfillOrder(Base): system_program = Column(String(44), nullable=False) addr_unlocker = Column(String(64), nullable=True) amount = Column(Numeric(30, 0), nullable=False) + middle_dst_token = Column(String(44), nullable=True) + middle_dst_amount = Column(Numeric(30, 0), nullable=True) + amm = Column(String(44), nullable=True) def __init__( self, @@ -439,6 +469,9 @@ def __init__( system_program, addr_unlocker, amount, + middle_dst_token, + middle_dst_amount, + amm, ): self.signature = signature self.state = state @@ -449,13 +482,17 @@ def __init__( self.system_program = system_program self.addr_unlocker = addr_unlocker self.amount = amount + self.middle_dst_token = middle_dst_token + self.middle_dst_amount = middle_dst_amount + self.amm = amm def __repr__(self): return ( f"" + f"amount={self.amount}, middle_dst_token={self.middle_dst_token}, " + f"middle_dst_amount={self.middle_dst_amount}, amm={self.amm}>)" ) @@ -742,40 +779,21 @@ def __repr__(self): ) -class MayanBlockchainTransaction(Base): +class MayanBlockchainTransaction(BlockchainTransaction): __tablename__ = "mayan_blockchain_transactions" - blockchain = Column(String(10), nullable=False) - transaction_hash = Column(String(88), nullable=False, primary_key=True) - block_number = Column(Integer, nullable=False) - timestamp = Column(BigInteger, nullable=False) - from_address = Column(String(42), nullable=True) - to_address = Column(String(42), nullable=True) - status = Column(Integer, nullable=False) - value = Column(Numeric(30, 0), nullable=True) - fee = Column(Numeric(30, 0), nullable=False) - - def __init__( - self, - blockchain, - transaction_hash, - block_number, - timestamp, - from_address, - to_address, - status, - value, - fee, - ): - self.blockchain = blockchain - self.transaction_hash = transaction_hash - self.block_number = block_number - self.timestamp = timestamp - self.from_address = from_address - self.to_address = to_address - self.status = status - self.value = value - self.fee = fee + def __repr__(self): + return ( + f"" + ) ########## Processed Data ########## @@ -789,6 +807,7 @@ class MayanCrossChainTransaction(Base): src_from_address = Column(String(44), nullable=False) src_to_address = Column(String(44), nullable=False) src_fee = Column(Numeric(30, 0), nullable=False) + src_value = Column(Numeric(30, 0), nullable=True) src_fee_usd = Column(Float, nullable=True) src_timestamp = Column(BigInteger, nullable=False) dst_blockchain = Column(String(10), nullable=False) @@ -796,15 +815,17 @@ class MayanCrossChainTransaction(Base): dst_from_address = Column(String(44), nullable=False) dst_to_address = Column(String(44), nullable=False) dst_fee = Column(Numeric(30, 0), nullable=False) + dst_value = Column(Numeric(30, 0), nullable=True) dst_fee_usd = Column(Float, nullable=True) dst_timestamp = Column(BigInteger, nullable=False) - refund_blockchain = Column(String(10), nullable=False) - refund_transaction_hash = Column(String(88), nullable=False) - refund_from_address = Column(String(44), nullable=False) - refund_to_address = Column(String(44), nullable=False) - refund_fee = Column(Numeric(30, 0), nullable=False) + refund_blockchain = Column(String(10), nullable=True) + refund_transaction_hash = Column(String(88), nullable=True) + refund_from_address = Column(String(44), nullable=True) + refund_to_address = Column(String(44), nullable=True) + refund_fee = Column(Numeric(30, 0), nullable=True) + refund_value = Column(Numeric(30, 0), nullable=True) refund_fee_usd = Column(Float, nullable=True) - refund_timestamp = Column(BigInteger, nullable=False) + refund_timestamp = Column(BigInteger, nullable=True) intent_id = Column(String(64), nullable=False, primary_key=True) depositor = Column(String(44), nullable=False) recipient = Column(String(44), nullable=False) @@ -812,11 +833,21 @@ class MayanCrossChainTransaction(Base): dst_contract_address = Column(String(44), nullable=False) input_amount = Column(Numeric(30, 0), nullable=False) input_amount_usd = Column(Float, nullable=True) + middle_src_token = Column(String(44), nullable=True) + middle_src_amount = Column(Numeric(30, 0), nullable=True) + middle_src_amount_usd = Column(Float, nullable=True) + middle_dst_token = Column(String(44), nullable=True) + middle_dst_amount = Column(Numeric(30, 0), nullable=True) + middle_dst_amount_usd = Column(Float, nullable=True) output_amount = Column(Numeric(30, 0), nullable=False) output_amount_usd = Column(Float, nullable=True) - refund_amount = Column(Numeric(30, 0), nullable=False) + refund_amount = Column(Numeric(30, 0), nullable=True) refund_amount_usd = Column(Float, nullable=True) - refund_token = Column(String(44), nullable=False) + refund_token = Column(String(44), nullable=True) + auction_id = Column(String(64), nullable=True) + auction_first_bid_timestamp = Column(BigInteger, nullable=True) + auction_last_bid_timestamp = Column(BigInteger, nullable=True) + auction_number_of_bids = Column(Integer, nullable=True) def __init__( self, @@ -825,6 +856,7 @@ def __init__( src_from_address, src_to_address, src_fee, + src_value, src_fee_usd, src_timestamp, dst_blockchain, @@ -832,6 +864,7 @@ def __init__( dst_from_address, dst_to_address, dst_fee, + dst_value, dst_fee_usd, dst_timestamp, refund_blockchain, @@ -839,6 +872,7 @@ def __init__( refund_from_address, refund_to_address, refund_fee, + refund_value, refund_fee_usd, refund_timestamp, intent_id, @@ -848,17 +882,28 @@ def __init__( dst_contract_address, input_amount, input_amount_usd, + middle_src_token, + middle_src_amount, + middle_src_amount_usd, + middle_dst_token, + middle_dst_amount, + middle_dst_amount_usd, output_amount, output_amount_usd, refund_amount, refund_amount_usd, refund_token, + auction_id, + auction_first_bid_timestamp, + auction_last_bid_timestamp, + auction_number_of_bids, ): self.src_blockchain = src_blockchain self.src_transaction_hash = src_transaction_hash self.src_from_address = src_from_address self.src_to_address = src_to_address self.src_fee = src_fee + self.src_value = src_value self.src_fee_usd = src_fee_usd self.src_timestamp = src_timestamp self.dst_blockchain = dst_blockchain @@ -866,6 +911,7 @@ def __init__( self.dst_from_address = dst_from_address self.dst_to_address = dst_to_address self.dst_fee = dst_fee + self.dst_value = dst_value self.dst_fee_usd = dst_fee_usd self.dst_timestamp = dst_timestamp self.refund_blockchain = refund_blockchain @@ -873,6 +919,7 @@ def __init__( self.refund_from_address = refund_from_address self.refund_to_address = refund_to_address self.refund_fee = refund_fee + self.refund_value = refund_value self.refund_fee_usd = refund_fee_usd self.refund_timestamp = refund_timestamp self.intent_id = intent_id @@ -882,8 +929,18 @@ def __init__( self.dst_contract_address = dst_contract_address self.input_amount = input_amount self.input_amount_usd = input_amount_usd + self.middle_src_token = middle_src_token + self.middle_src_amount = middle_src_amount + self.middle_src_amount_usd = middle_src_amount_usd + self.middle_dst_token = middle_dst_token + self.middle_dst_amount = middle_dst_amount + self.middle_dst_amount_usd = middle_dst_amount_usd self.output_amount = output_amount self.output_amount_usd = output_amount_usd self.refund_amount = refund_amount self.refund_amount_usd = refund_amount_usd self.refund_token = refund_token + self.auction_id = auction_id + self.auction_first_bid_timestamp = auction_first_bid_timestamp + self.auction_last_bid_timestamp = auction_last_bid_timestamp + self.auction_number_of_bids = auction_number_of_bids diff --git a/repository/mayan/repository.py b/repository/mayan/repository.py index 1c2a141..4fc9791 100644 --- a/repository/mayan/repository.py +++ b/repository/mayan/repository.py @@ -46,6 +46,14 @@ def event_exists(self, transaction_hash: str): .first() ) + def get_all_forwarded_eth(self): + with self.get_session() as session: + return ( + session.query(MayanForwarded) + .filter(MayanForwarded.token == "0x0000000000000000000000000000000000000000") + .all() + ) + class MayanOrderCreatedRepository(BaseRepository): def __init__(self, session_factory): @@ -64,6 +72,14 @@ def event_exists(self, key: str): with self.get_session() as session: return session.query(MayanOrderFulfilled).filter(MayanOrderFulfilled.key == key).first() + def update_middle_info_order_fulfilled( + self, key: str, middle_dst_token: str, middle_dst_amount: float + ): + with self.get_session() as session: + session.query(MayanOrderFulfilled).filter(MayanOrderFulfilled.key == key).update( + {"middle_dst_token": middle_dst_token, "middle_dst_amount": middle_dst_amount} + ) + class MayanOrderUnlockedRepository(BaseRepository): def __init__(self, session_factory): @@ -212,6 +228,7 @@ def get_unique_src_dst_contract_pairs(self): MayanCrossChainTransaction.src_contract_address, MayanCrossChainTransaction.dst_blockchain, MayanCrossChainTransaction.dst_contract_address, + func.count().label("pair_count"), ) .group_by( MayanCrossChainTransaction.src_blockchain, @@ -219,6 +236,7 @@ def get_unique_src_dst_contract_pairs(self): MayanCrossChainTransaction.dst_blockchain, MayanCrossChainTransaction.dst_contract_address, ) + .order_by(func.count().desc()) .all() ) diff --git a/rpcs/alchemy_client.py b/rpcs/alchemy_client.py index 3bfd7cb..391a70c 100644 --- a/rpcs/alchemy_client.py +++ b/rpcs/alchemy_client.py @@ -118,6 +118,12 @@ def get_token_prices_by_symbol_or_address( log_error(bridge, exception) + if "Your free app has exceeded its limit" in response.text: + # If the error is due to rate limiting, return an empty dict + # The tool will continue to run and fetch metadata for other tokens, + # as the rate limit is only for token price fetching + return {} + if i < 4: time.sleep(2**i) # Exponential backoff else: diff --git a/rpcs/evm_rpc_client.py b/rpcs/evm_rpc_client.py index f9812c2..6527e1f 100644 --- a/rpcs/evm_rpc_client.py +++ b/rpcs/evm_rpc_client.py @@ -71,6 +71,7 @@ def process_transaction(self, blockchain: str, tx_hash: str, block_number: str) if response_receipt and response_tx: response_receipt["result"]["value"] = response_tx["result"]["value"] + response_receipt["result"]["input"] = response_tx["result"]["input"] return response_receipt["result"] if response_receipt else {}, response_block[ "result" diff --git a/rpcs/rpc_client.py b/rpcs/rpc_client.py index 6b335fe..931af21 100644 --- a/rpcs/rpc_client.py +++ b/rpcs/rpc_client.py @@ -10,7 +10,7 @@ MAX_NUM_THREADS_EXTRACTOR, RPCS_CONFIG_FILE, ) -from utils.utils import CustomException, load_solana_api_key +from utils.utils import CustomException, load_solana_api_key, log_error class RPCClient(ABC): @@ -115,7 +115,15 @@ def make_request(self, rpc_url: str, blockchain_name: str, method: str, params: # exponentially and try again all endpoints. Only return once we have # a correct response time.sleep(backoff) - backoff *= 2 + log_error( + self.bridge, + ( + f"Failed to make RPC request to {blockchain_name}, method {method}, " + f"params {params}. Tried RPCs: {tried_rpcs}. Retrying with backoff ", + f"{backoff} seconds.", + ), + ) + backoff = (backoff * 2) if backoff < 30 else 0 except Exception as e: raise CustomException( diff --git a/tests/mayan/test_data_extraction.py b/tests/mayan/test_data_extraction.py index 5350710..8b204dc 100644 --- a/tests/mayan/test_data_extraction.py +++ b/tests/mayan/test_data_extraction.py @@ -35,7 +35,7 @@ def test_extract_data(): mayan_swap_and_fwd = MayanSwapAndForwardedRepository(DBSession) events = mayan_swap_and_fwd.get_all() print(f"Number of events in MayanSwapAndForwarded: {len(events)}") - assert len(events) == 5, "Expected 5 events in MayanSwapAndForwarded table after extraction." + assert len(events) == 264, "Expected 264 events in MayanSwapAndForwarded table after extraction" mayan_order_created = MayanOrderCreatedRepository(DBSession) events = mayan_order_created.get_all() @@ -57,7 +57,7 @@ def test_extract_data(): mayan_forwarded = MayanForwardedRepository(DBSession) events = mayan_forwarded.get_all() print(f"Number of events in MayanForwarded: {len(events)}") - assert len(events) == 4, "Expected 4 events in MayanForwarded table after extraction." + assert len(events) == 500, "Expected 500 events in MayanForwarded table after extraction." mayan_blockchain_transaction = MayanBlockchainTransactionRepository(DBSession) transactions = mayan_blockchain_transaction.get_all() diff --git a/tests/mayan/test_resolve_swaps.py b/tests/mayan/test_resolve_swaps.py new file mode 100644 index 0000000..7084ac5 --- /dev/null +++ b/tests/mayan/test_resolve_swaps.py @@ -0,0 +1,313 @@ +from extractor.mayan.handler import MayanHandler + + +def test_3_swaps(): + # 2jVEXxUpgJx6me5nXyX9VGS968FgDPVy5GNcjaho9mBKEcxuKUx1AGAYvqBud9GoTMWmHVDZpuPGeQaXosQTVCey + swaps = [ + { + "args": { + "amm": "SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe", + "input_mint": "cbbtcf3aa214zXHbiAZQwf4122FBYbraNdFqgw4iMij", + "input_amount": "61beb0", + "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "output_amount": "018f562d9a", + }, + "name": "SwapEvent", + }, + { + "args": { + "amm": "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc", + "input_mint": "cbbtcf3aa214zXHbiAZQwf4122FBYbraNdFqgw4iMij", + "input_amount": "0adc4d", + "output_mint": "27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4", + "output_amount": "09f8fa5f", + }, + "name": "SwapEvent", + }, + { + "args": { + "amm": "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo", + "input_mint": "27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4", + "input_amount": "09f8fa5f", + "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "output_amount": "2c5f0251", + }, + "name": "SwapEvent", + }, + ] + + result = MayanHandler.resolve_swaps("", swaps) + assert result == { + "args": { + "input_mint": "cbbtcf3aa214zXHbiAZQwf4122FBYbraNdFqgw4iMij", + "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "input_amount": "0x6c9afd", + "output_amount": "0x1bbb52feb", + } + } + + +def test_3_agg_swaps(): + # BoqZDVMr7ZK47r2rnJcCGJRX3LmywvswkqEyCkD1UC1HcdbKSNNt2JKSLxNbz547cmCQpRRuRk6qaHbGDTBxtXP + swaps = [ + { + "args": { + "amm": "ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY", + "input_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "input_amount": "0x3B09F493A", + "output_mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "output_amount": "0x25439125", + }, + "name": "SwapEvent", + }, + { + "args": { + "amm": "SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe", + "input_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "input_amount": "0x2F3B2A0FB", + "output_mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "output_amount": "0x1DCF7DDB", + }, + "name": "SwapEvent", + }, + { + "args": { + "amm": "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY", + "input_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "input_amount": "0xBCECA83F", + "output_mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "output_amount": "0x773CE70", + }, + "name": "SwapEvent", + }, + ] + + result = MayanHandler.resolve_swaps("", swaps) + assert result == { + "args": { + "input_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "output_mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "input_amount": "0x7613e9274", + "output_amount": "0x4a86dd70", + } + } + + +def test_1_swap(): + # 4D8wroxz46EaBzHwrd3Pyg7W1rDifVTSLjx93KL5Vy7KtM5A2UHGKek3PKepu4hhD4db36xJjKRtVKK3DFcfSLyB + swaps = [ + { + "args": { + "amm": "SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe", + "input_mint": "So11111111111111111111111111111111111111112", + "input_amount": "0x8077B20", + "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "output_amount": "0x1407A0E", + }, + "name": "SwapEvent", + } + ] + + result = MayanHandler.resolve_swaps("", swaps) + assert result == { + "args": { + "input_mint": "So11111111111111111111111111111111111111112", + "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "input_amount": "0x8077B20", + "output_amount": "0x1407A0E", + } + } + + +def test_1_linked_swaps(): + # 4CMPJt8VGYd3nFrss1HRDwua1iRx2dWetiuRWzS69z3fbMcRP1TanxwrmauQaoMmvfh3aAcBEv8j3vBj8k7PvHU2 + swaps = [ + { + "args": { + "amm": "2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c", + "input_mint": "So11111111111111111111111111111111111111112", + "input_amount": "0x15712CC", + "output_mint": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "output_amount": "0x356186", + }, + "name": "SwapEvent", + }, + { + "args": { + "amm": "obriQD1zbpyLz95G5n7nJe6a4DPjpFwa5XYPoNm113y", + "input_mint": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "input_amount": "0x356186", + "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "output_amount": "0x3568C3", + }, + "name": "SwapEvent", + }, + ] + + result = MayanHandler.resolve_swaps("", swaps) + assert result == { + "args": { + "input_mint": "So11111111111111111111111111111111111111112", + "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "input_amount": "0x15712cc", + "output_amount": "0x3568c3", + } + } + + +def test_1_linked_and_agg_swaps(): + # 55wrchD5fKuyLpuBKmr3h2uq9Rzt4JLyaYJ7eimVvS7Lxe8YCnzeKd4U3fwkF9JCR9AZwj8RU17jJFK327VuHNSk + swaps = [ + { + "args": { + "amm": "opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb", + "input_mint": "So11111111111111111111111111111111111111112", + "input_amount": "0x18AE17A400", + "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "output_amount": "0x3D9D61D35", + }, + "name": "SwapEvent", + }, + { + "args": { + "amm": "SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe", + "input_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "input_amount": "0x18A55A548", + "output_mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "output_amount": "0xFC94E34", + }, + "name": "SwapEvent", + }, + { + "args": { + "amm": "ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY", + "input_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "input_amount": "0x24F8077ED", + "output_mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "output_amount": "0x17ADE68C", + }, + "name": "SwapEvent", + }, + ] + + result = MayanHandler.resolve_swaps("", swaps) + assert result == { + "args": { + "input_mint": "So11111111111111111111111111111111111111112", + "output_mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "input_amount": "0x18ae17a400", + "output_amount": "0x277734c0", + } + } + + +def test_4_swaps(): + # 4zUMPu7pQscvmaExW53h45Q5H61uB71kxvN9quwxuLo86Mdrvizhd5YqQMS3XTwKM111HWHhCCusiuCunJBggiWb + swaps = [ + { + "args": { + "amm": "ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY", + "input_mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "input_amount": "0x5ca079a", + "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "output_amount": "0x902595de", + }, + "name": "SwapEvent", + }, + { + "args": { + "amm": "ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY", + "input_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "input_amount": "0x81bb6d47", + "output_mint": "So11111111111111111111111111111111111111112", + "output_amount": "0x3577ca10d", + }, + "name": "SwapEvent", + }, + { + "args": { + "amm": "opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb", + "input_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "input_amount": "0xe6a2897", + "output_mint": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "output_amount": "0xe680ca0", + }, + "name": "SwapEvent", + }, + { + "args": { + "amm": "2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c", + "input_mint": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "input_amount": "0xe680ca0", + "output_mint": "So11111111111111111111111111111111111111112", + "output_amount": "0x5f0db6f3", + }, + "name": "SwapEvent", + }, + ] + + result = MayanHandler.resolve_swaps("", swaps) + assert result == { + "args": { + "input_mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "output_mint": "So11111111111111111111111111111111111111112", + "input_amount": "0x5ca079a", + "output_amount": "0x3b68a5800", + } + } + + +def test_4_swaps_linkable(): + # SfSV2aKav4KMGqzEvHM7LvUqBdsGKoEZQMeQ6GEC6zhegJzT48BDePih7JTM3R2mR62442pr6Yn1pMHomZa6Nhr + swaps = [ + { + "args": { + "amm": "SoLFiHG9TfgtdUXUjWAxi3LtvYuFyDLVhBWxdMZxyCe", + "input_mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "input_amount": "0x6298dbc", + "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "output_amount": "0x9c8a005f", + }, + "name": "SwapEvent", + }, + { + "args": { + "amm": "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY", + "input_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "input_amount": "0x7d3b337f", + "output_mint": "KENJSUYLASHUMfHyy5o4Hp2FdNqZg1AsUPhfH2kYvEP", + "output_amount": "0x7ea51cb00", + }, + "name": "SwapEvent", + }, + { + "args": { + "amm": "2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c", + "input_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "input_amount": "0x1f4ecce0", + "output_mint": "So11111111111111111111111111111111111111112", + "output_amount": "0xccc6267f", + }, + "name": "SwapEvent", + }, + { + "args": { + "amm": "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", + "input_mint": "So11111111111111111111111111111111111111112", + "input_amount": "0xccc6267f", + "output_mint": "KENJSUYLASHUMfHyy5o4Hp2FdNqZg1AsUPhfH2kYvEP", + "output_amount": "0x1f9df046f", + }, + "name": "SwapEvent", + }, + ] + + result = MayanHandler.resolve_swaps("", swaps) + assert result == { + "args": { + "input_mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", + "output_mint": "KENJSUYLASHUMfHyy5o4Hp2FdNqZg1AsUPhfH2kYvEP", + "input_amount": "0x6298dbc", + "output_amount": "0x9e430cf6f", + } + } diff --git a/tests/polygon/test_data_extraction.py b/tests/polygon/test_data_extraction.py index 6de25f6..2ca7733 100644 --- a/tests/polygon/test_data_extraction.py +++ b/tests/polygon/test_data_extraction.py @@ -24,7 +24,7 @@ def test_extract_data(): polygon_deposit_requested = PolygonLockedTokenRepository(DBSession) events = polygon_deposit_requested.get_all() print(f"Number of events in PolygonLockedToken: {len(events)}") - assert len(events) == 143, "Expected 143 events in PolygonLockedToken table after extraction." + assert len(events) == 144, "Expected 144 events in PolygonLockedToken table after extraction." polygon_state_synced = PolygonStateSyncedRepository(DBSession) events = polygon_state_synced.get_all() @@ -58,8 +58,8 @@ def test_extract_data(): polygon_cross_chain_transactions_repo = PolygonCrossChainTransactionsRepository(DBSession) transactions = polygon_cross_chain_transactions_repo.get_all() print(f"Number of transactions in PolygonCrossChainTransactions: {len(transactions)}") - assert len(transactions) == 152, ( - "Expected 152 events in PolygonCrossChainTransactions table after extraction." + assert len(transactions) == 153, ( + "Expected 153 events in PolygonCrossChainTransactions table after extraction." ) polygon_plasma_cross_chain_transactions_repo = PolygonPlasmaCrossChainTransactionsRepository(