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