本文介绍了如何使用Python、Flask和Web3.py构建一个REST API,以支持Compound Finance提供的去中心化金融借贷服务。文章从定义术语开始,阐明了Compound的各种功能,如mint、redeem及借贷,同时一步步引导读者搭建环境、获取支持的代币并创建API,最后提到部署建议。内容深入且实用,适合开发者学习和实践。
Compound Finance 是去中心化金融领域的早期先锋之一,作为首批 DeFi 贷款平台之一。Compound 提供了一种方式,可以对多种代币(ETH、BAT、DAI、REP、WBTC、USDC 等)赚取利息。Compound 通过将你的资产锁定在以太坊链上的智能合约中实现这一点,这个合约每当挖矿出一个区块时(大约每 15 秒一次)就会支付利息。
如果你是一个交易所或钱包,你可能希望为用户提供一种方式,让他们在不交易的情况下将资产锁定在 Compound 中以赚取利息。在本指南中,我们将使用 Python、Flask 和 Web3.py 开发一个简单的 REST API,支持所有可用的 Compound 资产以及大多数允许你赚取利息的 Compound 智能合约方法。
在我们开始构建 API 之前,有一些与 Compound 相关的术语需要理解。这不是一个全面的词汇表,如果你想要更深入的解释,可以查看 Compound 财务文档中的 cTokens,但这里是一些基本术语来帮助我们开始:
cTokens:
cTokens 基本上是你在交换其他代币(如 ETH、BAT、WBTC、REP、DAI、USDC、USDT 和 ZRX)时获得的代币。cTokens 根据市场利率支付利息。Compound 不断添加新的 cTokens,你可以 在这里查看列表。
cTokens 本身技术上属于 ERC-20 代币。
铸造:
铸造是你借出代币并获得 cTokens 的过程。你发送所需资产,得到 cTokens。例如,你可以将 USDC 发送到 Compound 智能合约,得到 cUSDC - 在撰写本文时,其 年化收益率(APY) 为 1.24%。
赎回:
赎回是你从帐户请求资产的方式,可以得到你原始资产的一部分或全部 + 利息。例如,如果我通过发送 500 USDC “铸造”了 500 cUSDC,并且它增长到了 550 cUSDC,我可以调用赎回所有 cUSDC 余额,收到 550 USDC。
注意:对于我们的目的,我们只会编写一个 API 来查看余额、查看利率、铸造(即供应)和赎回代币。此外,为了保持示例简单,我们并未使用真实的 cToken 交易价格。
借贷:
借贷的意思很直接,除了你只能在拥有 cTokens 时才能借贷。对于每种 cToken 类型,你可以借贷的抵押品最大百分比。例如,对于 cETH,你最多可以借贷 75% 的 cTokens。为了更实际地说明,假设你使用 100 ETH 铸造 cETH。那么你只能借出 75 ETH,或其在 Compound 协议/智能合约中支持的资产等值。一旦你借出资金,利息会在每个区块被挖掘后根据 Compound 设定的借贷利率开始累积。
还款:
这是最后一部分。这当然是你偿还借款的方式。你需要偿还借款的金额加上利息。要了解你在任何时刻欠多少钱,你需要询问 Compound 智能合约。
今天,我们将使用 Python 3 进行开发。因此,请确保你已安装该版本。首先创建一个目录来存放我们的工作:
$ mkdir compound-rest && cd compound-rest
现在,让我们设置一个轻量级的虚拟环境,以保持系统 Python 的干净:
$ python3 -m venv compound_env && source compound_env/bin/activate
如果你使用的是不同的 shell,例如 fish,你可能需要在上述例子中的 bin/activate
中添加一个扩展。请在这里查找你的扩展。到现在为止,你的终端大致应该如下所示:
最后一步是添加一个 requirements.txt
文件并安装所有所需的软件,在 compound-rest
目录中创建一个名为 requirements.txt
的新文件,并将以下内容放入其中:
flask
web3
requests
现在继续安装它们:
$ pip3 install -r requirements.txt
你需要自己安装并同步以太坊节点,或者使用 QuickNode 等服务来访问以太坊区块链。如果你想同步自己的节点,我们强烈推荐使用 EthHub 作为起点。
如果你想跳过所有这些,可以在 这里 创建一个免费的 QuickNode 账户并获取一个端点。创建端点后,复制 HTTP 提供者,如下图所示:
重要提示:请保存此 URL,我们稍后需要它。
接下来,让我们编写一个小脚本,从 Compound 获取你可以赚取利息的所有支持货币。他们有一个方便的端点可以使用:https://api.compound.finance/api/v2/ctoken
以下是启动的脚本,我将其放在 supported_tokens.py
中:
import requests
import pprint
res = requests.get("https://api.compound.finance/api/v2/ctoken")
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(res.json())
去运行它:
$ python3 supported_tokens.py
如果你看到包含 cToken
的一大堆输出 - 那你应该没问题。它应该看起来像这样:
我们将利用这个来确保我们的 REST API 动态支持新资产,而不必每次更新代码。供参考,如果你想查看我们使用的内容的官方文档,请查看 Compound 文档。
在继续之前,我们需要编写一个小脚本来生成以太坊钱包,包括私钥和公钥地址。如果你已有自己的钱包,可以使用那个,但我们不太建议这样。将以下内容放入名为 create_address.py
的文件中:
from web3.auto import w3
acct = w3.eth.account.create()
print("私钥 **务必保存**: {}".format(acct.privateKey.hex()))
print("ETH 地址: {}".format(acct.address))
保存私钥和地址,我们将在下一步中需要它们。
这就是我们应用程序的核心,所有的代码将放入我们早前创建的 compound-rest
文件夹中的 app.py
文件中:
import os
import json
import requests
from decimal import *
from flask import Flask, request
from web3 import Web3
app = Flask(__name__)
ETH_PROVIDER = os.environ.get('ETH_PROVIDER')
ETH_ACCT_KEY = os.environ.get('ETH_ACCT_KEY')
ETH_ADDRESS = os.environ.get('ETH_ADDR')
w3 = Web3(Web3.HTTPProvider(ETH_PROVIDER))
@app.route('/ctokens')
def ctokens():
"""
返回所有 cTokens:
- 符号
- Compound 上的符号
- 利率
"""
res = requests.get("https://api.compound.finance/api/v2/ctoken")
response = []
for t in res.json()['cToken']:
token = {
'symbol': t['underlying_symbol'],
'compound_symbol': t['symbol'],
'rate': '{}%'.format(round(Decimal(t['supply_rate']['value'])*100, 2))
}
response.append(token)
return ({'supported': response}, {'Content-Type': 'application/json'})
@app.route('/ctokens/<symbol>')
def ctokens_detail(symbol):
"""
返回特定 cToken 的基础资产:
- 符号
- 应计利息
- 本金
- 当前 cToken 余额
"""
symbol = symbol.upper()
tokens = requests.get("https://api.compound.finance/api/v2/ctoken")
balances = requests.get("https://api.compound.finance/api/v2/account?addresses[]={}".format(ETH_ADDRESS))
response = {
'principal': '0.0',
'current_balance': '0.0',
'interest_accrued': '0.0',
'ctoken_balance': '0.0'
}
for t in tokens.json()['cToken']:
if t['underlying_symbol'] == symbol:
response['symbol'] = t['underlying_symbol']
accts = balances.json()['accounts']
if len(accts) > 0:
for acct in accts[0]['tokens']:
if acct['symbol'] == t['symbol']:
abi_url = "https://raw.githubusercontent.com/compound-finance/compound-protocol/master/networks/mainnet-abi.json"
abi = requests.get(abi_url)
tokens = requests.get("https://api.compound.finance/api/v2/ctoken")
contract_address = [t['token_address'] for t in tokens.json()['cToken'] if t['underlying_symbol'] == symbol][0]
compound_token_contract = w3.eth.contract(abi=abi.json()["c{}".format(symbol)], address=Web3.toChecksumAddress(contract_address))
nonce = w3.eth.getTransactionCount(ETH_ADDRESS)
total = acct['safe_withdraw_amount_underlying']['value']
interest = acct['lifetime_supply_interest_accrued']['value']
response['principal'] = "{}".format(Decimal(total) - Decimal(interest))
response['current_balance'] = total
response['interest_accrued'] = interest
response['ctoken_balance'] = str(compound_token_contract.functions.balanceOf(ETH_ADDRESS).call())
return (response, {'Content-Type': 'application/json'})
@app.route('/ctokens/<symbol>/mint', methods=['POST'])
def ctokens_mint(symbol):
"""
根据以下内容铸造特定 cToken:
- 符号
- 数量
并返回:
- 符号
- 铸造的数量
- 交易 ID
"""
symbol = symbol.upper()
amt = request.form['amount']
abi_url = "https://raw.githubusercontent.com/compound-finance/compound-protocol/master/networks/mainnet-abi.json"
abi = requests.get(abi_url)
tokens = requests.get("https://api.compound.finance/api/v2/ctoken")
contract_address = [t['token_address'] for t in tokens.json()['cToken'] if t['underlying_symbol'] == symbol][0]
amount = w3.toWei(amt, 'ether')
compound_token_contract = w3.eth.contract(abi=abi.json()["c{}".format(symbol)], address=Web3.toChecksumAddress(contract_address))
nonce = w3.eth.getTransactionCount(ETH_ADDRESS)
mint_tx = compound_token_contract.functions.mint().buildTransaction({
'chainId': 1,
'gas': 500000,
'gasPrice': w3.toWei('20', 'gwei'),
'nonce': nonce,
'value': int(amount)
})
signed_txn = w3.eth.account.sign_transaction(mint_tx, ETH_ACCT_KEY)
try:
tx = w3.eth.sendRawTransaction(signed_txn.rawTransaction)
except ValueError as err:
return (json.loads(str(err).replace("'", '"')), 402, {'Content-Type': 'application/json'})
response = {
'symbol': symbol,
'amount': amt,
'tx_id': tx.hex()
}
return (response, {'Content-Type': 'application/json'})
@app.route('/ctokens/<symbol>/redeem', methods=['POST'])
def ctokens_redeem(symbol):
"""
根据以下内容赎回特定 cToken:
- 符号
- 数量
并返回:
- 符号
- 交易 ID
"""
symbol = symbol.upper()
amt = request.form['amount']
abi_url = "https://raw.githubusercontent.com/compound-finance/compound-protocol/master/networks/mainnet-abi.json"
abi = requests.get(abi_url)
tokens = requests.get("https://api.compound.finance/api/v2/ctoken")
contract_address = [t['token_address'] for t in tokens.json()['cToken'] if t['underlying_symbol'] == symbol][0]
compound_token_contract = w3.eth.contract(abi=abi.json()["c{}".format(symbol)], address=Web3.toChecksumAddress(contract_address))
nonce = w3.eth.getTransactionCount(ETH_ADDRESS)
redeem_tx = compound_token_contract.functions.redeem(int(amt)).buildTransaction({
'chainId': 1,
'gas': 500000,
'gasPrice': w3.toWei('20', 'gwei'),
'nonce': nonce
})
signed_txn = w3.eth.account.sign_transaction(redeem_tx, ETH_ACCT_KEY)
try:
tx = w3.eth.sendRawTransaction(signed_txn.rawTransaction)
except ValueError as err:
return (json.loads(str(err).replace("'", '"')), 402, {'Content-Type': 'application/json'})
response = {
'symbol': symbol,
'tx_id': tx.hex()
}
return (response, {'Content-Type': 'application/json'})
保存该文件。现在让我们运行服务器:
$ ETH_ADDR=<WALLET_ADDRESS> ETH_ACCT_KEY=<WALLET_PRIVATE_KEY> ETH_PROVIDER=<ETHEREUM_NODE_PROVIDER_HERE> FLASK_APP=app.py flask run
请记得用之前部分提供的适当值替换 <WALLET_ADDRESS>
、<WALLET_PRIVATE_KEY>
和 <ETHEREUM_NODE_PROVIDER_HERE>
中的部分。当你运行服务器后,请为该钱包提供你选择的资产,以及一些用于交易费用的 ETH,你就可以开始了。以下是对 localhost 的 cURL 请求的一些屏幕截图:
通过 API 列出所有支持的 Compound 资产:
在我们的指南中,我们不会将此应用程序部署到生产环境,但我们强烈建议 这个关于将 Flask 应用程序部署到生产的 Digital Ocean 教程 和 这个关于使用 Lets Encrypt 为应用程序提供安全性的教程,这是一个免费的 SSL 提供商。
请告诉我们 如果你有任何反馈或希望新主题。我们很乐意听到你的声音。
- 原文链接: quicknode.com/guides/def...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!