Coins: 22,875
Exchanges: 1,522
Market Cap: $2.588T 1.7%
24h Vol: $72.436B
Fear & Greed: Fear (37)
Premium
TABLE OF CONTENTS

Developers’ Guide: How to Build a Crypto Wallet Using Python

Onchain
REST
Demo
Python
Wallet Crypto Ethereum Cryptocurrency Python
4.4
| by
Rollend Xavier
|
Edited by
Ru Jun Ang
-
Make us preferred on Google

In the dynamic world of cryptocurrencies, the ability to interact with blockchain networks is of utmost importance. One of the key components facilitating this interaction is the crypto wallet.

In this tutorial, I will be demonstrating how to build a basic crypto wallet with Python, designed to provide a simple and user-friendly interface for interacting with the Ethereum network. We’ll be using the Ethereum network for this demo due to its popularity and wide usage in the crypto space. Our wallet will be ERC-20 token-compatible, a standard for most projects built on Ethereum. To keep testing free and safe, the wallet connects to Sepolia, Ethereum’s test network, by default.

How to build a crypto wallet - Python guide for developers | CoinGecko API

Let’s first review the key functionalities of our crypto wallet.

Level of Functionality

We’ll kickstart the process by determining our crypto wallet’s key functionalities:

  • Generating and Storing Keys: The wallet can generate a new pair of public and private keys. The public key, also known as the wallet address, is used to receive funds, while the private key is used to sign transactions.
  • Displaying Balance: The wallet can display the user’s balance in Ether, or in any ERC-20 token based on the token’s contract address.
  • Sending and Receiving Transactions: The wallet can send Ether to other Ethereum addresses, and anyone can send funds to its address. Transaction details are signed with the user’s private key and sent via the Infura API. The transaction hash, used to track the transaction, is returned. Test Ether (for test networks) or real Ether (for the Ethereum mainnet) is needed to fully test this functionality.
  • Viewing Market Chart: The application can fetch and display the market chart data for a specific contract in USD. It uses CoinGecko API to get the market chart data.

Disclaimer: This is a basic wallet and does not have advanced features such as staking, swapping, or interacting with decentralized applications (dApps). It is a learning project and should not be used to hold real funds.

The wallet will use web3.py, a Python library that allows interaction with the Ethereum blockchain through JSON-RPC, and the Infura API, a service that provides access to Ethereum nodes, to connect to the network and perform transactions.

The front-end of the wallet will run on a web browser and will use HTML, CSS, and JavaScript to create the interface and functionality. The front-end will communicate with the back-end, which will be written in Python, using Flask, a web framework that allows building web applications.


Prerequisites and Installing the Required Python Libraries

Before building, there are a few prerequisites that you need to have:

  • Python: Python 3.9 or higher should be installed on your system, as we will be using Python to write our back-end code. You can download it from the official Python website.
  • Flask: Flask is a lightweight web framework for Python, and will be used to create our web application. You can install Flask using pip, which is a package manager for Python.
  • Web3.py: Web3.py is a Python library for interacting with Ethereum. It’s a crucial part of our project as it allows us to generate accounts and interact with the Ethereum blockchain. This guide uses web3.py version 7. You can install web3.py using pip.
  • Infura Account: Infura is a service that provides access to the Ethereum network. You will need to create an Infura account to get your Infura API key.
  • Sepolia Test ETH: To test sending transactions without spending real funds, get free Sepolia ETH from a faucet, such as the Google Cloud Sepolia faucet.
  • CoinGecko API: We will be using the CoinGecko API to fetch the market chart data for cryptocurrencies. The CoinGecko API has a free Demo plan accessible to all users with a 100 calls/min rate limit and a monthly cap of 10,000 calls. Sign up for a CoinGecko account and apply for the Demo plan to generate your complimentary API key.
  • Text Editor or IDE: You will need a text editor or an Integrated Development Environment (IDE) to write your code. Some popular choices include Visual Studio Code, PyCharm, and Sublime Text.
💡 Prefer less setup? You can call the CoinGecko endpoint in this guide with CoinGecko’s official Python SDK or TypeScript SDK instead of raw HTTP requests. The SDK handles the base URL, headers, and authentication for you, so there is less boilerplate to write.

While not mandatory, having a basic understanding of how cryptocurrencies and the Ethereum blockchain work will help understand the code and the workings of the crypto wallet.

Before running the code, you also need to install the necessary Python libraries. Create a project folder and add a requirements.txt file with the following contents:

1
2
3
4
flask>=3.0
web3>=7.0
requests>=2.31
python-dotenv>=1.0

Then open your terminal or command prompt in the project folder and run:

pip install -r requirements.txt

💡Pro-tip: On macOS and Linux, you might need to use python3 and pip3 instead of python and pip. Installing the libraries inside a virtual environment (python3 -m venv .venv) keeps them separate from your other projects.

Next, store your keys in a .env file in the same folder, instead of hardcoding them in your code. The app reads them with python-dotenv. The repository includes this template as env.example:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Copy this file to .env and fill in your keys: cp env.example .env

# Flask session signing key: any long random string
FLASK_SECRET_KEY=replace-with-a-long-random-string

# Infura API key (https://www.infura.io/). Used to connect to Ethereum Sepolia.
INFURA_API_KEY=your_infura_api_key

# Optional: any Ethereum JSON-RPC URL. Overrides INFURA_API_KEY when set.
# ETH_RPC_URL=https://sepolia.infura.io/v3/your_infura_api_key

# CoinGecko Demo API key (https://www.coingecko.com/en/api/pricing)
COINGECKO_DEMO_API_KEY=your_coingecko_demo_api_key

💡Pro-tip: Never commit your .env file to GitHub or share it. It contains your API keys.


How to Build a Crypto Wallet on Ethereum

Developers can build a crypto wallet on Ethereum by using Python for both the back-end (integration with web3 libraries for blockchain interaction) and front-end (Flask, for the user interface). Here is an illustrative example of the implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import json
import os
from decimal import Decimal, InvalidOperation

import requests
from dotenv import load_dotenv
from eth_account import Account
from flask import Flask, jsonify, render_template, request, session
from web3 import Web3
from web3.exceptions import BadFunctionCallOutput, ContractLogicError, Web3RPCError

load_dotenv()

app = Flask(__name__)
app.config["SECRET_KEY"] = os.environ["FLASK_SECRET_KEY"]

# Sepolia testnet by default, so you can test with free test ETH.
# Set ETH_RPC_URL to use another network or RPC provider.
rpc_url = os.environ.get("ETH_RPC_URL") or f"https://sepolia.infura.io/v3/{os.environ['INFURA_API_KEY']}"
web3 = Web3(Web3.HTTPProvider(rpc_url))

COINGECKO_API_KEY = os.environ["COINGECKO_DEMO_API_KEY"]
COINGECKO_BASE_URL = "https://api.coingecko.com/api/v3"

with open("erc20_abi.json") as f:
    erc20_abi = json.load(f)

# Private keys stay in server memory, never in the session cookie.
# Flask's default session is signed but not encrypted, so the browser can read it.
private_keys = {}


@app.route("/new_account", methods=["GET"])
def new_account():
    account = Account.create()
    private_key = Web3.to_hex(account.key)
    private_keys[account.address] = private_key
    session["address"] = account.address
    # The private key is returned once so you can back it up. Never share it.
    return jsonify({"address": account.address, "privateKey": private_key})


@app.route("/balance", methods=["GET"])
@app.route("/balance/<contract_address>", methods=["GET"])
def get_balance(contract_address=None):
    address = session.get("address")
    if not address:
        return jsonify({"error": "Create an account first."}), 400

    # No contract address: return the account's ETH balance
    if not contract_address:
        balance_wei = web3.eth.get_balance(address)
        return jsonify({"symbol": "ETH", "balance": str(Web3.from_wei(balance_wei, "ether"))})

    if not Web3.is_address(contract_address):
        return jsonify({"error": "Invalid contract address."}), 400

    contract = web3.eth.contract(address=Web3.to_checksum_address(contract_address), abi=erc20_abi)
    try:
        raw_balance = contract.functions.balanceOf(address).call()
        decimals = contract.functions.decimals().call()
        symbol = contract.functions.symbol().call()
    except (BadFunctionCallOutput, ContractLogicError):
        return jsonify({"error": "No ERC-20 token found at this address on the connected network."}), 400

    balance = Decimal(raw_balance) / (Decimal(10) ** decimals)
    return jsonify({"symbol": symbol, "balance": str(balance), "raw_balance": str(raw_balance)})


@app.route("/send_transaction", methods=["POST"])
def send_transaction():
    address = session.get("address")
    if address not in private_keys:
        return jsonify({"error": "Create an account first."}), 400

    data = request.get_json()
    to_address = data.get("to", "")
    if not Web3.is_address(to_address):
        return jsonify({"error": "Invalid recipient address."}), 400
    try:
        value = Web3.to_wei(Decimal(str(data.get("amount"))), "ether")
    except (InvalidOperation, ValueError):
        return jsonify({"error": "Invalid amount."}), 400

    try:
        txn = {
            "from": address,
            "to": Web3.to_checksum_address(to_address),
            "value": value,
            "nonce": web3.eth.get_transaction_count(address, "pending"),
            "chainId": web3.eth.chain_id,
        }
        # EIP-1559 fees: a tip for the validator plus room for the base fee to rise
        priority_fee = web3.eth.max_priority_fee
        base_fee = web3.eth.get_block("latest")["baseFeePerGas"]
        txn["maxPriorityFeePerGas"] = priority_fee
        txn["maxFeePerGas"] = 2 * base_fee + priority_fee
        txn["gas"] = web3.eth.estimate_gas(txn)

        signed_txn = Account.sign_transaction(txn, private_keys[address])
        txn_hash = web3.eth.send_raw_transaction(signed_txn.raw_transaction)
    except (Web3RPCError, ValueError) as e:
        # For example, insufficient funds for gas and value
        return jsonify({"error": str(e)}), 400

    return jsonify({"transaction_hash": Web3.to_hex(txn_hash)})


@app.route("/market_chart/<contract_address>/<days>", methods=["GET"])
def get_market_chart(contract_address, days):
    response = requests.get(
        f"{COINGECKO_BASE_URL}/coins/ethereum/contract/{contract_address}/market_chart",
        params={"vs_currency": "usd", "days": days},
        headers={"x-cg-demo-api-key": COINGECKO_API_KEY},
        timeout=10,
    )
    # Or use the official CoinGecko Python SDK (pip install coingecko-sdk):
    # from coingecko_sdk import Coingecko
    # client = Coingecko(demo_api_key=COINGECKO_API_KEY, environment="demo")
    # market_chart = client.coins.contract.market_chart.get(
    #     contract_address, id="ethereum", vs_currency="usd", days=days
    # ).to_dict()
    if response.status_code != 200:
        return jsonify({"error": response.json()}), response.status_code
    return jsonify(response.json())


@app.route("/")
def home():
    return render_template("index.html")


if __name__ == "__main__":
    app.run(debug=True, port=int(os.environ.get("PORT", 5000)))

This Flask application has four routes:

  1. /new_account: Generates a new Ethereum account and returns the address and private key. The private key is kept in server memory, not in the browser session cookie, because Flask’s default session is signed but not encrypted.
  2. /balance/<contract_address>: Fetches the balance of the newly created account for the ERC-20 token at contract_address using the Web3 package, adjusted for the token’s decimals. Without a contract address (/balance), it returns the account’s ETH balance.
  3. /send_transaction: This endpoint allows the wallet to send Ether to other Ethereum addresses. It takes the recipient’s address and the amount to be sent as input, builds an EIP-1559 transaction with current network fees, signs it with the user’s private key and sends it via the Infura API. The transaction hash is returned, which can be used to track the transaction on the Ethereum network.
  4. /market_chart/<contract_address>/<days>: Fetches the market chart data for the given contract address over the specified number of days using the CoinGecko API’s Coin Historical Chart Data by Token Address endpoint and returns it. The contract_address is the address of the ERC-20 contract on Ethereum mainnet and days is the number of past days to fetch the market chart data. The Demo API key is sent in the x-cg-demo-api-key header.

💡Pro-tip: In Ethereum, addresses returned by this package are checksum addresses, which contain a mix of uppercase and lowercase letters. The checksum validates the address, and web3.py rejects mixed-case addresses with an incorrect checksum. The app converts every address you enter with Web3.to_checksum_address(), so you can paste addresses in any case.

You can run this application locally by executing the Python script, and accessing the routes in your web browser or using a tool like curl or Postman.

Remember to fill in your Infura API key, CoinGecko Demo API key and a random Flask secret key in your .env file. Always remember to keep your private keys and API keys secure and never share them with anyone!

Referencing the HTML and ERC-20 Code

The code for this project is quite extensive, involving both HTML for the user interface and ERC-20 token interactions on the Ethereum blockchain. For a detailed view of the code, please refer to the provided Github repository.

The repository contains all the necessary files and instructions to understand the workings of the project. It includes the HTML code for the user interface (templates/index.html), the ERC-20 ABI (erc20_abi.json) and the Python code for interacting with ERC-20 tokens using the Web3 library. The repository serves as a comprehensive guide to understanding and running the project.

Run the Wallet in Web Browser

Let’s run our wallet in the browser, following these steps:

  1. Save the code in a file named app.py, next to the templates/index.html and erc20_abi.json files from the repository.
  2. Open your terminal or command prompt.
  3. Navigate to the directory where you saved app.py.
  4. Run the file by typing the following command and pressing enter: python app.py
  5. After running the command, you should see an output indicating that your Flask application is running. You can then open your web browser and navigate to http://localhost:5000 to interact with your application. On macOS, if port 5000 is already used by AirPlay Receiver, run PORT=5001 python app.py and open http://localhost:5001 instead.

The above program is tested on Sepolia with 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238, the contract address for USDC on Sepolia. The balance displayed is 0, indicating the newly created account doesn’t hold any USDC. The market chart is tested with 0x6B175474E89094C44Da98b954EedeAC495271d0F, the Ethereum mainnet contract address for DAI Stablecoin. The market chart data is displayed using CoinGecko API and represents the price history of DAI in USD.

For transactions, the user can enter the recipient’s address and the amount of Ether to be sent. When the Send Transaction button is clicked, a POST request is made to the /send_transaction endpoint with the recipient’s address and the amount as JSON data. Fund the new address with Sepolia ETH from a faucet first, then track the returned transaction hash on Sepolia Etherscan.


Advanced Crypto Wallet Functionality and Further Considerations

While this guide covers only basic crypto wallet development, developers who might want to expand wallet functionalities can consider the following:

  • Token Swapping: Implement functionality to swap one type of token for another. This could involve integrating with a decentralized exchange protocol like Uniswap or SushiSwap.
  • Staking and Yield Farming: Allow users to stake their tokens or provide liquidity to earn rewards. This would involve interacting with various DeFi protocols.
  • NFT Support: Expand the wallet to support Non-Fungible Tokens (NFTs). This would allow users to hold and transfer unique digital assets.
  • Portfolio Value and PnL: Show every token the wallet holds with its USD value, plus trades, transfers and profit and loss, using CoinGecko API’s onchain wallet endpoints (Analyst plan and above), instead of calling each token contract one by one.

Additionally, another use-case would be to build a crypto dashboard with functionalities like:

  • Crypto Price Alerts: Notify the user when the price of a crypto asset reaches a certain level and display it on the dashboard using the /simple/price endpoint, or receive a push notification when a top-100 coin makes a significant price move with CoinGecko webhooks (Basic plan and above).
  • Crypto News and Trends: Display the latest news, trending coins, and popular categories from the crypto space on the dashboard using the /news (Analyst plan and above), /search/trending and /coins/categories endpoints.
  • Crypto Market Data: Provide the user with various market data such as market cap, volume, dominance, and the Crypto Fear & Greed Index on the dashboard using the /global and /coins/markets endpoints.

You can also use a library like Plotly or Dash to create interactive charts and graphs for your dashboard.

Remember, developing advanced features requires a deep understanding of Ethereum, smart contracts, and the various protocols you’ll be interacting with. Always prioritize the security of your users’ funds and adhere to best practices in the blockchain industry.


Conclusion

Building a basic crypto wallet for the Ethereum network using the Web3 Python library and CoinGecko API is a great way to understand and analyze how blockchain transactions work. This wallet can serve as a foundation for more complex applications, such as wallets with staking or swapping features, support multiple networks, or display NFT data.

Crypto developers and projects who want access to more API endpoints and greater data depth may consider subscribing to the CoinGecko Analyst API.

Get Richer Crypto Data API


Interested in more API resources? This extensive DEX Data API guide will walk you through how to get market data for a liquidity pool, access onchain data for DEX-traded tokens and more.

CoinGecko's Content Editorial Guidelines
CoinGecko’s content aims to demystify the crypto industry. While certain posts you see may be sponsored, we strive to uphold the highest standards of editorial quality and integrity, and do not publish any content that has not been vetted by our editors.
Learn more
Want to be the first to know about upcoming airdrops?
Subscribe to the CoinGecko Daily Newsletter!
Join 600,000+ crypto enthusiasts, traders, and degens in getting the latest crypto news, articles, videos, and reports by subscribing to our FREE newsletter.
Tell us how much you like this article!
Vote count: 28
Rollend Xavier
Rollend Xavier
Rollend is a Microsoft Certified Cloud Architect with 16 years of experience. He is the author of the book “Automate Your Life: Streamline Your Daily Tasks with Python: 30 Powerful Ways to Streamline Your Daily Tasks with Python”. Follow the author on Twitter @RollendXavier

Related Articles

Select Currency
Suggested Currencies
USD
US Dollar
IDR
Indonesian Rupiah
TWD
New Taiwan Dollar
EUR
Euro
KRW
South Korean Won
JPY
Japanese Yen
RUB
Russian Ruble
CNY
Chinese Yuan
Fiat Currencies
AED
United Arab Emirates Dirham
ARS
Argentine Peso
AUD
Australian Dollar
BDT
Bangladeshi Taka
BHD
Bahraini Dinar
BMD
Bermudian Dollar
BRL
Brazil Real
CAD
Canadian Dollar
CHF
Swiss Franc
CLP
Chilean Peso
CZK
Czech Koruna
DKK
Danish Krone
GBP
British Pound Sterling
GEL
Georgian Lari
HKD
Hong Kong Dollar
HUF
Hungarian Forint
ILS
Israeli New Shekel
INR
Indian Rupee
KWD
Kuwaiti Dinar
LKR
Sri Lankan Rupee
MMK
Burmese Kyat
MXN
Mexican Peso
MYR
Malaysian Ringgit
NGN
Nigerian Naira
NOK
Norwegian Krone
NZD
New Zealand Dollar
PHP
Philippine Peso
PKR
Pakistani Rupee
PLN
Polish Zloty
SAR
Saudi Riyal
SEK
Swedish Krona
SGD
Singapore Dollar
THB
Thai Baht
TRY
Turkish Lira
UAH
Ukrainian hryvnia
VND
Vietnamese đồng
ZAR
South African Rand
XDR
IMF Special Drawing Rights
Cryptocurrencies
BTC
Bitcoin
ETH
Ether
LTC
Litecoin
BCH
Bitcoin Cash
BNB
Binance Coin
EOS
EOS
XRP
XRP
XLM
Lumens
LINK
Chainlink
DOT
Polkadot
YFI
Yearn.finance
SOL
Solana
Bitcoin Units
BITS
Bits
SATS
Satoshi
Commodities
XAG
Silver - Troy Ounce
XAU
Gold - Troy Ounce
Select Language
Popular Languages
EN
English
RU
Русский
DE
Deutsch
PL
język polski
ES
Español
VI
Tiếng việt
FR
Français
PT-BR
Português
All Languages
AR
العربية
BG
български
CS
čeština
DA
dansk
EL
Ελληνικά
FI
suomen kieli
HE
עִבְרִית
HI
हिंदी
HR
hrvatski
HU
Magyar nyelv
ID
Bahasa Indonesia
IT
Italiano
JA
日本語
KO
한국어
LT
lietuvių kalba
NL
Nederlands
NO
norsk
RO
Limba română
SK
slovenský jazyk
SL
slovenski jezik
SV
Svenska
TH
ภาษาไทย
TR
Türkçe
UK
украї́нська мо́ва
ZH
简体中文
ZH-TW
繁體中文
Welcome to CoinGecko
Welcome back!
Login or Sign up in seconds
or
Sign in with . Not you?
Forgot your password?
Didn't receive confirmation instructions?
Resend confirmation instructions
Password must contain at least 8 characters including 1 uppercase letter, 1 lowercase letter, 1 number, and 1 special character
By continuing, you acknowledge that you've read and agree fully to our Terms of Service and Privacy Policy.
Get Price Alerts with CoinGecko App
Forgot your password?
You will receive an email with instructions on how to reset your password in a few minutes.
Resend confirmation instructions
You will receive an email with instructions for how to confirm your email address in a few minutes.
Get the CoinGecko app.
Scan this QR code to download the app now App QR Code Or check it out in the app stores
Add NFT
CoinGecko
Better on the app
Real-time price alerts and a faster, smoother experience.