How to Swap on the Gnosis Chain with the Web3.py Python library
- Last updated: Sep 13, 2024

⚠️Disclaimer: Crypto-currencies are still a young technology, so only use money you can afford to lose. Automating transactions on blockchains can be extremely dangerous and a source of errors that can result in the total loss of invested sums. Make sure you understand every action before you take any of the steps described below.⚠️
I was looking for a way to make swaps on the Gnosis Chain (which is an Ethereum sidechain) via scripts. After some research, I heard about the Web3.py Python library that lets you interact with an Ethereum node. I then discovered that the exchange aggregator https://app.1inch.io/ provides an API which support Web3.py, and they also provide documentation here: https://portal.1inch.dev/! Easy money! Well… Unfortunately it's pretty awful and it's broken. (If it wasn't, this article wouldn't be useful, would it?) So, I'm going to describe here how to swap on the Gnosis Chain using the 1inch API.
- What I will do:
- Create a wallet
- Show some uses of the 1inch API
- Do a simple swap from USDT to USDC
Create a 1inch account

To be able to use their API you need to sign in via a GitHub or a Google from the 1inch portal here: https://portal.1inch.dev/login.

- It will allow to get a API Key essential to make requests on the 1inch API:

Install the Web3.py library
Note: For this example I used a fresh Debian installed machine.
- Create a Python virtual space:
john@desktop:~$ python3 -m venv .web3
- Load the Python virtual space:
john@desktop:~$ source .web3/bin/activate
- Install the Web3.py library:
(.web3) john@desktop:~$ python3 -m pip install web3
- Upgrade the Web3.py library:
(.web3) john@desktop:~$ python3 -m pip install --upgrade web3
- Run Python:
(.web3) john@desktop:~$ python3
Python 3.11.2 (main, Mar 13 2023, 12:18:29) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
Create a Wallet
- Connect to the Python virtual space:
john@desktop:~$ source .web3/bin/activate
(.web3) john@desktop:~$ python3
- Import the Web3 library:
>>> from web3 import Web3
>>> w3 = Web3()
- Create the wallet:
>>> acc = w3.eth.account.create()
- Show the private key:
>>> w3.to_hex(acc._private_key)
- Show the wallet address:
>>> acc.address
- Now we have:
- Our wallet address that we can share
- Our private key that we need to keep… private…
Connect MetaMask to the Wallet
I particularly recommend (especially at the beginning) using a Web Wallet in parallel to check that everything we do is correct. This can be done using MetaMask, for example, or any other. Just make sure you download a trusted wallet from a trusted source.
- From a preconfigured MetaMask click to current Account, then select Add account or hardware wallet, then Import account:

- From here just paste your private key and click Import:

- Switch to the Gnosis Network:

- From here, you can import your tokens with their respective addresses:

Using the 1inch API
As a reminder, we'll need the 1inch API key, see here to find out how to get it. And let's look at a few examples to see how to interact with 1inch API to get familiar with it.
- Things to know:
- The Gnosis Chain number is: 100
- The USDT token Gnosis address is: 0x4ECaBa5870353805a9F068101A40E0f32ed605C6
- The USDC token Gnosis address is: 0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83
- You can find more token addresses, for example, here: https://www.coingecko.com/
- Convert Ethereum addresses with Web3.to_checksum_address instruction when using the Web3.py library because Ethereum addresses are case sensitives. Ex: Web3.to_checksum_address("0x4ecaba5870353805a9f068101a40e0f32ed605c6")
Connect to the previously created wallet
The first step before interacting with the 1inch API is to connect to our previously created wallet.
- Connect to the Python virtual space:
john@desktop:~$ source .web3/bin/activate
(.web3) john@desktop:~$ python3
- Import the Web3 library then the private key:
>>> from web3 import Web3
>>> private_key = "0xPRIVATE_KEY"
- Show the wallet address:
>>> from eth_account import Account
>>> from eth_account.signers.local import LocalAccount
>>> account: LocalAccount = Account.from_key(private_key)
>>> print(f"The wallet address is: {account.address}")
The wallet address is: 0xWALLET_ADDRESS
Get balance
1inch API
We can use the 1inch API to check our wallet balance. Let's see how to do that.
- Import requests module which allow to send HTTP requests using Python:
>>> import requests
- Set variables:
>>> chainId = 100 #It's the Gnosis chain number, use 1 for ethereum
>>> wallet_address = account.address
>>> url = f'https://api.1inch.dev/balance/v1.2/{chainId}/balances/{wallet_address}'
>>> api_key = "1INCH_API_KEY"
- Show formated url:
>>> url
https://api.1inch.dev/balance/v1.2/100/balances/0xWALLET_ADDRESS
- Ask balance information to the 1inch API:
>>> response = requests.get(url, headers={'Authorization': f'Bearer {api_key}'})
- Check request response:
>>> response.status_code #200 means good
200
- Show the balance of every token:
>>> for token, balance in response.json().items():
... print(f"{token}: {balance}")
...
[…]
0xc38e84bcc2d2693dd77d89f2b86a83e7fe98afa5: 0
0xaf204776c7245bf4147c2612bf6e5972ee483701: 0
0xce11e14225575945b8e6dc0d4f2dd4c570f79d9f: 0
- Show non null balance only:
>>> for token, balance in response.json().items():
... if balance != "0":
... print(f"{token}: {balance}")
...
0xddafbb505ad214d7b80b1f830fccc89b60fb7a83: 1100000 #USDC token
0x4ecaba5870353805a9f068101a40e0f32ed605c6: 210038 #USDT token
0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee: 550838582408935942 #xDai token
Alternative
The 1inch API doesn't list all tokens, but we can request the balance of a specific token directly with the Web3.py library.
>>> import json
>>> from web3 import Web3
>>> wallet_address = "0xWALLET_ADDRESS"
>>> w3 = Web3(Web3.HTTPProvider("https://gnosis.drpc.org"))
>>> usdc_token = Web3.to_checksum_address("0x4ecaba5870353805a9f068101a40e0f32ed605c6")
>>> token_t_abi = json.loads('[{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]')
>>> token = w3.eth.contract(address=usdc_token, abi=token_t_abi)
>>> token.functions.balanceOf(wallet_address).call()
50286 #I'm poor :(
- To obtain the balance of native tokens (Note: xDai for Gnosis):
>>> w3.eth.get_balance(wallet_address)
550838582408935942
Get token value
1inch API can give us the token value, let's see how to do that.
- Import requests module which allow to send HTTP requests using Python:
>>> import requests
- Set variables:
>>> chainId = 100 #It's the Gnosis chain number, use 1 for ethereum
>>> url = f'https://api.1inch.dev/price/v1.1/{chainId}'
>>> api_key = "1INCH_API_KEY"
>>> usdt_token = "0x4ECaBa5870353805a9F068101A40E0f32ed605C6"
- Get USDT value:
>>> response = requests.post(url, headers={'Authorization': f'Bearer {api_key}'}, json={"tokens": f'{usdt_token}'})
>>> response.json()
{'0x4ecaba5870353805a9f068101a40e0f32ed605c6': '1001591626048481422'}
- From here we can convert our xDAI value to USDT:
>>> 550838582408935942/1001591626048481422
0.5499632465799719
Swap USDT to USDC
Now that we're comfortable with the interaction between our wallet and the 1inch API, we can move on to the next step, which is the objective of this tutorial: swapping one token to another. To make the things simple, we're going to swap a small quantity of USDT tokens for USDC.
Prerequisites
- Import requests modules and Web3 library:
>>> import requests
>>> from web3 import Web3
- Connect to a Gnosis node and check connection:
2024.06 Update: I recently had an error: "too many arguments, want at most 1" when using the estimate_gas function, resolved by replacing the provider (see https://chainlist.org/chain/100 for others alternatives)
>>> w3 = Web3(Web3.HTTPProvider("https://gnosis.drpc.org")) #specify a gnosis node to connect to, see https://docs.gnosischain.com/tools/rpc/ for more
>>> w3.is_connected() #Check if well connected to the node, should return True
- Set variables:
>>> chainId = 100 #It's the Gnosis chain number, use 1 for ethereum, 137 for Polygon etc…
>>> api_key = "1INCH_API_KEY"
>>> wallet_address = "0xWALLET_ADDRESS"
>>> private_key = "0xPRIVATE_KEY"
>>> usdt_token = "0x4ECaBa5870353805a9F068101A40E0f32ed605C6"
>>> usdc_token = "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83"
>>> headers = { "Authorization": f'Bearer {api_key}', "accept": "application/json" }
Swap Parameters
- Define swap parameters:
>>> swapParams = {
... "src": "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", #USDT
... "dst": "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83", #USDC
... "amount": "10000", #0.01$
... "from": wallet_address,
... "slippage": 1,
... "disableEstimate": False,
... "allowPartialFill": False
... }
Token Allowance
Pre-treatment
We need to allow the 1inch router to access to the tokens within our wallet we want to swap. To do so we will send a http request with token allowance values to the 1inch API and it will send us back a data value to sign.
>>> url = f'https://api.1inch.dev/swap/v5.2/{chainId}/approve/transaction?tokenAddress={swapParams["src"]}&amount={swapParams["amount"]}'
>>> url
'https://api.1inch.dev/swap/v5.2/100/approve/transaction?tokenAddress=0x4ECaBa5870353805a9F068101A40E0f32ed605C6&amount=10000'
>>> response = requests.get(url, headers=headers)
>>> response.json()
{'data': '0x095ea7b30000000000000000000000001111111263eec25477b68fb85ef929f73d9955120000000000000000000000000000000000000000000000000000000000001620', 'gasPrice': '50041321313', 'to': '0x4ecaba5870353805a9f068101a40e0f32ed605c6', 'value': '0'}
- Reformat the transaction:
We need to reformat values sent because it's unusable in its current state.
>>> transaction = response.json()
>>> up = {'to': Web3.to_checksum_address(swapParams["src"]), 'from': wallet_address, 'chainId': chainId, 'value': int(transaction["value"]), 'maxFeePerGas': int(transaction["gasPrice"]), 'maxPriorityFeePerGas': int(transaction["gasPrice"]), 'nonce': w3.eth.get_transaction_count(wallet_address)}
>>> transaction.update(up)
>>> transaction.pop('gasPrice') #remove gasPrice value
Add estimate gas
>>> w3.eth.estimate_gas(transaction) #show gas value
51759
>>> up = {'gas': w3.eth.estimate_gas(transaction)}
>>> transaction.update(up) #add gas value inside transaction
Sign and send transaction
>>> signed_transaction = w3.eth.account.sign_transaction(transaction, private_key) #sign the transaction with the private key
>>> signed_transaction["raw_transaction"] #show rawTransaction
HexBytes('0x02b2a3acd005afb5d0c879683d6c49d321c78b7afb706618b12bfacf9c77521344679d7e0bfdf715908417d8ebe2eac74239f232f5ba4f3e2a32c2c80a493a03ed4b5cb68511a69116c10b3f48b166d219b469889652c61f2d45a2be4282b18b044c6976753796a50c17892902f44f86ed8e0d56fc706d7dbf2706c02f03517a4213e846dfa6028c5c03ee1d19b3838280326398f435331c457452a218eb8efa8aa064e3737750353a5604388c1c674ef0c8aa0c')
>>> payload = { "rawTransaction": signed_transaction["raw_transaction"].hex()} #build the payload to send
>>> url = f'https://api.1inch.dev/tx-gateway/v1.1/{chainId}/broadcast' #Format the url
>>> response = requests.post(url, json=payload, headers=headers) #Send payload to API
>>> response.json() #show Hash transaction
{'transactionHash': '0x9f88569114b0fee6ff2d1499047cb7790fac5b1c23a063eb1a48594f64bdbbc6'}
Check and verify
Gnosisscan
- Copy and paste the transaction hash to https://gnosisscan.io/:

- And look for an Approve transaction:

Revoke.cash
- We can also check from the https://revoke.cash/ website:

1inch API
- We can also use the 1inch API:
>>> url = f'https://api.1inch.dev/swap/v5.2/{chainId}/approve/allowance?tokenAddress={swapParams["src"]}&walletAddress={wallet_address}'
>>> response = requests.get(url, headers={'Authorization': f'Bearer {api_key}'})
>>> response.json()
>>> {'allowance': '10000'}
Do the Swap
Pre-treatment
First, we are setting up the url with the swap parameters and request the swap data from the 1inch API.
>>> url = f'https://api.1inch.dev/swap/v5.2/{chainId}/swap?src={swapParams["src"]}&dst={swapParams["dst"]}&amount={swapParams["amount"]}&from={wallet_address}&slippage={swapParams["slippage"]}&disableEstimate={swapParams["disableEstimate"]}&allowPartialFill={swapParams["allowPartialFill"]}'
>>> response = requests.get(url, headers=headers)
>>> response.json() #show response
{'toAmount': '10000', 'tx': {'from': '0xWALLET_ADDRESS', 'to': '0x1111111254eeb25477b68fb85ed929f73a960582', 'data': '0x0502b1c5000000000000000000000000ddafbb505ad214d7b80b1f830fccc89b60fb7a83000000000000000000000000000000000000000000000000000000000000162000000000000000000000000000000000000000000000000000000000000026ac0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000180000000000000003c6c004074c2eff722017ad7c142476f545a051084da2c428b1bcac8', 'value': '0', 'gas': 147876, 'gasPrice': '12282946363'}}
- Reformat the transaction:
>>> raw = response.json()
>>> transaction = {'to': Web3.to_checksum_address(raw.get('tx', None)["to"]), 'from': Web3.to_checksum_address(raw.get('tx', None)["from"]), 'chainId': chainId, 'maxFeePerGas': int(raw.get('tx', None)["gasPrice"]), 'maxPriorityFeePerGas': int(raw.get('tx', None)["gasPrice"]), 'nonce': w3.eth.get_transaction_count(wallet_address), 'data': raw.get('tx', None)["data"]}
Add estimate gas
>>> up = {'gas': w3.eth.estimate_gas(transaction), 'value': 0}
>>> transaction.update(up)
Sign and send transaction
>>> signed_transaction = w3.eth.account.sign_transaction(transaction, private_key) #sign the transaction with the private key
>>> payload = { "rawTransaction": signed_transaction["raw_transaction"].hex()} #build the payload to send
>>> response = requests.post(f'https://api.1inch.dev/tx-gateway/v1.1/{chainId}/broadcast', json=payload, headers=headers) #send the transaction
>>> response.json() #show Hash transaction
{'transactionHash': '0xbabdab5472fd9f8458cb37bea01fa9005a64e481f3009e9dce8bcaa9b98aa829'}
Gnosisscan
- Here again, we can go to https://gnosisscan.io/ to check that the transaction has been completed:
