├── .env.secret ├── .env.shared ├── .gitignore ├── L402 ├── __init__.py ├── l402_api_chain.py └── requests_l402.py ├── README.md ├── __init__.py ├── all_tools ├── api_tools │ ├── __init__.py │ └── tools.py └── bitcoin_tools │ ├── __init__.py │ └── tools.py ├── connect.py ├── langchain_L402_agent.ipynb ├── lightning ├── __init__.py └── lightning.py ├── llm_bitcoin_tools.ipynb ├── main.py ├── protos ├── lightning.proto ├── lightning_pb2.py ├── lightning_pb2_grpc.py ├── router.proto ├── router_pb2.py └── router_pb2_grpc.py ├── requirements.txt └── utils.py /.env.secret: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | -------------------------------------------------------------------------------- /.env.shared: -------------------------------------------------------------------------------- 1 | MACAROON_PATH= 2 | CERT_PATH= 3 | LND_NODE_HOST= 4 | LND_NODE_PORT= 5 | TARGET_HOST= 6 | API_TOOL_DESCRIPTION= 7 | API_TOOL_NAME= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | langchain 2 | __pycache__ 3 | -------------------------------------------------------------------------------- /L402/__init__.py: -------------------------------------------------------------------------------- 1 | from .l402_api_chain import L402APIChain 2 | from .requests_l402 import RequestsL402Wrapper 3 | -------------------------------------------------------------------------------- /L402/l402_api_chain.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | from typing import Any, Dict, Optional 4 | from langchain.chains.api.prompt import API_RESPONSE_PROMPT, API_URL_PROMPT 5 | from langchain.chains import APIChain 6 | from langchain.prompts import BasePromptTemplate 7 | from langchain.base_language import BaseLanguageModel 8 | from langchain.chains.llm import LLMChain 9 | 10 | from .requests_l402 import RequestsL402Wrapper 11 | from .requests_l402 import ResponseTextWrapper 12 | 13 | from lightning import LightningNode 14 | 15 | class L402APIChain(APIChain): 16 | requests_wrapper: Any 17 | 18 | @classmethod 19 | def from_llm_and_api_docs( 20 | cls, 21 | llm: BaseLanguageModel, 22 | api_docs: str, 23 | headers: Optional[dict] = None, 24 | api_url_prompt: BasePromptTemplate = API_URL_PROMPT, 25 | api_response_prompt: BasePromptTemplate = API_RESPONSE_PROMPT, 26 | lightning_node = None, 27 | **kwargs: Any, 28 | ) -> APIChain: 29 | """Load chain from just an LLM and the api docs.""" 30 | 31 | requests_L402 = RequestsL402Wrapper(lightning_node, requests) 32 | lang_chain_request_L402 = ResponseTextWrapper( 33 | requests_wrapper=requests_L402, 34 | ) 35 | 36 | get_request_chain = LLMChain(llm=llm, prompt=api_url_prompt) 37 | get_answer_chain = LLMChain(llm=llm, prompt=api_response_prompt) 38 | 39 | return cls( 40 | api_request_chain=get_request_chain, 41 | api_answer_chain=get_answer_chain, 42 | requests_wrapper=lang_chain_request_L402, 43 | api_docs=api_docs, 44 | **kwargs, 45 | ) 46 | -------------------------------------------------------------------------------- /L402/requests_l402.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from typing import Any 4 | 5 | import requests 6 | import re 7 | 8 | L402_ERROR_CODE = 402 9 | 10 | AUTH_HEADER = "WWW-Authenticate" 11 | 12 | class RequestsL402Wrapper(object): 13 | """ 14 | """ 15 | 16 | def __init__(self, lnd_node, requests): 17 | self.lnd_node = lnd_node 18 | self.requests = requests 19 | 20 | def _L402_auth(self, response): 21 | auth_header = response.headers.get(AUTH_HEADER) 22 | 23 | # Extract macaroon value 24 | macaroon = re.search(r'macaroon="(.*?)"', auth_header).group(1) 25 | 26 | # Extract invoice value 27 | invoice = re.search(r'invoice="(.*?)"', auth_header).group(1) 28 | 29 | print("Paying invoice: ", invoice) 30 | 31 | pre_image = self.lnd_node.pay_invoice(invoice) 32 | 33 | print("Obtained preimage: ", pre_image) 34 | 35 | headers = { 36 | 'Authorization': 'LSAT {macaroon}:{pre_image}'.format( 37 | macaroon=macaroon, pre_image=pre_image, 38 | ), 39 | } 40 | 41 | print("Assembling final authorization header: ", headers) 42 | 43 | return headers 44 | 45 | def _L402(func): 46 | def wrapper(self, *args, **kwargs): 47 | requests_func = getattr(self.requests, func.__name__) 48 | 49 | response = requests_func(*args, **kwargs) 50 | 51 | if response.status_code != L402_ERROR_CODE: 52 | return response 53 | 54 | print("L402 error path={path}, attempting to pay invoice and retry request...".format(path=args[0])) 55 | 56 | L402_auth_header = self._L402_auth(response) 57 | 58 | kwargs.setdefault('headers', {}) 59 | kwargs['headers'].update(L402_auth_header) 60 | 61 | return requests_func(*args, **kwargs) 62 | return wrapper 63 | 64 | # TODO(roasbeef): should also be able to set the set of headers, etc 65 | 66 | @_L402 67 | def get(self, url, **kwargs): 68 | return 69 | 70 | @_L402 71 | def post(self, url, data=None, json=None, **kwargs): 72 | return 73 | 74 | @_L402 75 | def put(self, url, data=None, **kwargs): 76 | return 77 | 78 | @_L402 79 | def delete(self, url, **kwargs): 80 | return 81 | 82 | @_L402 83 | def head(self, url, **kwargs): 84 | return 85 | 86 | @_L402 87 | def patch(self, url, data=None, **kwargs): 88 | return 89 | 90 | class ResponseTextWrapper(BaseModel): 91 | requests_wrapper: Any 92 | 93 | @staticmethod 94 | def response_text(func): 95 | def wrapper(*args, **kwargs): 96 | response = func(*args, **kwargs) 97 | return response.text 98 | return wrapper 99 | 100 | @response_text 101 | def get(self, url, **kwargs): 102 | return self.requests_wrapper.get(url, **kwargs) 103 | 104 | @response_text 105 | def post(self, url, data=None, json=None, **kwargs): 106 | return self.requests_wrapper.post(url, data, json, **kwargs) 107 | 108 | @response_text 109 | def put(self, url, data=None, **kwargs): 110 | return self.requests_wrapper.put(url, data, **kwargs) 111 | 112 | @response_text 113 | def delete(self, url, **kwargs): 114 | return self.requests_wrapper.delete(url, **kwargs) 115 | 116 | @response_text 117 | def head(self, url, **kwargs): 118 | return self.requests_wrapper.head(url, **kwargs) 119 | 120 | @response_text 121 | def patch(self, url, data=None, **kwargs): 122 | return self.requests_wrapper.patch(url, data, **kwargs) 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LangChainBitcoin 2 | 3 | `LangChainBitcoin` is a suite of tools that enables `langchain` agents to 4 | directly interact with Bitcoin and also the Lightning Network. This package has 5 | two main features: 6 | 7 | * **LLM Agent BitcoinTools**: Using the newly available Open AP GPT-3/4 8 | function calls and the built in set of abstractions for tools in 9 | `langchain`, users can create agents that are capaable of holding Bitcoin 10 | balance (on-chain and on LN), sending/receiving Bitcoin on LN, and also 11 | generally interacting with a Lightning node (lnd). 12 | 13 | * **L402 HTTP API Traversal**: LangChainL402 is a Python project that enables 14 | users of the `requests` package to easily navigate APIs that require 15 | [L402](https://docs.lightning.engineering/the-lightning-network/l402) based 16 | authentication. This project also includes a LangChain APIChain compatible 17 | wrapper that enables LangChain agents to interact with APIs that require 18 | L402 for payment or authentication. This enables agents to access 19 | real-world resources behind a Lightning metered API. 20 | 21 | 22 | ## Features 23 | - Provides a wrapper around `requests` library to handle LSATs and Lightning 24 | payments. 25 | 26 | - Easily integrates with APIs requiring L402-based authentication. 27 | 28 | - Designed to operate seamlessly with LND (Lightning Network Daemon). 29 | 30 | - Enables LangChain Agents traverse APIs that require L402 authentication 31 | within an API Chain. 32 | 33 | - Generic set of Bitcoin tools giving agents the ability to hold and use the 34 | Internet's native currency. 35 | 36 | 37 | 38 | ## Installation 39 | 40 | To install the LangChainL402 project, you can clone the repository directly 41 | from GitHub: 42 | 43 | ```bash 44 | git clone https://github.com/lightninglabs/LangChainBitcoin.git 45 | cd LangChainBitcoin 46 | ``` 47 | 48 | Please ensure you have all the necessary Python dependencies installed. You can 49 | install them using pip: 50 | ``` 51 | pip install -r requirements.txt 52 | ``` 53 | 54 | ## Usage 55 | 56 | ### LLM Agent Bitcoin Tools 57 | 58 | Check out the contained Jupyter notebook for an interactive example you can 59 | run/remix: [LLM Bitcoin Tools](llm_bitcoin_tools.ipynb). 60 | 61 | ### L402 API Traversal 62 | 63 | Check out the contained Jupyter notebook for an interactive example you can 64 | run/remix: [LangChain402](langchain_L402_agent.ipynb). 65 | 66 | This project provides classes to handle Lightning payments (e.g., `LndNode`, 67 | `LightningNode`) and to manage HTTP requests with L402-based authentication 68 | (`RequestsL402Wrapper`, `ResponseTextWrapper`). 69 | 70 | First, initialize the `LndNode` with your Lightning node's details. Then, you 71 | can use the modified `L402APIChain` wrapper around the normal `APIChain` class 72 | to instantiate an L402-aware API Chain. If the server responds with a 402 73 | Payment Required status code, the library will automatically handle the payment 74 | and retry the request. 75 | 76 | Here is a basic example: 77 | ```python 78 | import requests 79 | 80 | from lightning import LndNode 81 | 82 | from l402_api_chain import L402APIChain 83 | from langchain_openai import OpenAI 84 | 85 | # Initialize LndNode 86 | Add node data to the .env.shared file 87 | 88 | # Add API data 89 | Specifics of the serivce to be used can be added to API_DOCS via utils.py 90 | 91 | # Instruction the AI agent 92 | Instructions can be delivered to the AI agent via agent_executor.invoke main.py 93 | ``` 94 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from lightning import LightningNode 2 | from lightning import LndNode 3 | from L402 import L402APIChain 4 | -------------------------------------------------------------------------------- /all_tools/api_tools/__init__.py: -------------------------------------------------------------------------------- 1 | from .tools import * 2 | -------------------------------------------------------------------------------- /all_tools/api_tools/tools.py: -------------------------------------------------------------------------------- 1 | from dotenv import dotenv_values 2 | from langchain.tools import StructuredTool 3 | from langchain.chains import APIChain 4 | 5 | config = dotenv_values(".env.shared") 6 | 7 | 8 | def api_tool_factory(api_chain: APIChain, name: str, description: str): 9 | api_tool = StructuredTool.from_function( 10 | func=lambda query: api_chain.invoke(query), 11 | name=name, 12 | description=description, 13 | return_direct=True, 14 | ) 15 | return api_tool 16 | -------------------------------------------------------------------------------- /all_tools/bitcoin_tools/__init__.py: -------------------------------------------------------------------------------- 1 | from .tools import LndTools 2 | -------------------------------------------------------------------------------- /all_tools/bitcoin_tools/tools.py: -------------------------------------------------------------------------------- 1 | from langchain.agents.agent_toolkits.base import BaseToolkit 2 | from langchain.tools import BaseTool, tool 3 | 4 | from protos import lightning_pb2 as ln 5 | from langchain.chains import APIChain 6 | from lightning import LndNode 7 | 8 | from typing import List 9 | 10 | 11 | class LndTools(BaseToolkit): 12 | class Config: 13 | arbitrary_types_allowed = True 14 | 15 | lnd_node: LndNode 16 | 17 | @classmethod 18 | def from_lnd_node(cls, lnd_node: LndNode) -> "LndTools": 19 | return cls(lnd_node=lnd_node) 20 | 21 | def _decode_invoice_tool(self): 22 | @tool 23 | def decode_invoice(invoice: str) -> ln.PayReq: 24 | """ 25 | Takes an encoded payment request (also called an invoice) string 26 | and attempts to decode it, returning a full description of the 27 | conditions encoded within the payment request. 28 | 29 | An example invoice in a JSON-like format looks like: 30 | { 31 | destination: , 32 | payment_hash: , 33 | num_satoshis: , 34 | timestamp: , 35 | expiry: , 36 | description: , 37 | description_hash: , 38 | fallback_addr: , 39 | cltv_expiry: , 40 | route_hints: , 41 | payment_addr: , 42 | num_msat: , 43 | features: , 44 | } 45 | 46 | This can be used to get more information about an invoice before 47 | trying to pay it. 48 | """ 49 | decoded_invoice = self.lnd_node.decode_invoice(invoice) 50 | return decoded_invoice 51 | 52 | return decode_invoice 53 | 54 | def _send_payment_tool(self): 55 | @tool 56 | def send_payment(invoice: str) -> ln.SendResponse: 57 | """ 58 | Can be used to make a payment to a valid Lightning invoice. 59 | Information about the payment is returned, such as the pre-image, 60 | the route it took, the amount of fees paid. 61 | 62 | An example of a JSON-like response is: 63 | { 64 | payment_error: , 65 | payment_preimage: , 66 | payment_route: , 67 | payment_hash: , 68 | } 69 | """ 70 | payment_resp = self.lnd_node.send_payment(invoice) 71 | return payment_resp 72 | 73 | return send_payment 74 | 75 | def _channel_balance_tool(self): 76 | @tool 77 | def channel_balance() -> ln.ChannelBalanceResponse: 78 | """ 79 | Returns the current off-chain balance of a node. This is the amount 80 | of satoshis channels, or the total amount the node has on the 81 | Lightning Network. 82 | 83 | An example of a JSON-like response is: 84 | 85 | { 86 | balance: , 87 | pending_open_balance: , 88 | local_balance: , 89 | remote_balance: , 90 | unsettled_local_balance: , 91 | unsettled_remote_balance: , 92 | pending_open_local_balance: , 93 | pending_open_remote_balance: , 94 | } 95 | 96 | """ 97 | return self.lnd_node.channel_balance() 98 | 99 | return channel_balance 100 | 101 | def _wallet_balance_tool(self): 102 | @tool 103 | def wallet_balance() -> ln.WalletBalanceResponse: 104 | """ 105 | Returns the current on-chain balance of a node. This is the amount 106 | of satoshis that a node has in non-channel UTXOs (unspent 107 | transaction outputs). 108 | 109 | An example JSON-like response is: 110 | 111 | { 112 | total_balance: , 113 | confirmed_balance: , 114 | unconfirmed_balance: , 115 | locked_balance: , 116 | reserved_balance_anchor_chan: , 117 | account_balance: , 118 | } 119 | """ 120 | return self.lnd_node.wallet_balance() 121 | 122 | return wallet_balance 123 | 124 | def _get_info_tool(self): 125 | @tool 126 | def get_info() -> ln.GetInfoResponse: 127 | """ 128 | Returns general information about an lnd node. This includes things 129 | like how many channels a node has, its public key, current height. 130 | 131 | An example of a JSON-like response is: 132 | 133 | { 134 | "version": , 135 | "commit_hash": , 136 | "identity_pubkey": , 137 | "alias": , 138 | "num_pending_channels": , 139 | "num_active_channels": , 140 | "num_inactive_channels": , 141 | "num_peers": , 142 | "block_height": , 143 | "block_hash": , 144 | "best_header_timestamp": , 145 | "synced_to_chain": , 146 | "synced_to_graph": , 147 | "testnet": , 148 | "chains": , 149 | "uris": , 150 | "features": , 151 | } 152 | """ 153 | return self.lnd_node.get_info() 154 | 155 | return get_info 156 | 157 | def _check_invoice_status(self): 158 | pass 159 | 160 | def _create_invoice(self): 161 | @tool 162 | def create_invoice(memo: str, value: int) -> ln.AddInvoiceResponse: 163 | """ 164 | Creates a new invoice on the Lightning Network. This in invoice can 165 | be used to receiveds funds on Lightning. 166 | 167 | An example of a JSON-like response is: 168 | { 169 | r_hash: , 170 | payment_request: , 171 | add_index: , 172 | payment_addr: , 173 | } 174 | 175 | """ 176 | return self.lnd_node.create_invoice(memo, value) 177 | 178 | return create_invoice 179 | 180 | def get_tools(self) -> List[BaseTool]: 181 | """Get the tools in the toolkit.""" 182 | 183 | tools = [] 184 | 185 | for attribute_name in dir(self): 186 | if attribute_name.endswith("_tool"): 187 | attribute = getattr(self, attribute_name) 188 | 189 | if attribute is None: 190 | continue 191 | 192 | tool_func = attribute() 193 | 194 | if tool_func is None: 195 | continue 196 | 197 | if callable(attribute): 198 | tools.append(attribute()) 199 | 200 | return tools 201 | -------------------------------------------------------------------------------- /connect.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | # Replace these variables with your actual values 4 | url = "https://cad9f3dc-360d-49d9-b9e3-5c868510e369-00-1zslnuhag6k0c.riker.replit.dev:8080/v1/getinfo" 5 | macaroon = "your_hex_encoded_macaroon" 6 | cert_path = "/path/to/your/tls.cert" 7 | 8 | # Prepare the headers with the macaroon 9 | headers = { 10 | "Grpc-Metadata-macaroon": macaroon, 11 | } 12 | 13 | # It's important to verify with the TLS certificate LND uses, which is usually named tls.cert 14 | response = requests.get(url, headers=headers, verify=cert_path) 15 | 16 | # Check the response 17 | if response.status_code == 200: 18 | print("Connection successful!") 19 | print("Node info:", response.json()) 20 | else: 21 | print("Failed to connect to the node. Status code:", response.status_code) 22 | print("Error message:", response.text) 23 | -------------------------------------------------------------------------------- /langchain_L402_agent.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "f713e7dd", 6 | "metadata": {}, 7 | "source": [ 8 | "# LangChain402: An L402-aware APIChain for LangChain Agents" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "dd225454", 14 | "metadata": {}, 15 | "source": [ 16 | "In this notebook, the L402 APIChain wrapper for LangChain agents. This enables LangChain agents to hit APIs that are gated behind an L402 reverse proxy. The [L402 protocol](https://docs.lightning.engineering/the-lightning-network/l402) is a protocol that finally leverages the forgotten `HTTP 402 Payment Required` error code to create a programmatic way to pay for content on on the web. The content may just be normal authentication, a blog post, or even a rich API that uses LN micro payments to implement a metered API. The L402 protocol enables AI Agents to _pay_ for API-based resources using the internet's native currency. " 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "id": "e028be83", 22 | "metadata": {}, 23 | "source": [ 24 | "We'll start by importing a class we can use to access lnd, and also the API chain wrapper: " 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 1, 30 | "id": "25d6bf90", 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "from lightning import LndNode\n", 35 | "from L402 import L402APIChain\n", 36 | "\n", 37 | "from langchain_openai import OpenAI" 38 | ] 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "id": "15af950d", 43 | "metadata": {}, 44 | "source": [ 45 | "In `langchain`, API chains work by consuming a description of an API, which is then passed along to a series of LLM chains to try to figure out how to call an API to answer a prompt/request. In this example, we'll create docs for a fake API running locally, which looks very similar to the popular OpenAI API structure: \n" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": 13, 51 | "id": "957d48d3", 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "API_DOCS ='''BASE URL: http://localhost:8085\n", 56 | "\n", 57 | "API Documentation\n", 58 | "The API endpoint /v1/models HTTP endpoint can be used to fetch the set of supported models. The response is text, the list of models separated by a comma\n", 59 | "\n", 60 | "Request:\n", 61 | "There are no query params for the API\n", 62 | "\n", 63 | "Response:\n", 64 | "A JSON object of the set of supported models, which looks similar to:\n", 65 | "{\n", 66 | " \"models\": [\n", 67 | " {\n", 68 | " \"id\": \"gpt9\",\n", 69 | " \"name\": \"GPT-9\",\n", 70 | " \"version\": \"1.0\"\n", 71 | " },\n", 72 | " {\n", 73 | " \"id\": \"gpt10\",\n", 74 | " \"name\": \"GPT-10\",\n", 75 | " \"version\": \"1.0\"\n", 76 | " },\n", 77 | " {\n", 78 | " \"id\": \"gptalphabeta\",\n", 79 | " \"name\": \"GPT-Alpha-Beta\",\n", 80 | " \"version\": \"1.0\"\n", 81 | " }\n", 82 | " ]\n", 83 | "}\n", 84 | "'''" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "id": "300dd818", 90 | "metadata": {}, 91 | "source": [ 92 | "We described the URL of the API, the endpoint available, and also gave it a few-shot example of what a response looks like. " 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "id": "11105cc8", 98 | "metadata": {}, 99 | "source": [ 100 | "Next, we'll initialize the API chain wrapper, and also an agent that can use the L402 API Chain traverse the API. Note that the agent doesn't need to know about L402 explicitly, instead the negotiation will happen in the background" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": 6, 106 | "id": "f519a455", 107 | "metadata": {}, 108 | "outputs": [], 109 | "source": [ 110 | "url = 'http://localhost:8085/v1'\n", 111 | "\n", 112 | "lnd_node = LndNode(\n", 113 | " cert_path='~/gocode/src/github.com/lightningnetwork/lnd/test_lnd2/tls.cert',\n", 114 | " macaroon_path='~/gocode/src/github.com/lightningnetwork/lnd/test_lnd2/data/chain/bitcoin/simnet/admin.macaroon',\n", 115 | " host='localhost',\n", 116 | " port=10018\n", 117 | ")\n", 118 | "\n", 119 | "llm = OpenAI(temperature=0)\n", 120 | "\n", 121 | "chain_new = L402APIChain.from_llm_and_api_docs(\n", 122 | " llm, API_DOCS, lightning_node=lnd_node, verbose=True,\n", 123 | ")" 124 | ] 125 | }, 126 | { 127 | "cell_type": "markdown", 128 | "id": "44f32887", 129 | "metadata": {}, 130 | "source": [ 131 | "First, we'll try to hit the url above using just normal `requests` to show that we need L402 awareness:" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": 11, 137 | "id": "52066762", 138 | "metadata": {}, 139 | "outputs": [ 140 | { 141 | "name": "stdout", 142 | "output_type": "stream", 143 | "text": [ 144 | "\n", 145 | "{'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Access-Control-Allow-Headers': 'Authorization, Grpc-Metadata-macaroon, WWW-Authenticate', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Origin': '*', 'Access-Control-Expose-Headers': 'WWW-Authenticate', 'Connection': 'keep-alive', 'Content-Type': 'text/plain; charset=utf-8', 'User-Agent': 'python-requests/2.30.0', 'Www-Authenticate': 'LSAT macaroon=\"AgEEbHNhdAJCAAD5dBUU9CqsQKbOM72ZikBtaQemr4Pe812w/qrHyKIoQEUkoNdaqgj/pUuJ97nPbEhHvLLIZnc79FZu00m1IXvgAAIac2VydmljZXM9Z3B0LTQtYWxsLXByb3h5OjAAAi1ncHQtNC1hbGwtcHJveHlfY2FwYWJpbGl0aWVzPWdwdDMtdHVyYm8sZ3B0LTQAAhZ2YWxpZF91bnRpbD0yMDIzLTA2LTAxAAImZ3B0LTQtYWxsLXByb3h5X3ZhbGlkX3VudGlsPTE3MTg3NjMzMTkAAAYg2LCG/XVLCOF9PPYKCweoGrzqDOKageDIUfktodXpKL8=\", invoice=\"lnsb1u1pjfz2avpp5l96p298592kypfkwxw7enzjqd45s0f40s000xhdsl64v0j9z9pqqdq8f3f5z4qcqzzsxqyz5vqsp5wmp7xry0svqv0h5xs27rd6ut50lk0xj3yeltaa2grk9cvpqx6trq9qyyssq7n68wh6swj5kc68y8v7c2s05zy28nts3na5s2w9gvzv6xcm43wgs7nqqn87pyhmz7nz8vdwdx83am5kd32mhrzkt7mw7zxmvtysrt2cqwq6h8a\"', 'X-Content-Type-Options': 'nosniff', 'Date': 'Tue, 20 Jun 2023 04:31:40 GMT', 'Content-Length': '17'}\n" 146 | ] 147 | } 148 | ], 149 | "source": [ 150 | "import requests \n", 151 | "resp = requests.get(url)\n", 152 | "print(resp)\n", 153 | "print(resp.headers)" 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "id": "41a469e1", 159 | "metadata": {}, 160 | "source": [ 161 | "Now we'll try it with the agent: " 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": 14, 167 | "id": "3018a887", 168 | "metadata": {}, 169 | "outputs": [ 170 | { 171 | "name": "stdout", 172 | "output_type": "stream", 173 | "text": [ 174 | "\n", 175 | "\n", 176 | "\u001b[1m> Entering new chain...\u001b[0m\n", 177 | "\u001b[32;1m\u001b[1;3m http://localhost:8085/v1/models\u001b[0m\n", 178 | "L402 error path=http://localhost:8085/v1/models, attempting to pay invoice and retry request...\n", 179 | "Paying invoice: lnsb1u1pjfztqzpp5cta25amjvvrka0n9q6xu263k994ulhl9en8ez3qax4ufn7dxdt9sdq8f3f5z4qcqzzsxqyz5vqsp5ph0dp4kwt02g7qg7fwut3xnwexg6d4luct34m4stx90lhc4xx3es9qyyssq88ur2k9x6c9zpl40t4tnmd920sxnl59ts8p08z3gktw9sg2hxt08dr48v4a3fmufplavexp43uzgzfsmaelxkzuxgq5tu7jjg6elmrgqq2jugh\n", 180 | "Obtained preimage: 51ec6cd0c35651ad86af89f7d839a008f7bd751f346cc2db5786d69b4d5601d3\n", 181 | "Assembling final authorization header: {'Authorization': 'LSAT AgEEbHNhdAJCAADC+qp3cmMHbr5lBo3FajYpa8/f5czPkUQdNXiZ+aZqy6XVq8NJXWavB1wPMhkAysyqO/SKTtsFQK/LDrSlf0fsAAIac2VydmljZXM9Z3B0LTQtYWxsLXByb3h5OjAAAi1ncHQtNC1hbGwtcHJveHlfY2FwYWJpbGl0aWVzPWdwdDMtdHVyYm8sZ3B0LTQAAhZ2YWxpZF91bnRpbD0yMDIzLTA2LTAxAAImZ3B0LTQtYWxsLXByb3h5X3ZhbGlkX3VudGlsPTE3MTg3NjMzMTkAAAYgk38yUftD8+DHAAgT966aT8wQ3jTbVgWeBgivn3STRP0=:51ec6cd0c35651ad86af89f7d839a008f7bd751f346cc2db5786d69b4d5601d3'}\n", 182 | "\u001b[33;1m\u001b[1;3m\n", 183 | " \"models\": [\n", 184 | " {\n", 185 | " \"id\": \"gpt9\",\n", 186 | " \"name\": \"GPT-9\",\n", 187 | " \"version\": \"1.0\"\n", 188 | " },\n", 189 | " {\n", 190 | " \"id\": \"gpt88\",\n", 191 | " \"name\": \"GPT-88\",\n", 192 | " \"version\": \"1.0\"\n", 193 | " },\n", 194 | " {\n", 195 | " \"id\": \"gptgammaz\",\n", 196 | " \"name\": \"GPT-Gammaz\",\n", 197 | " \"version\": \"1.0\"\n", 198 | " }\n", 199 | " ]\n", 200 | "}\u001b[0m\n", 201 | "\n", 202 | "\u001b[1m> Finished chain.\u001b[0m\n", 203 | " There are 3 total models supported.\n", 204 | "\n", 205 | "\n", 206 | "\u001b[1m> Entering new chain...\u001b[0m\n", 207 | "\u001b[32;1m\u001b[1;3m http://localhost:8085/v1/models\u001b[0m\n", 208 | "L402 error path=http://localhost:8085/v1/models, attempting to pay invoice and retry request...\n", 209 | "Paying invoice: lnsb1u1pjfztq9pp5sher4f44jvh99kfxl4uvzffdve9yxftnlzd3gdm457ukl6pg8atsdq8f3f5z4qcqzzsxqyz5vqsp5a35mxrmg26lp8769f22f4sxv0n0evcqpfgyq7t0m0zu5ntmxcxaq9qyyssqdu9nayyuh9rfu3scxx7779t3j0l60hh0c5hr94a8hu7w44wyuc53pcq6kpyu6tuq7z8zsnrawlrh2h6rh9kjjnn72ltvpqhcpxurx5sqy00kce\n", 210 | "Obtained preimage: 763dceda2e7703dda1b42a2cc5d988f28e5b67aeb185c0ac55abf195f8f48fcf\n", 211 | "Assembling final authorization header: {'Authorization': 'LSAT AgEEbHNhdAJCAACF8jqmtZMuUtkm/XjBJS1mSkMlc/ibFDd1p7lv6Cg/V6DAZywF3wFcE7qe8RQwpz5OfYB9HAqCj5rPD0oqakwxAAIac2VydmljZXM9Z3B0LTQtYWxsLXByb3h5OjAAAi1ncHQtNC1hbGwtcHJveHlfY2FwYWJpbGl0aWVzPWdwdDMtdHVyYm8sZ3B0LTQAAhZ2YWxpZF91bnRpbD0yMDIzLTA2LTAxAAImZ3B0LTQtYWxsLXByb3h5X3ZhbGlkX3VudGlsPTE3MTg3NjMzMTkAAAYga9/JBhraQHSAnRXCpknwaep97dsp+rb55JKmLoFT0Xg=:763dceda2e7703dda1b42a2cc5d988f28e5b67aeb185c0ac55abf195f8f48fcf'}\n", 212 | "\u001b[33;1m\u001b[1;3m\n", 213 | " \"models\": [\n", 214 | " {\n", 215 | " \"id\": \"gpt9\",\n", 216 | " \"name\": \"GPT-9\",\n", 217 | " \"version\": \"1.0\"\n", 218 | " },\n", 219 | " {\n", 220 | " \"id\": \"gpt88\",\n", 221 | " \"name\": \"GPT-88\",\n", 222 | " \"version\": \"1.0\"\n", 223 | " },\n", 224 | " {\n", 225 | " \"id\": \"gptgammaz\",\n", 226 | " \"name\": \"GPT-Gammaz\",\n", 227 | " \"version\": \"1.0\"\n", 228 | " }\n", 229 | " ]\n", 230 | "}\u001b[0m\n", 231 | "\n", 232 | "\u001b[1m> Finished chain.\u001b[0m\n", 233 | " The API supports three models: GPT-9, GPT-88, and GPT-Gammaz.\n" 234 | ] 235 | } 236 | ], 237 | "source": [ 238 | "output = chain_new.run('how many total models are supported?')\n", 239 | "print(output)\n", 240 | "\n", 241 | "output = chain_new.run('which models are supported?')\n", 242 | "print(output)" 243 | ] 244 | } 245 | ], 246 | "metadata": { 247 | "kernelspec": { 248 | "display_name": "Python 3 (ipykernel)", 249 | "language": "python", 250 | "name": "python3" 251 | }, 252 | "language_info": { 253 | "codemirror_mode": { 254 | "name": "ipython", 255 | "version": 3 256 | }, 257 | "file_extension": ".py", 258 | "mimetype": "text/x-python", 259 | "name": "python", 260 | "nbconvert_exporter": "python", 261 | "pygments_lexer": "ipython3", 262 | "version": "3.10.8" 263 | } 264 | }, 265 | "nbformat": 4, 266 | "nbformat_minor": 5 267 | } 268 | -------------------------------------------------------------------------------- /lightning/__init__.py: -------------------------------------------------------------------------------- 1 | from .lightning import LndNode 2 | from .lightning import LightningNode 3 | -------------------------------------------------------------------------------- /lightning/lightning.py: -------------------------------------------------------------------------------- 1 | from protos import lightning_pb2 as ln 2 | from protos import lightning_pb2_grpc as lnrpc 3 | 4 | import grpc 5 | import os 6 | 7 | import codecs 8 | 9 | import binascii 10 | 11 | # Due to updated ECDSA generated tls.cert we need to let gprc know that we need 12 | # to use that cipher suite otherwise there will be a handhsake error when we 13 | # communicate with the lnd rpc server. 14 | os.environ["GRPC_SSL_CIPHER_SUITES"] = 'HIGH+ECDSA' 15 | 16 | class LightningNode(object): 17 | """ 18 | """ 19 | 20 | def pay_invoice(self, invoice): 21 | raise NotImplementedError() 22 | 23 | class LndNode(LightningNode): 24 | """ 25 | """ 26 | 27 | def __init__(self, cert_path, macaroon_path, host='localhost', port='10009'): 28 | self.cert_path = cert_path 29 | self.macaroon_path = macaroon_path 30 | self.host = host 31 | self.port = port 32 | 33 | # TODO(roasbeef): pick out other details for cert + macaroon path 34 | 35 | self._grpc_conn = None 36 | 37 | with open(os.path.expanduser(self.cert_path), 'rb') as f: 38 | cert_bytes = f.read() 39 | self._cert_creds = grpc.ssl_channel_credentials(cert_bytes) 40 | 41 | with open(os.path.expanduser(self.macaroon_path), 'rb') as f: 42 | macaroon_bytes = f.read() 43 | self._macaroon = codecs.encode(macaroon_bytes, 'hex') 44 | 45 | def metadata_callback(context, callback): 46 | # for more info see grpc docs 47 | callback([('macaroon', self._macaroon)], None) 48 | 49 | # now build meta data credentials 50 | auth_creds = grpc.metadata_call_credentials(metadata_callback) 51 | 52 | # combine the cert credentials and the macaroon auth credentials 53 | # such that every call is properly encrypted and authenticated 54 | combined_creds = grpc.composite_channel_credentials( 55 | self._cert_creds, auth_creds, 56 | ) 57 | 58 | # finally pass in the combined credentials when creating a channel 59 | channel = grpc.secure_channel( 60 | '{}:{}'.format(host, port), combined_creds, 61 | ) 62 | 63 | self._grpc_conn = lnrpc.LightningStub(channel) 64 | 65 | def pay_invoice(self, invoice, amt=None): 66 | pay_resp = self._grpc_conn.SendPaymentSync( 67 | ln.SendRequest(payment_request=invoice, amt=amt), 68 | ) 69 | 70 | pre_image = binascii.hexlify(pay_resp.payment_preimage).decode('utf-8') 71 | 72 | return pre_image 73 | 74 | def send_payment(self, invoice): 75 | return self._grpc_conn.SendPaymentSync( 76 | ln.SendRequest(payment_request=invoice), 77 | ) 78 | 79 | def decode_invoice(self, invoice): 80 | req = ln.PayReqString(pay_req=invoice) 81 | decode_resp = self._grpc_conn.DecodePayReq(req) 82 | 83 | return decode_resp 84 | 85 | def channel_balance(self): 86 | return self._grpc_conn.ChannelBalance(ln.ChannelBalanceRequest()) 87 | 88 | def wallet_balance(self): 89 | return self._grpc_conn.WalletBalance(ln.WalletBalanceRequest()) 90 | 91 | def get_info(self): 92 | return self._grpc_conn.GetInfo(ln.GetInfoRequest()) 93 | -------------------------------------------------------------------------------- /llm_bitcoin_tools.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "bd3e7775", 6 | "metadata": {}, 7 | "source": [ 8 | "# LangChainBitcoin: LLM Agent Bitcoin Tools" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "4a4a3371", 14 | "metadata": {}, 15 | "source": [ 16 | "This notebook shows some examples of how to use the set of \"tools\" provided in the `LangChainBitcoin` project with the `langchain` project and also the newly announced native function calls provided by the OpenAPI GPt-3/4 APIs. By hooking up Lightning tools directly to the LLM agent, we give it the ability to: interact with the Bitcoin network, hold/send/recv Bitcoin (on-chain or over LN), and also eventually directly mmanage/drive an active node.\n", 17 | "\n", 18 | "Earlier versions of the `langchain` project included support for \"tools\" which an agent can use to figure out how to answer a prompt or iterate towards a goal. This was implemented via a series of prompts and chained LLMs to \"teach\" the LLM how to use a set of tools. \n", 19 | "\n", 20 | "On June 13th, 2023 [OpenAPI released support for native function calls](https://openai.com/blog/function-calling-and-other-api-updates)! Native function calls can be used to directly give the LLM native tools that it can use. Basic usage of thes function calls can be used to ensure that the agent emits structred data like JSON, which can be difficult produce reliably otherwise. The latest releases of the `langchain` library augment the prior abstractions with direct support for the new function API provided by Open AI. \n", 21 | "\n", 22 | "By combining the new function call API with `langchain`, and also our custom set of Bitcoin/Lightning tools, we're able to create agents that are able to directly interact with Bitcoin and also the Lightning Network. At a high level, we create some functions implementing tools, then _describe_ these tools to the agent. A description can include a strict schema for the input arguments (`a: string, b: Invoice`, etc) and also some few-shot examples of what a response would look like. \n", 23 | "\n", 24 | "In the rest of this notebook, we'll demo some basic tools to show how things are initialized, and also how to create and prompt the agent. " 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "id": "3233c26a", 30 | "metadata": {}, 31 | "source": [ 32 | "We'll start with some imports needed to get the ball rolling:" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 2, 38 | "id": "3a40634c", 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "from bitcoin_tools import LndTools\n", 43 | "from lightning import LndNode\n", 44 | "from langchain.agents import initialize_agent, AgentType\n", 45 | "from langchain.chat_models import ChatOpenAI" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "id": "eb768060", 51 | "metadata": {}, 52 | "source": [ 53 | "Next we'll create a connection to our lnd node. This uses some dummy data, but be sure slot in proper macaroon, TLS, and connection information. In the future, this'll use LNC to make it much easier to pair to a node:" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 3, 59 | "id": "b4fb4dfb", 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "lnd_node = LndNode(\n", 64 | " cert_path='~/gocode/src/github.com/lightningnetwork/lnd/test_lnd2/tls.cert',\n", 65 | " macaroon_path='~/gocode/src/github.com/lightningnetwork/lnd/test_lnd2/data/chain/bitcoin/simnet/admin.macaroon',\n", 66 | " host='localhost',\n", 67 | " port=10018\n", 68 | ")" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "id": "c03676db", 74 | "metadata": {}, 75 | "source": [ 76 | "Next we'll initialize our set of Bitcoin tools, passing in the live `lnd_node` connection above:" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 4, 82 | "id": "f7d9f94e", 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "lnd_tools = LndTools.from_lnd_node(lnd_node=lnd_node)" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "id": "556f8310", 92 | "metadata": {}, 93 | "source": [ 94 | "The final part of our set up will create an instance of the GPT 3.5 model with a cut off date of June 13th. This date is important as only this version of the model knows how to make function calls. GPT 4 works too, but it's a bit slower, and we're not doing anything too fancy here. " 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 5, 100 | "id": "35a1627e", 101 | "metadata": {}, 102 | "outputs": [], 103 | "source": [ 104 | "llm_model = ChatOpenAI(model=\"gpt-3.5-turbo-0613\")\n", 105 | "\n", 106 | "bitcoin_agent = initialize_agent(\n", 107 | " lnd_tools.get_tools(),\n", 108 | " llm_model,\n", 109 | " agent=AgentType.OPENAI_FUNCTIONS,\n", 110 | " verbose=True,\n", 111 | ")" 112 | ] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "id": "20d90488", 117 | "metadata": {}, 118 | "source": [ 119 | "With our agent initialized, let's try out some example prompts!" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "id": "0589a7cb", 125 | "metadata": {}, 126 | "source": [ 127 | "First, let's see if it can figure out some basic information abuot the node that it's connected to:" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 6, 133 | "id": "65dcfdca", 134 | "metadata": {}, 135 | "outputs": [ 136 | { 137 | "name": "stdout", 138 | "output_type": "stream", 139 | "text": [ 140 | "\n", 141 | "\n", 142 | "\u001b[1m> Entering new chain...\u001b[0m\n", 143 | "\u001b[32;1m\u001b[1;3m\n", 144 | "Invoking: `channel_balance` with `{}`\n", 145 | "\n", 146 | "\n", 147 | "\u001b[0m\u001b[36;1m\u001b[1;3mbalance: 30399\n", 148 | "local_balance {\n", 149 | " sat: 30399\n", 150 | " msat: 30399000\n", 151 | "}\n", 152 | "remote_balance {\n", 153 | " sat: 966131\n", 154 | " msat: 966131000\n", 155 | "}\n", 156 | "unsettled_local_balance {\n", 157 | "}\n", 158 | "unsettled_remote_balance {\n", 159 | "}\n", 160 | "pending_open_local_balance {\n", 161 | "}\n", 162 | "pending_open_remote_balance {\n", 163 | "}\n", 164 | "\u001b[0m\u001b[32;1m\u001b[1;3mYour current channel balance is 30,399 satoshis.\u001b[0m\n", 165 | "\n", 166 | "\u001b[1m> Finished chain.\u001b[0m\n", 167 | "Your current channel balance is 30,399 satoshis.\n" 168 | ] 169 | } 170 | ], 171 | "source": [ 172 | "print(bitcoin_agent.run('What is my current channel balance in satoshis?'))" 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": 7, 178 | "id": "f055e9bb", 179 | "metadata": {}, 180 | "outputs": [ 181 | { 182 | "name": "stdout", 183 | "output_type": "stream", 184 | "text": [ 185 | "\n", 186 | "\n", 187 | "\u001b[1m> Entering new chain...\u001b[0m\n", 188 | "\u001b[32;1m\u001b[1;3m\n", 189 | "Invoking: `wallet_balance` with `{}`\n", 190 | "\n", 191 | "\n", 192 | "\u001b[0m\u001b[33;1m\u001b[1;3mtotal_balance: 1000\n", 193 | "confirmed_balance: 1000\n", 194 | "reserved_balance_anchor_chan: 10000\n", 195 | "account_balance {\n", 196 | " key: \"imported\"\n", 197 | " value {\n", 198 | " confirmed_balance: 1000\n", 199 | " }\n", 200 | "}\n", 201 | "account_balance {\n", 202 | " key: \"default\"\n", 203 | " value {\n", 204 | " }\n", 205 | "}\n", 206 | "\u001b[0m\u001b[32;1m\u001b[1;3mYour current on-chain balance is 1000 satoshis.\u001b[0m\n", 207 | "\n", 208 | "\u001b[1m> Finished chain.\u001b[0m\n", 209 | "Your current on-chain balance is 1000 satoshis.\n" 210 | ] 211 | } 212 | ], 213 | "source": [ 214 | "print(bitcoin_agent.run('What is my current on chain balance'))" 215 | ] 216 | }, 217 | { 218 | "cell_type": "code", 219 | "execution_count": 8, 220 | "id": "7c3de09e", 221 | "metadata": {}, 222 | "outputs": [ 223 | { 224 | "name": "stdout", 225 | "output_type": "stream", 226 | "text": [ 227 | "\n", 228 | "\n", 229 | "\u001b[1m> Entering new chain...\u001b[0m\n", 230 | "\u001b[32;1m\u001b[1;3m\n", 231 | "Invoking: `wallet_balance` with `{}`\n", 232 | "\n", 233 | "\n", 234 | "\u001b[0m\u001b[33;1m\u001b[1;3mtotal_balance: 1000\n", 235 | "confirmed_balance: 1000\n", 236 | "reserved_balance_anchor_chan: 10000\n", 237 | "account_balance {\n", 238 | " key: \"imported\"\n", 239 | " value {\n", 240 | " confirmed_balance: 1000\n", 241 | " }\n", 242 | "}\n", 243 | "account_balance {\n", 244 | " key: \"default\"\n", 245 | " value {\n", 246 | " }\n", 247 | "}\n", 248 | "\u001b[0m\u001b[32;1m\u001b[1;3mNo, there is no unconfirmed change in the node. The total balance is 1000 satoshis, and the confirmed balance is also 1000 satoshis.\u001b[0m\n", 249 | "\n", 250 | "\u001b[1m> Finished chain.\u001b[0m\n", 251 | "No, there is no unconfirmed change in the node. The total balance is 1000 satoshis, and the confirmed balance is also 1000 satoshis.\n" 252 | ] 253 | } 254 | ], 255 | "source": [ 256 | "print(bitcoin_agent.run('Do I have any unconfirmed change in the node?'))" 257 | ] 258 | }, 259 | { 260 | "cell_type": "code", 261 | "execution_count": 9, 262 | "id": "f85d0df3", 263 | "metadata": {}, 264 | "outputs": [ 265 | { 266 | "name": "stdout", 267 | "output_type": "stream", 268 | "text": [ 269 | "\n", 270 | "\n", 271 | "\u001b[1m> Entering new chain...\u001b[0m\n", 272 | "\u001b[32;1m\u001b[1;3m\n", 273 | "Invoking: `get_info` with `{}`\n", 274 | "\n", 275 | "\n", 276 | "\u001b[0m\u001b[38;5;200m\u001b[1;3mversion: \"0.16.99-beta commit=v0.16.0-beta-350-gbbbf7d33f\"\n", 277 | "commit_hash: \"bbbf7d33fb1527acebb44e2a69d16fbcf24cc2fa\"\n", 278 | "identity_pubkey: \"0231c1b36b280d1ecccff2fd7526b8188b83f918eb549b182f6d1d0c577bfede4c\"\n", 279 | "alias: \"0231c1b36b280d1ecccf\"\n", 280 | "color: \"#3399ff\"\n", 281 | "num_active_channels: 1\n", 282 | "num_peers: 1\n", 283 | "block_height: 3305\n", 284 | "block_hash: \"1931aa2f19b59676edfef04dbbf82ddcf878297f0db27490011f2f790837c372\"\n", 285 | "best_header_timestamp: 1686961637\n", 286 | "synced_to_graph: true\n", 287 | "chains {\n", 288 | " chain: \"bitcoin\"\n", 289 | " network: \"simnet\"\n", 290 | "}\n", 291 | "features {\n", 292 | " key: 0\n", 293 | " value {\n", 294 | " name: \"data-loss-protect\"\n", 295 | " is_required: true\n", 296 | " is_known: true\n", 297 | " }\n", 298 | "}\n", 299 | "features {\n", 300 | " key: 5\n", 301 | " value {\n", 302 | " name: \"upfront-shutdown-script\"\n", 303 | " is_known: true\n", 304 | " }\n", 305 | "}\n", 306 | "features {\n", 307 | " key: 7\n", 308 | " value {\n", 309 | " name: \"gossip-queries\"\n", 310 | " is_known: true\n", 311 | " }\n", 312 | "}\n", 313 | "features {\n", 314 | " key: 9\n", 315 | " value {\n", 316 | " name: \"tlv-onion\"\n", 317 | " is_known: true\n", 318 | " }\n", 319 | "}\n", 320 | "features {\n", 321 | " key: 12\n", 322 | " value {\n", 323 | " name: \"static-remote-key\"\n", 324 | " is_required: true\n", 325 | " is_known: true\n", 326 | " }\n", 327 | "}\n", 328 | "features {\n", 329 | " key: 14\n", 330 | " value {\n", 331 | " name: \"payment-addr\"\n", 332 | " is_required: true\n", 333 | " is_known: true\n", 334 | " }\n", 335 | "}\n", 336 | "features {\n", 337 | " key: 17\n", 338 | " value {\n", 339 | " name: \"multi-path-payments\"\n", 340 | " is_known: true\n", 341 | " }\n", 342 | "}\n", 343 | "features {\n", 344 | " key: 23\n", 345 | " value {\n", 346 | " name: \"anchors-zero-fee-htlc-tx\"\n", 347 | " is_known: true\n", 348 | " }\n", 349 | "}\n", 350 | "features {\n", 351 | " key: 27\n", 352 | " value {\n", 353 | " name: \"shutdown-any-segwit\"\n", 354 | " is_known: true\n", 355 | " }\n", 356 | "}\n", 357 | "features {\n", 358 | " key: 30\n", 359 | " value {\n", 360 | " name: \"amp\"\n", 361 | " is_required: true\n", 362 | " is_known: true\n", 363 | " }\n", 364 | "}\n", 365 | "features {\n", 366 | " key: 31\n", 367 | " value {\n", 368 | " name: \"amp\"\n", 369 | " is_known: true\n", 370 | " }\n", 371 | "}\n", 372 | "features {\n", 373 | " key: 45\n", 374 | " value {\n", 375 | " name: \"explicit-commitment-type\"\n", 376 | " is_known: true\n", 377 | " }\n", 378 | "}\n", 379 | "features {\n", 380 | " key: 2023\n", 381 | " value {\n", 382 | " name: \"script-enforced-lease\"\n", 383 | " is_known: true\n", 384 | " }\n", 385 | "}\n", 386 | "\u001b[0m\u001b[32;1m\u001b[1;3mI am hooked up to version 0.16.99-beta of lnd.\u001b[0m\n", 387 | "\n", 388 | "\u001b[1m> Finished chain.\u001b[0m\n", 389 | "I am hooked up to version 0.16.99-beta of lnd.\n" 390 | ] 391 | } 392 | ], 393 | "source": [ 394 | "print(bitcoin_agent.run('what version of lnd are you hooked up to?'))" 395 | ] 396 | }, 397 | { 398 | "cell_type": "markdown", 399 | "id": "250a003d", 400 | "metadata": {}, 401 | "source": [ 402 | "So it can figure out which function it needs to call in order to figure how to execute/satsify the prompt. Let's trying some stuff that's a bit more LN specific:" 403 | ] 404 | }, 405 | { 406 | "cell_type": "code", 407 | "execution_count": 10, 408 | "id": "394fba62", 409 | "metadata": {}, 410 | "outputs": [ 411 | { 412 | "name": "stdout", 413 | "output_type": "stream", 414 | "text": [ 415 | "\n", 416 | "\n", 417 | "\u001b[1m> Entering new chain...\u001b[0m\n", 418 | "\u001b[32;1m\u001b[1;3m\n", 419 | "Invoking: `decode_invoice` with `{'invoice': 'lnsb100u1pjfz83lpp55h08akqscecxejh5jfecdx7hxf693tsv7c3lyp207fjqwmx9a8fsdqqcqzzsxqyz5vqsp5xhdak7n7h2dtl0gky9xzffnwel67h94jawkn0skpkj4xmcvtyphq9qyyssqrk86elwl2wctg6pw4uxjh4a3swrpljfvefy3xcxxk90tu484wxrpyu5pcwdfk65yjgw4fash3dsyuyxdyalengm4pwwj8t083cgqyhcphvaj27'}`\n", 420 | "\n", 421 | "\n", 422 | "\u001b[0m\u001b[33;1m\u001b[1;3mdestination: \"038cf9360fa60a6de14ad7490950f90e06e828d4557a711aa54fabc6a5f2b4d81d\"\n", 423 | "payment_hash: \"a5de7ed810c6706ccaf49273869bd7327458ae0cf623f2054ff264076cc5e9d3\"\n", 424 | "num_satoshis: 10000\n", 425 | "timestamp: 1687232063\n", 426 | "expiry: 86400\n", 427 | "cltv_expiry: 80\n", 428 | "payment_addr: \"5\\333\\333z~\\272\\232\\277\\275\\026!L$\\246n\\317\\365\\353\\226\\262\\353\\2557\\302\\301\\264\\252m\\341\\213 n\"\n", 429 | "num_msat: 10000000\n", 430 | "features {\n", 431 | " key: 9\n", 432 | " value {\n", 433 | " name: \"tlv-onion\"\n", 434 | " is_known: true\n", 435 | " }\n", 436 | "}\n", 437 | "features {\n", 438 | " key: 14\n", 439 | " value {\n", 440 | " name: \"payment-addr\"\n", 441 | " is_required: true\n", 442 | " is_known: true\n", 443 | " }\n", 444 | "}\n", 445 | "features {\n", 446 | " key: 17\n", 447 | " value {\n", 448 | " name: \"multi-path-payments\"\n", 449 | " is_known: true\n", 450 | " }\n", 451 | "}\n", 452 | "\u001b[0m\u001b[32;1m\u001b[1;3mThe invoice is requesting 10,000 satoshis.\u001b[0m\n", 453 | "\n", 454 | "\u001b[1m> Finished chain.\u001b[0m\n", 455 | "The invoice is requesting 10,000 satoshis.\n" 456 | ] 457 | } 458 | ], 459 | "source": [ 460 | "invoice = \"lnsb100u1pjfz83lpp55h08akqscecxejh5jfecdx7hxf693tsv7c3lyp207fjqwmx9a8fsdqqcqzzsxqyz5vqsp5xhdak7n7h2dtl0gky9xzffnwel67h94jawkn0skpkj4xmcvtyphq9qyyssqrk86elwl2wctg6pw4uxjh4a3swrpljfvefy3xcxxk90tu484wxrpyu5pcwdfk65yjgw4fash3dsyuyxdyalengm4pwwj8t083cgqyhcphvaj27\"\n", 461 | "prompt = \"How many satoshis is this invoice requesting: {invoice}\".format(invoice=invoice)\n", 462 | "print(bitcoin_agent.run(prompt))" 463 | ] 464 | }, 465 | { 466 | "cell_type": "code", 467 | "execution_count": 11, 468 | "id": "4f5adaef", 469 | "metadata": {}, 470 | "outputs": [ 471 | { 472 | "name": "stdout", 473 | "output_type": "stream", 474 | "text": [ 475 | "\n", 476 | "\n", 477 | "\u001b[1m> Entering new chain...\u001b[0m\n", 478 | "\u001b[32;1m\u001b[1;3m\n", 479 | "Invoking: `decode_invoice` with `{'invoice': 'lnsb100u1pjfz83lpp55h08akqscecxejh5jfecdx7hxf693tsv7c3lyp207fjqwmx9a8fsdqqcqzzsxqyz5vqsp5xhdak7n7h2dtl0gky9xzffnwel67h94jawkn0skpkj4xmcvtyphq9qyyssqrk86elwl2wctg6pw4uxjh4a3swrpljfvefy3xcxxk90tu484wxrpyu5pcwdfk65yjgw4fash3dsyuyxdyalengm4pwwj8t083cgqyhcphvaj27'}`\n", 480 | "\n", 481 | "\n", 482 | "\u001b[0m\u001b[33;1m\u001b[1;3mdestination: \"038cf9360fa60a6de14ad7490950f90e06e828d4557a711aa54fabc6a5f2b4d81d\"\n", 483 | "payment_hash: \"a5de7ed810c6706ccaf49273869bd7327458ae0cf623f2054ff264076cc5e9d3\"\n", 484 | "num_satoshis: 10000\n", 485 | "timestamp: 1687232063\n", 486 | "expiry: 86400\n", 487 | "cltv_expiry: 80\n", 488 | "payment_addr: \"5\\333\\333z~\\272\\232\\277\\275\\026!L$\\246n\\317\\365\\353\\226\\262\\353\\2557\\302\\301\\264\\252m\\341\\213 n\"\n", 489 | "num_msat: 10000000\n", 490 | "features {\n", 491 | " key: 9\n", 492 | " value {\n", 493 | " name: \"tlv-onion\"\n", 494 | " is_known: true\n", 495 | " }\n", 496 | "}\n", 497 | "features {\n", 498 | " key: 14\n", 499 | " value {\n", 500 | " name: \"payment-addr\"\n", 501 | " is_required: true\n", 502 | " is_known: true\n", 503 | " }\n", 504 | "}\n", 505 | "features {\n", 506 | " key: 17\n", 507 | " value {\n", 508 | " name: \"multi-path-payments\"\n", 509 | " is_known: true\n", 510 | " }\n", 511 | "}\n", 512 | "\u001b[0m" 513 | ] 514 | }, 515 | { 516 | "name": "stderr", 517 | "output_type": "stream", 518 | "text": [ 519 | "Retrying langchain.chat_models.openai.ChatOpenAI.completion_with_retry.._completion_with_retry in 1.0 seconds as it raised RateLimitError: That model is currently overloaded with other requests. You can retry your request, or contact us through our help center at help.openai.com if the error persists. (Please include the request ID 976ee90ffe8a550092ca828c03f191f6 in your message.).\n" 520 | ] 521 | }, 522 | { 523 | "name": "stdout", 524 | "output_type": "stream", 525 | "text": [ 526 | "\u001b[32;1m\u001b[1;3mThis Lightning invoice is for a payment of 10,000 satoshis (0.0001 BTC). The payment is to be made to the destination address \"038cf9360fa60a6de14ad7490950f90e06e828d4557a711aa54fabc6a5f2b4d81d\". The payment hash is \"a5de7ed810c6706ccaf49273869bd7327458ae0cf623f2054ff264076cc5e9d3\".\n", 527 | "\n", 528 | "The invoice has a timestamp of 1687232063, indicating when it was created. It will expire in 86400 seconds (24 hours). The CLTV expiry is set to 80, which means the payment must be completed before a certain block height.\n", 529 | "\n", 530 | "The payment address is a series of characters that uniquely identifies the payment. It is represented as \"5\\333\\333z~\\272\\232\\277\\275\\026!L$\\246n\\317\\365\\353\\226\\262\\353\\2557\\302\\301\\264\\252m\\341\\213 n\".\n", 531 | "\n", 532 | "The invoice also includes some features. The \"tlv-onion\" feature allows for onion-encrypted payloads using the TLV format. The \"payment-addr\" feature is required and indicates that a payment address is included in the invoice. The \"multi-path-payments\" feature supports splitting the payment into multiple paths for improved reliability.\n", 533 | "\n", 534 | "Please note that this invoice is specific to the Lightning Network and can only be paid using a Lightning-enabled wallet or service.\u001b[0m\n", 535 | "\n", 536 | "\u001b[1m> Finished chain.\u001b[0m\n", 537 | "This Lightning invoice is for a payment of 10,000 satoshis (0.0001 BTC). The payment is to be made to the destination address \"038cf9360fa60a6de14ad7490950f90e06e828d4557a711aa54fabc6a5f2b4d81d\". The payment hash is \"a5de7ed810c6706ccaf49273869bd7327458ae0cf623f2054ff264076cc5e9d3\".\n", 538 | "\n", 539 | "The invoice has a timestamp of 1687232063, indicating when it was created. It will expire in 86400 seconds (24 hours). The CLTV expiry is set to 80, which means the payment must be completed before a certain block height.\n", 540 | "\n", 541 | "The payment address is a series of characters that uniquely identifies the payment. It is represented as \"5\\333\\333z~\\272\\232\\277\\275\\026!L$\\246n\\317\\365\\353\\226\\262\\353\\2557\\302\\301\\264\\252m\\341\\213 n\".\n", 542 | "\n", 543 | "The invoice also includes some features. The \"tlv-onion\" feature allows for onion-encrypted payloads using the TLV format. The \"payment-addr\" feature is required and indicates that a payment address is included in the invoice. The \"multi-path-payments\" feature supports splitting the payment into multiple paths for improved reliability.\n", 544 | "\n", 545 | "Please note that this invoice is specific to the Lightning Network and can only be paid using a Lightning-enabled wallet or service.\n" 546 | ] 547 | } 548 | ], 549 | "source": [ 550 | "prompt = \"What else can you tell me about this Lightning invoice? Your response should be in natural language, as if you were describing the payment requirements to a customer. {invoice}\".format(invoice=invoice)\n", 551 | "\n", 552 | "print(bitcoin_agent.run(prompt))" 553 | ] 554 | }, 555 | { 556 | "cell_type": "code", 557 | "execution_count": 17, 558 | "id": "cda19d7d", 559 | "metadata": {}, 560 | "outputs": [ 561 | { 562 | "name": "stdout", 563 | "output_type": "stream", 564 | "text": [ 565 | "\n", 566 | "\n", 567 | "\u001b[1m> Entering new chain...\u001b[0m\n", 568 | "\u001b[32;1m\u001b[1;3m\n", 569 | "Invoking: `send_payment` with `{'invoice': 'lnsb10n1pjfzgscpp5gayl6r39ty0cnjsdf889aywn9qlvmr7msru48vjc29k5akde43hsdqqcqzzsxqyz5vqsp5mq4hutpexz6zp8kyfr6l9khxnx4gzkqsxrmgdxz0l9edk57am64q9qyyssq2j2hg7rzyfr4t9t0t2jqm7qq86atzz4w82l2zggvghq4zx5vcey3m93skdjwq58x63530ryke53tms920sj244mgftdthw54g22qwfgqq2kgfc'}`\n", 570 | "\n", 571 | "\n", 572 | "\u001b[0m\u001b[36;1m\u001b[1;3mpayment_preimage: \"/8\\245\\276@;\\027\\265\\327\\343\\0259T\\370\\310$6\\264.G\\253*\\025d\\262z~\\3777\\373j}\"\n", 573 | "payment_route {\n", 574 | " total_time_lock: 3388\n", 575 | " total_amt: 1\n", 576 | " hops {\n", 577 | " chan_id: 3619592278704128\n", 578 | " chan_capacity: 1000000\n", 579 | " amt_to_forward: 1\n", 580 | " expiry: 3388\n", 581 | " amt_to_forward_msat: 1000\n", 582 | " pub_key: \"038cf9360fa60a6de14ad7490950f90e06e828d4557a711aa54fabc6a5f2b4d81d\"\n", 583 | " tlv_payload: true\n", 584 | " mpp_record {\n", 585 | " payment_addr: \"\\330+~,90\\264 \\236\\304H\\365\\362\\332\\346\\231\\252\\201X\\0200\\366\\206\\230O\\371r\\333S\\335\\336\\252\"\n", 586 | " total_amt_msat: 1000\n", 587 | " }\n", 588 | " }\n", 589 | " total_amt_msat: 1000\n", 590 | "}\n", 591 | "payment_hash: \"GI\\375\\016%Y\\037\\211\\312\\rI\\316^\\221\\323(>\\315\\217\\333\\200\\371S\\262XQmN\\331\\271\\254o\"\n", 592 | "\u001b[0m\u001b[32;1m\u001b[1;3mThe payment was successful. The preimage for the invoice is: \"/8\\245\\276@;\\027\\265\\327\\343\\0259T\\370\\310$6\\264.G\\253*\\025d\\262z~\\3777\\373j}\".\n", 593 | "\n", 594 | "The payment took the following route:\n", 595 | "- Total time lock: 3388\n", 596 | "- Total amount: 1 satoshi\n", 597 | "\n", 598 | "Route hops:\n", 599 | "- Channel ID: 3619592278704128\n", 600 | "- Channel capacity: 1000000 satoshis\n", 601 | "- Amount to forward: 1 satoshi\n", 602 | "- Expiry: 3388\n", 603 | "- Amount to forward in milli-satoshis: 1000 msat\n", 604 | "- Public key: \"038cf9360fa60a6de14ad7490950f90e06e828d4557a711aa54fabc6a5f2b4d81d\"\n", 605 | "- TLV payload: true\n", 606 | "- MPP record:\n", 607 | " - Payment address: \"\\330+~,90\\264 \\236\\304H\\365\\362\\332\\346\\231\\252\\201X\\0200\\366\\206\\230O\\371r\\333S\\335\\336\\252\"\n", 608 | " - Total amount in milli-satoshis: 1000 msat\n", 609 | "\n", 610 | "The total amount in milli-satoshis for the payment is 1000 msat.\n", 611 | "\n", 612 | "Please note that the fees paid for the payment are not explicitly provided in the response.\u001b[0m\n", 613 | "\n", 614 | "\u001b[1m> Finished chain.\u001b[0m\n", 615 | "The payment was successful. The preimage for the invoice is: \"/8\\245\\276@;\\027\\265\\327\\343\\0259T\\370\\310$6\\264.G\\253*\\025d\\262z~\\3777\\373j}\".\n", 616 | "\n", 617 | "The payment took the following route:\n", 618 | "- Total time lock: 3388\n", 619 | "- Total amount: 1 satoshi\n", 620 | "\n", 621 | "Route hops:\n", 622 | "- Channel ID: 3619592278704128\n", 623 | "- Channel capacity: 1000000 satoshis\n", 624 | "- Amount to forward: 1 satoshi\n", 625 | "- Expiry: 3388\n", 626 | "- Amount to forward in milli-satoshis: 1000 msat\n", 627 | "- Public key: \"038cf9360fa60a6de14ad7490950f90e06e828d4557a711aa54fabc6a5f2b4d81d\"\n", 628 | "- TLV payload: true\n", 629 | "- MPP record:\n", 630 | " - Payment address: \"\\330+~,90\\264 \\236\\304H\\365\\362\\332\\346\\231\\252\\201X\\0200\\366\\206\\230O\\371r\\333S\\335\\336\\252\"\n", 631 | " - Total amount in milli-satoshis: 1000 msat\n", 632 | "\n", 633 | "The total amount in milli-satoshis for the payment is 1000 msat.\n", 634 | "\n", 635 | "Please note that the fees paid for the payment are not explicitly provided in the response.\n" 636 | ] 637 | } 638 | ], 639 | "source": [ 640 | "invoice_to_pay = \"lnsb10n1pjfzgscpp5gayl6r39ty0cnjsdf889aywn9qlvmr7msru48vjc29k5akde43hsdqqcqzzsxqyz5vqsp5mq4hutpexz6zp8kyfr6l9khxnx4gzkqsxrmgdxz0l9edk57am64q9qyyssq2j2hg7rzyfr4t9t0t2jqm7qq86atzz4w82l2zggvghq4zx5vcey3m93skdjwq58x63530ryke53tms920sj244mgftdthw54g22qwfgqq2kgfc\"\n", 641 | "prompt = \"Pay this invoice, and return the preimage for ths invoice. Also can you tell me anything about the route the payment took, and how much in fees I paid: {invoice}\".format(invoice=invoice_to_pay)\n", 642 | "print(bitcoin_agent.run(prompt))" 643 | ] 644 | }, 645 | { 646 | "cell_type": "code", 647 | "execution_count": null, 648 | "id": "c47d16dd", 649 | "metadata": {}, 650 | "outputs": [], 651 | "source": [] 652 | }, 653 | { 654 | "cell_type": "code", 655 | "execution_count": null, 656 | "id": "d00035cc", 657 | "metadata": {}, 658 | "outputs": [], 659 | "source": [] 660 | }, 661 | { 662 | "cell_type": "code", 663 | "execution_count": null, 664 | "id": "5521d2e8", 665 | "metadata": {}, 666 | "outputs": [], 667 | "source": [] 668 | } 669 | ], 670 | "metadata": { 671 | "kernelspec": { 672 | "display_name": "Python 3 (ipykernel)", 673 | "language": "python", 674 | "name": "python3" 675 | }, 676 | "language_info": { 677 | "codemirror_mode": { 678 | "name": "ipython", 679 | "version": 3 680 | }, 681 | "file_extension": ".py", 682 | "mimetype": "text/x-python", 683 | "name": "python", 684 | "nbconvert_exporter": "python", 685 | "pygments_lexer": "ipython3", 686 | "version": "3.10.8" 687 | } 688 | }, 689 | "nbformat": 4, 690 | "nbformat_minor": 5 691 | } 692 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import dotenv_values 3 | from lightning import LndNode 4 | from utils import LLMUtils 5 | 6 | config = {**dotenv_values(".env.shared"), **dotenv_values(".env.secret"), **os.environ} 7 | 8 | os.environ["OPENAI_API_KEY"] = config.get("OPENAI_API_KEY", "missed") 9 | 10 | lnd_node = LndNode( 11 | cert_path=config.get("CERT_PATH", "missed"), 12 | macaroon_path=config.get("MACAROON_PATH", "missed"), 13 | host=config.get("LND_NODE_HOST", "missed"), 14 | port=config.get("LND_NODE_PORT", "0"), 15 | ) 16 | 17 | llm_utils = LLMUtils(lnd_node=lnd_node) 18 | target_api_tool = llm_utils.get_target_api_tool() 19 | agent_executor = llm_utils.get_entry_point(additional_tools=[target_api_tool]) 20 | 21 | # Provide instructons to the AI agent here. 22 | agent_executor.invoke({"input": "How many channels does my node have open?"}) 23 | -------------------------------------------------------------------------------- /protos/router.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "lightning.proto"; 4 | 5 | package routerrpc; 6 | 7 | option go_package = "github.com/lightningnetwork/lnd/lnrpc/routerrpc"; 8 | 9 | // Router is a service that offers advanced interaction with the router 10 | // subsystem of the daemon. 11 | service Router { 12 | /* 13 | SendPaymentV2 attempts to route a payment described by the passed 14 | PaymentRequest to the final destination. The call returns a stream of 15 | payment updates. 16 | */ 17 | rpc SendPaymentV2 (SendPaymentRequest) returns (stream lnrpc.Payment); 18 | 19 | /* 20 | TrackPaymentV2 returns an update stream for the payment identified by the 21 | payment hash. 22 | */ 23 | rpc TrackPaymentV2 (TrackPaymentRequest) returns (stream lnrpc.Payment); 24 | 25 | /* 26 | TrackPayments returns an update stream for every payment that is not in a 27 | terminal state. Note that if payments are in-flight while starting a new 28 | subscription, the start of the payment stream could produce out-of-order 29 | and/or duplicate events. In order to get updates for every in-flight 30 | payment attempt make sure to subscribe to this method before initiating any 31 | payments. 32 | */ 33 | rpc TrackPayments (TrackPaymentsRequest) returns (stream lnrpc.Payment); 34 | 35 | /* 36 | EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it 37 | may cost to send an HTLC to the target end destination. 38 | */ 39 | rpc EstimateRouteFee (RouteFeeRequest) returns (RouteFeeResponse); 40 | 41 | /* 42 | Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via 43 | the specified route. This method differs from SendPayment in that it 44 | allows users to specify a full route manually. This can be used for 45 | things like rebalancing, and atomic swaps. It differs from the newer 46 | SendToRouteV2 in that it doesn't return the full HTLC information. 47 | */ 48 | rpc SendToRoute (SendToRouteRequest) returns (SendToRouteResponse) { 49 | option deprecated = true; 50 | } 51 | 52 | /* 53 | SendToRouteV2 attempts to make a payment via the specified route. This 54 | method differs from SendPayment in that it allows users to specify a full 55 | route manually. This can be used for things like rebalancing, and atomic 56 | swaps. 57 | */ 58 | rpc SendToRouteV2 (SendToRouteRequest) returns (lnrpc.HTLCAttempt); 59 | 60 | /* 61 | ResetMissionControl clears all mission control state and starts with a clean 62 | slate. 63 | */ 64 | rpc ResetMissionControl (ResetMissionControlRequest) 65 | returns (ResetMissionControlResponse); 66 | 67 | /* 68 | QueryMissionControl exposes the internal mission control state to callers. 69 | It is a development feature. 70 | */ 71 | rpc QueryMissionControl (QueryMissionControlRequest) 72 | returns (QueryMissionControlResponse); 73 | 74 | /* 75 | XImportMissionControl is an experimental API that imports the state provided 76 | to the internal mission control's state, using all results which are more 77 | recent than our existing values. These values will only be imported 78 | in-memory, and will not be persisted across restarts. 79 | */ 80 | rpc XImportMissionControl (XImportMissionControlRequest) 81 | returns (XImportMissionControlResponse); 82 | 83 | /* 84 | GetMissionControlConfig returns mission control's current config. 85 | */ 86 | rpc GetMissionControlConfig (GetMissionControlConfigRequest) 87 | returns (GetMissionControlConfigResponse); 88 | 89 | /* 90 | SetMissionControlConfig will set mission control's config, if the config 91 | provided is valid. 92 | */ 93 | rpc SetMissionControlConfig (SetMissionControlConfigRequest) 94 | returns (SetMissionControlConfigResponse); 95 | 96 | /* 97 | Deprecated. QueryProbability returns the current success probability 98 | estimate for a given node pair and amount. The call returns a zero success 99 | probability if no channel is available or if the amount violates min/max 100 | HTLC constraints. 101 | */ 102 | rpc QueryProbability (QueryProbabilityRequest) 103 | returns (QueryProbabilityResponse); 104 | 105 | /* 106 | BuildRoute builds a fully specified route based on a list of hop public 107 | keys. It retrieves the relevant channel policies from the graph in order to 108 | calculate the correct fees and time locks. 109 | */ 110 | rpc BuildRoute (BuildRouteRequest) returns (BuildRouteResponse); 111 | 112 | /* 113 | SubscribeHtlcEvents creates a uni-directional stream from the server to 114 | the client which delivers a stream of htlc events. 115 | */ 116 | rpc SubscribeHtlcEvents (SubscribeHtlcEventsRequest) 117 | returns (stream HtlcEvent); 118 | 119 | /* 120 | Deprecated, use SendPaymentV2. SendPayment attempts to route a payment 121 | described by the passed PaymentRequest to the final destination. The call 122 | returns a stream of payment status updates. 123 | */ 124 | rpc SendPayment (SendPaymentRequest) returns (stream PaymentStatus) { 125 | option deprecated = true; 126 | } 127 | 128 | /* 129 | Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for 130 | the payment identified by the payment hash. 131 | */ 132 | rpc TrackPayment (TrackPaymentRequest) returns (stream PaymentStatus) { 133 | option deprecated = true; 134 | } 135 | 136 | /** 137 | HtlcInterceptor dispatches a bi-directional streaming RPC in which 138 | Forwarded HTLC requests are sent to the client and the client responds with 139 | a boolean that tells LND if this htlc should be intercepted. 140 | In case of interception, the htlc can be either settled, cancelled or 141 | resumed later by using the ResolveHoldForward endpoint. 142 | */ 143 | rpc HtlcInterceptor (stream ForwardHtlcInterceptResponse) 144 | returns (stream ForwardHtlcInterceptRequest); 145 | 146 | /* 147 | UpdateChanStatus attempts to manually set the state of a channel 148 | (enabled, disabled, or auto). A manual "disable" request will cause the 149 | channel to stay disabled until a subsequent manual request of either 150 | "enable" or "auto". 151 | */ 152 | rpc UpdateChanStatus (UpdateChanStatusRequest) 153 | returns (UpdateChanStatusResponse); 154 | } 155 | 156 | message SendPaymentRequest { 157 | // The identity pubkey of the payment recipient 158 | bytes dest = 1; 159 | 160 | /* 161 | Number of satoshis to send. 162 | 163 | The fields amt and amt_msat are mutually exclusive. 164 | */ 165 | int64 amt = 2; 166 | 167 | /* 168 | Number of millisatoshis to send. 169 | 170 | The fields amt and amt_msat are mutually exclusive. 171 | */ 172 | int64 amt_msat = 12; 173 | 174 | // The hash to use within the payment's HTLC 175 | bytes payment_hash = 3; 176 | 177 | /* 178 | The CLTV delta from the current height that should be used to set the 179 | timelock for the final hop. 180 | */ 181 | int32 final_cltv_delta = 4; 182 | 183 | // An optional payment addr to be included within the last hop of the route. 184 | bytes payment_addr = 20; 185 | 186 | /* 187 | A bare-bones invoice for a payment within the Lightning Network. With the 188 | details of the invoice, the sender has all the data necessary to send a 189 | payment to the recipient. The amount in the payment request may be zero. In 190 | that case it is required to set the amt field as well. If no payment request 191 | is specified, the following fields are required: dest, amt and payment_hash. 192 | */ 193 | string payment_request = 5; 194 | 195 | /* 196 | An upper limit on the amount of time we should spend when attempting to 197 | fulfill the payment. This is expressed in seconds. If we cannot make a 198 | successful payment within this time frame, an error will be returned. 199 | This field must be non-zero. 200 | */ 201 | int32 timeout_seconds = 6; 202 | 203 | /* 204 | The maximum number of satoshis that will be paid as a fee of the payment. 205 | If this field is left to the default value of 0, only zero-fee routes will 206 | be considered. This usually means single hop routes connecting directly to 207 | the destination. To send the payment without a fee limit, use max int here. 208 | 209 | The fields fee_limit_sat and fee_limit_msat are mutually exclusive. 210 | */ 211 | int64 fee_limit_sat = 7; 212 | 213 | /* 214 | The maximum number of millisatoshis that will be paid as a fee of the 215 | payment. If this field is left to the default value of 0, only zero-fee 216 | routes will be considered. This usually means single hop routes connecting 217 | directly to the destination. To send the payment without a fee limit, use 218 | max int here. 219 | 220 | The fields fee_limit_sat and fee_limit_msat are mutually exclusive. 221 | */ 222 | int64 fee_limit_msat = 13; 223 | 224 | /* 225 | Deprecated, use outgoing_chan_ids. The channel id of the channel that must 226 | be taken to the first hop. If zero, any channel may be used (unless 227 | outgoing_chan_ids are set). 228 | */ 229 | uint64 outgoing_chan_id = 8 [jstype = JS_STRING, deprecated = true]; 230 | 231 | /* 232 | The channel ids of the channels are allowed for the first hop. If empty, 233 | any channel may be used. 234 | */ 235 | repeated uint64 outgoing_chan_ids = 19; 236 | 237 | /* 238 | The pubkey of the last hop of the route. If empty, any hop may be used. 239 | */ 240 | bytes last_hop_pubkey = 14; 241 | 242 | /* 243 | An optional maximum total time lock for the route. This should not exceed 244 | lnd's `--max-cltv-expiry` setting. If zero, then the value of 245 | `--max-cltv-expiry` is enforced. 246 | */ 247 | int32 cltv_limit = 9; 248 | 249 | /* 250 | Optional route hints to reach the destination through private channels. 251 | */ 252 | repeated lnrpc.RouteHint route_hints = 10; 253 | 254 | /* 255 | An optional field that can be used to pass an arbitrary set of TLV records 256 | to a peer which understands the new records. This can be used to pass 257 | application specific data during the payment attempt. Record types are 258 | required to be in the custom range >= 65536. When using REST, the values 259 | must be encoded as base64. 260 | */ 261 | map dest_custom_records = 11; 262 | 263 | // If set, circular payments to self are permitted. 264 | bool allow_self_payment = 15; 265 | 266 | /* 267 | Features assumed to be supported by the final node. All transitive feature 268 | dependencies must also be set properly. For a given feature bit pair, either 269 | optional or remote may be set, but not both. If this field is nil or empty, 270 | the router will try to load destination features from the graph as a 271 | fallback. 272 | */ 273 | repeated lnrpc.FeatureBit dest_features = 16; 274 | 275 | /* 276 | The maximum number of partial payments that may be use to complete the full 277 | amount. 278 | */ 279 | uint32 max_parts = 17; 280 | 281 | /* 282 | If set, only the final payment update is streamed back. Intermediate updates 283 | that show which htlcs are still in flight are suppressed. 284 | */ 285 | bool no_inflight_updates = 18; 286 | 287 | /* 288 | The largest payment split that should be attempted when making a payment if 289 | splitting is necessary. Setting this value will effectively cause lnd to 290 | split more aggressively, vs only when it thinks it needs to. Note that this 291 | value is in milli-satoshis. 292 | */ 293 | uint64 max_shard_size_msat = 21; 294 | 295 | /* 296 | If set, an AMP-payment will be attempted. 297 | */ 298 | bool amp = 22; 299 | 300 | /* 301 | The time preference for this payment. Set to -1 to optimize for fees 302 | only, to 1 to optimize for reliability only or a value inbetween for a mix. 303 | */ 304 | double time_pref = 23; 305 | } 306 | 307 | message TrackPaymentRequest { 308 | // The hash of the payment to look up. 309 | bytes payment_hash = 1; 310 | 311 | /* 312 | If set, only the final payment update is streamed back. Intermediate updates 313 | that show which htlcs are still in flight are suppressed. 314 | */ 315 | bool no_inflight_updates = 2; 316 | } 317 | 318 | message TrackPaymentsRequest { 319 | /* 320 | If set, only the final payment updates are streamed back. Intermediate 321 | updates that show which htlcs are still in flight are suppressed. 322 | */ 323 | bool no_inflight_updates = 1; 324 | } 325 | 326 | message RouteFeeRequest { 327 | /* 328 | The destination once wishes to obtain a routing fee quote to. 329 | */ 330 | bytes dest = 1; 331 | 332 | /* 333 | The amount one wishes to send to the target destination. 334 | */ 335 | int64 amt_sat = 2; 336 | } 337 | 338 | message RouteFeeResponse { 339 | /* 340 | A lower bound of the estimated fee to the target destination within the 341 | network, expressed in milli-satoshis. 342 | */ 343 | int64 routing_fee_msat = 1; 344 | 345 | /* 346 | An estimate of the worst case time delay that can occur. Note that callers 347 | will still need to factor in the final CLTV delta of the last hop into this 348 | value. 349 | */ 350 | int64 time_lock_delay = 2; 351 | } 352 | 353 | message SendToRouteRequest { 354 | // The payment hash to use for the HTLC. 355 | bytes payment_hash = 1; 356 | 357 | // Route that should be used to attempt to complete the payment. 358 | lnrpc.Route route = 2; 359 | 360 | /* 361 | Whether the payment should be marked as failed when a temporary error is 362 | returned from the given route. Set it to true so the payment won't be 363 | failed unless a terminal error is occurred, such as payment timeout, no 364 | routes, incorrect payment details, or insufficient funds. 365 | */ 366 | bool skip_temp_err = 3; 367 | } 368 | 369 | message SendToRouteResponse { 370 | // The preimage obtained by making the payment. 371 | bytes preimage = 1; 372 | 373 | // The failure message in case the payment failed. 374 | lnrpc.Failure failure = 2; 375 | } 376 | 377 | message ResetMissionControlRequest { 378 | } 379 | 380 | message ResetMissionControlResponse { 381 | } 382 | 383 | message QueryMissionControlRequest { 384 | } 385 | 386 | // QueryMissionControlResponse contains mission control state. 387 | message QueryMissionControlResponse { 388 | reserved 1; 389 | 390 | // Node pair-level mission control state. 391 | repeated PairHistory pairs = 2; 392 | } 393 | 394 | message XImportMissionControlRequest { 395 | // Node pair-level mission control state to be imported. 396 | repeated PairHistory pairs = 1; 397 | 398 | // Whether to force override MC pair history. Note that even with force 399 | // override the failure pair is imported before the success pair and both 400 | // still clamp existing failure/success amounts. 401 | bool force = 2; 402 | } 403 | 404 | message XImportMissionControlResponse { 405 | } 406 | 407 | // PairHistory contains the mission control state for a particular node pair. 408 | message PairHistory { 409 | // The source node pubkey of the pair. 410 | bytes node_from = 1; 411 | 412 | // The destination node pubkey of the pair. 413 | bytes node_to = 2; 414 | 415 | reserved 3, 4, 5, 6; 416 | 417 | PairData history = 7; 418 | } 419 | 420 | message PairData { 421 | // Time of last failure. 422 | int64 fail_time = 1; 423 | 424 | /* 425 | Lowest amount that failed to forward rounded to whole sats. This may be 426 | set to zero if the failure is independent of amount. 427 | */ 428 | int64 fail_amt_sat = 2; 429 | 430 | /* 431 | Lowest amount that failed to forward in millisats. This may be 432 | set to zero if the failure is independent of amount. 433 | */ 434 | int64 fail_amt_msat = 4; 435 | 436 | reserved 3; 437 | 438 | // Time of last success. 439 | int64 success_time = 5; 440 | 441 | // Highest amount that we could successfully forward rounded to whole sats. 442 | int64 success_amt_sat = 6; 443 | 444 | // Highest amount that we could successfully forward in millisats. 445 | int64 success_amt_msat = 7; 446 | } 447 | 448 | message GetMissionControlConfigRequest { 449 | } 450 | 451 | message GetMissionControlConfigResponse { 452 | /* 453 | Mission control's currently active config. 454 | */ 455 | MissionControlConfig config = 1; 456 | } 457 | 458 | message SetMissionControlConfigRequest { 459 | /* 460 | The config to set for mission control. Note that all values *must* be set, 461 | because the full config will be applied. 462 | */ 463 | MissionControlConfig config = 1; 464 | } 465 | 466 | message SetMissionControlConfigResponse { 467 | } 468 | 469 | message MissionControlConfig { 470 | /* 471 | Deprecated, use AprioriParameters. The amount of time mission control will 472 | take to restore a penalized node or channel back to 50% success probability, 473 | expressed in seconds. Setting this value to a higher value will penalize 474 | failures for longer, making mission control less likely to route through 475 | nodes and channels that we have previously recorded failures for. 476 | */ 477 | uint64 half_life_seconds = 1 [deprecated = true]; 478 | 479 | /* 480 | Deprecated, use AprioriParameters. The probability of success mission 481 | control should assign to hop in a route where it has no other information 482 | available. Higher values will make mission control more willing to try hops 483 | that we have no information about, lower values will discourage trying these 484 | hops. 485 | */ 486 | float hop_probability = 2 [deprecated = true]; 487 | 488 | /* 489 | Deprecated, use AprioriParameters. The importance that mission control 490 | should place on historical results, expressed as a value in [0;1]. Setting 491 | this value to 1 will ignore all historical payments and just use the hop 492 | probability to assess the probability of success for each hop. A zero value 493 | ignores hop probability completely and relies entirely on historical 494 | results, unless none are available. 495 | */ 496 | float weight = 3 [deprecated = true]; 497 | 498 | /* 499 | The maximum number of payment results that mission control will store. 500 | */ 501 | uint32 maximum_payment_results = 4; 502 | 503 | /* 504 | The minimum time that must have passed since the previously recorded failure 505 | before we raise the failure amount. 506 | */ 507 | uint64 minimum_failure_relax_interval = 5; 508 | 509 | enum ProbabilityModel { 510 | APRIORI = 0; 511 | BIMODAL = 1; 512 | } 513 | 514 | /* 515 | ProbabilityModel defines which probability estimator should be used in 516 | pathfinding. Note that the bimodal estimator is experimental. 517 | */ 518 | ProbabilityModel model = 6; 519 | 520 | /* 521 | EstimatorConfig is populated dependent on the estimator type. 522 | */ 523 | oneof EstimatorConfig { 524 | AprioriParameters apriori = 7; 525 | BimodalParameters bimodal = 8; 526 | } 527 | } 528 | 529 | message BimodalParameters { 530 | /* 531 | NodeWeight defines how strongly other previous forwardings on channels of a 532 | router should be taken into account when computing a channel's probability 533 | to route. The allowed values are in the range [0, 1], where a value of 0 534 | means that only direct information about a channel is taken into account. 535 | */ 536 | double node_weight = 1; 537 | 538 | /* 539 | ScaleMsat describes the scale over which channels statistically have some 540 | liquidity left. The value determines how quickly the bimodal distribution 541 | drops off from the edges of a channel. A larger value (compared to typical 542 | channel capacities) means that the drop off is slow and that channel 543 | balances are distributed more uniformly. A small value leads to the 544 | assumption of very unbalanced channels. 545 | */ 546 | uint64 scale_msat = 2; 547 | 548 | /* 549 | DecayTime describes the information decay of knowledge about previous 550 | successes and failures in channels. The smaller the decay time, the quicker 551 | we forget about past forwardings. 552 | */ 553 | uint64 decay_time = 3; 554 | } 555 | 556 | message AprioriParameters { 557 | /* 558 | The amount of time mission control will take to restore a penalized node 559 | or channel back to 50% success probability, expressed in seconds. Setting 560 | this value to a higher value will penalize failures for longer, making 561 | mission control less likely to route through nodes and channels that we 562 | have previously recorded failures for. 563 | */ 564 | uint64 half_life_seconds = 1; 565 | 566 | /* 567 | The probability of success mission control should assign to hop in a route 568 | where it has no other information available. Higher values will make mission 569 | control more willing to try hops that we have no information about, lower 570 | values will discourage trying these hops. 571 | */ 572 | double hop_probability = 2; 573 | 574 | /* 575 | The importance that mission control should place on historical results, 576 | expressed as a value in [0;1]. Setting this value to 1 will ignore all 577 | historical payments and just use the hop probability to assess the 578 | probability of success for each hop. A zero value ignores hop probability 579 | completely and relies entirely on historical results, unless none are 580 | available. 581 | */ 582 | double weight = 3; 583 | 584 | /* 585 | The fraction of a channel's capacity that we consider to have liquidity. For 586 | amounts that come close to or exceed the fraction, an additional penalty is 587 | applied. A value of 1.0 disables the capacity factor. Allowed values are in 588 | [0.75, 1.0]. 589 | */ 590 | double capacity_fraction = 4; 591 | } 592 | 593 | message QueryProbabilityRequest { 594 | // The source node pubkey of the pair. 595 | bytes from_node = 1; 596 | 597 | // The destination node pubkey of the pair. 598 | bytes to_node = 2; 599 | 600 | // The amount for which to calculate a probability. 601 | int64 amt_msat = 3; 602 | } 603 | 604 | message QueryProbabilityResponse { 605 | // The success probability for the requested pair. 606 | double probability = 1; 607 | 608 | // The historical data for the requested pair. 609 | PairData history = 2; 610 | } 611 | 612 | message BuildRouteRequest { 613 | /* 614 | The amount to send expressed in msat. If set to zero, the minimum routable 615 | amount is used. 616 | */ 617 | int64 amt_msat = 1; 618 | 619 | /* 620 | CLTV delta from the current height that should be used for the timelock 621 | of the final hop 622 | */ 623 | int32 final_cltv_delta = 2; 624 | 625 | /* 626 | The channel id of the channel that must be taken to the first hop. If zero, 627 | any channel may be used. 628 | */ 629 | uint64 outgoing_chan_id = 3 [jstype = JS_STRING]; 630 | 631 | /* 632 | A list of hops that defines the route. This does not include the source hop 633 | pubkey. 634 | */ 635 | repeated bytes hop_pubkeys = 4; 636 | 637 | // An optional payment addr to be included within the last hop of the route. 638 | bytes payment_addr = 5; 639 | } 640 | 641 | message BuildRouteResponse { 642 | /* 643 | Fully specified route that can be used to execute the payment. 644 | */ 645 | lnrpc.Route route = 1; 646 | } 647 | 648 | message SubscribeHtlcEventsRequest { 649 | } 650 | 651 | /* 652 | HtlcEvent contains the htlc event that was processed. These are served on a 653 | best-effort basis; events are not persisted, delivery is not guaranteed 654 | (in the event of a crash in the switch, forward events may be lost) and 655 | some events may be replayed upon restart. Events consumed from this package 656 | should be de-duplicated by the htlc's unique combination of incoming and 657 | outgoing channel id and htlc id. [EXPERIMENTAL] 658 | */ 659 | message HtlcEvent { 660 | /* 661 | The short channel id that the incoming htlc arrived at our node on. This 662 | value is zero for sends. 663 | */ 664 | uint64 incoming_channel_id = 1; 665 | 666 | /* 667 | The short channel id that the outgoing htlc left our node on. This value 668 | is zero for receives. 669 | */ 670 | uint64 outgoing_channel_id = 2; 671 | 672 | /* 673 | Incoming id is the index of the incoming htlc in the incoming channel. 674 | This value is zero for sends. 675 | */ 676 | uint64 incoming_htlc_id = 3; 677 | 678 | /* 679 | Outgoing id is the index of the outgoing htlc in the outgoing channel. 680 | This value is zero for receives. 681 | */ 682 | uint64 outgoing_htlc_id = 4; 683 | 684 | /* 685 | The time in unix nanoseconds that the event occurred. 686 | */ 687 | uint64 timestamp_ns = 5; 688 | 689 | enum EventType { 690 | UNKNOWN = 0; 691 | SEND = 1; 692 | RECEIVE = 2; 693 | FORWARD = 3; 694 | } 695 | 696 | /* 697 | The event type indicates whether the htlc was part of a send, receive or 698 | forward. 699 | */ 700 | EventType event_type = 6; 701 | 702 | oneof event { 703 | ForwardEvent forward_event = 7; 704 | ForwardFailEvent forward_fail_event = 8; 705 | SettleEvent settle_event = 9; 706 | LinkFailEvent link_fail_event = 10; 707 | SubscribedEvent subscribed_event = 11; 708 | FinalHtlcEvent final_htlc_event = 12; 709 | } 710 | } 711 | 712 | message HtlcInfo { 713 | // The timelock on the incoming htlc. 714 | uint32 incoming_timelock = 1; 715 | 716 | // The timelock on the outgoing htlc. 717 | uint32 outgoing_timelock = 2; 718 | 719 | // The amount of the incoming htlc. 720 | uint64 incoming_amt_msat = 3; 721 | 722 | // The amount of the outgoing htlc. 723 | uint64 outgoing_amt_msat = 4; 724 | } 725 | 726 | message ForwardEvent { 727 | // Info contains details about the htlc that was forwarded. 728 | HtlcInfo info = 1; 729 | } 730 | 731 | message ForwardFailEvent { 732 | } 733 | 734 | message SettleEvent { 735 | // The revealed preimage. 736 | bytes preimage = 1; 737 | } 738 | 739 | message FinalHtlcEvent { 740 | bool settled = 1; 741 | bool offchain = 2; 742 | } 743 | 744 | message SubscribedEvent { 745 | } 746 | 747 | message LinkFailEvent { 748 | // Info contains details about the htlc that we failed. 749 | HtlcInfo info = 1; 750 | 751 | // FailureCode is the BOLT error code for the failure. 752 | lnrpc.Failure.FailureCode wire_failure = 2; 753 | 754 | /* 755 | FailureDetail provides additional information about the reason for the 756 | failure. This detail enriches the information provided by the wire message 757 | and may be 'no detail' if the wire message requires no additional metadata. 758 | */ 759 | FailureDetail failure_detail = 3; 760 | 761 | // A string representation of the link failure. 762 | string failure_string = 4; 763 | } 764 | 765 | enum FailureDetail { 766 | UNKNOWN = 0; 767 | NO_DETAIL = 1; 768 | ONION_DECODE = 2; 769 | LINK_NOT_ELIGIBLE = 3; 770 | ON_CHAIN_TIMEOUT = 4; 771 | HTLC_EXCEEDS_MAX = 5; 772 | INSUFFICIENT_BALANCE = 6; 773 | INCOMPLETE_FORWARD = 7; 774 | HTLC_ADD_FAILED = 8; 775 | FORWARDS_DISABLED = 9; 776 | INVOICE_CANCELED = 10; 777 | INVOICE_UNDERPAID = 11; 778 | INVOICE_EXPIRY_TOO_SOON = 12; 779 | INVOICE_NOT_OPEN = 13; 780 | MPP_INVOICE_TIMEOUT = 14; 781 | ADDRESS_MISMATCH = 15; 782 | SET_TOTAL_MISMATCH = 16; 783 | SET_TOTAL_TOO_LOW = 17; 784 | SET_OVERPAID = 18; 785 | UNKNOWN_INVOICE = 19; 786 | INVALID_KEYSEND = 20; 787 | MPP_IN_PROGRESS = 21; 788 | CIRCULAR_ROUTE = 22; 789 | } 790 | 791 | enum PaymentState { 792 | /* 793 | Payment is still in flight. 794 | */ 795 | IN_FLIGHT = 0; 796 | 797 | /* 798 | Payment completed successfully. 799 | */ 800 | SUCCEEDED = 1; 801 | 802 | /* 803 | There are more routes to try, but the payment timeout was exceeded. 804 | */ 805 | FAILED_TIMEOUT = 2; 806 | 807 | /* 808 | All possible routes were tried and failed permanently. Or were no 809 | routes to the destination at all. 810 | */ 811 | FAILED_NO_ROUTE = 3; 812 | 813 | /* 814 | A non-recoverable error has occurred. 815 | */ 816 | FAILED_ERROR = 4; 817 | 818 | /* 819 | Payment details incorrect (unknown hash, invalid amt or 820 | invalid final cltv delta) 821 | */ 822 | FAILED_INCORRECT_PAYMENT_DETAILS = 5; 823 | 824 | /* 825 | Insufficient local balance. 826 | */ 827 | FAILED_INSUFFICIENT_BALANCE = 6; 828 | } 829 | 830 | message PaymentStatus { 831 | // Current state the payment is in. 832 | PaymentState state = 1; 833 | 834 | /* 835 | The pre-image of the payment when state is SUCCEEDED. 836 | */ 837 | bytes preimage = 2; 838 | 839 | reserved 3; 840 | 841 | /* 842 | The HTLCs made in attempt to settle the payment [EXPERIMENTAL]. 843 | */ 844 | repeated lnrpc.HTLCAttempt htlcs = 4; 845 | } 846 | 847 | message CircuitKey { 848 | /// The id of the channel that the is part of this circuit. 849 | uint64 chan_id = 1; 850 | 851 | /// The index of the incoming htlc in the incoming channel. 852 | uint64 htlc_id = 2; 853 | } 854 | 855 | message ForwardHtlcInterceptRequest { 856 | /* 857 | The key of this forwarded htlc. It defines the incoming channel id and 858 | the index in this channel. 859 | */ 860 | CircuitKey incoming_circuit_key = 1; 861 | 862 | // The incoming htlc amount. 863 | uint64 incoming_amount_msat = 5; 864 | 865 | // The incoming htlc expiry. 866 | uint32 incoming_expiry = 6; 867 | 868 | /* 869 | The htlc payment hash. This value is not guaranteed to be unique per 870 | request. 871 | */ 872 | bytes payment_hash = 2; 873 | 874 | // The requested outgoing channel id for this forwarded htlc. Because of 875 | // non-strict forwarding, this isn't necessarily the channel over which the 876 | // packet will be forwarded eventually. A different channel to the same peer 877 | // may be selected as well. 878 | uint64 outgoing_requested_chan_id = 7; 879 | 880 | // The outgoing htlc amount. 881 | uint64 outgoing_amount_msat = 3; 882 | 883 | // The outgoing htlc expiry. 884 | uint32 outgoing_expiry = 4; 885 | 886 | // Any custom records that were present in the payload. 887 | map custom_records = 8; 888 | 889 | // The onion blob for the next hop 890 | bytes onion_blob = 9; 891 | 892 | // The block height at which this htlc will be auto-failed to prevent the 893 | // channel from force-closing. 894 | int32 auto_fail_height = 10; 895 | } 896 | 897 | /** 898 | ForwardHtlcInterceptResponse enables the caller to resolve a previously hold 899 | forward. The caller can choose either to: 900 | - `Resume`: Execute the default behavior (usually forward). 901 | - `Reject`: Fail the htlc backwards. 902 | - `Settle`: Settle this htlc with a given preimage. 903 | */ 904 | message ForwardHtlcInterceptResponse { 905 | /** 906 | The key of this forwarded htlc. It defines the incoming channel id and 907 | the index in this channel. 908 | */ 909 | CircuitKey incoming_circuit_key = 1; 910 | 911 | // The resolve action for this intercepted htlc. 912 | ResolveHoldForwardAction action = 2; 913 | 914 | // The preimage in case the resolve action is Settle. 915 | bytes preimage = 3; 916 | 917 | // Encrypted failure message in case the resolve action is Fail. 918 | // 919 | // If failure_message is specified, the failure_code field must be set 920 | // to zero. 921 | bytes failure_message = 4; 922 | 923 | // Return the specified failure code in case the resolve action is Fail. The 924 | // message data fields are populated automatically. 925 | // 926 | // If a non-zero failure_code is specified, failure_message must not be set. 927 | // 928 | // For backwards-compatibility reasons, TEMPORARY_CHANNEL_FAILURE is the 929 | // default value for this field. 930 | lnrpc.Failure.FailureCode failure_code = 5; 931 | } 932 | 933 | enum ResolveHoldForwardAction { 934 | SETTLE = 0; 935 | FAIL = 1; 936 | RESUME = 2; 937 | } 938 | 939 | message UpdateChanStatusRequest { 940 | lnrpc.ChannelPoint chan_point = 1; 941 | 942 | ChanStatusAction action = 2; 943 | } 944 | 945 | enum ChanStatusAction { 946 | ENABLE = 0; 947 | DISABLE = 1; 948 | AUTO = 2; 949 | } 950 | 951 | message UpdateChanStatusResponse { 952 | } 953 | -------------------------------------------------------------------------------- /protos/router_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: router.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf.internal import builder as _builder 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import descriptor_pool as _descriptor_pool 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | import lightning_pb2 as lightning__pb2 15 | 16 | 17 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0crouter.proto\x12\trouterrpc\x1a\x0flightning.proto\"\xb7\x05\n\x12SendPaymentRequest\x12\x0c\n\x04\x64\x65st\x18\x01 \x01(\x0c\x12\x0b\n\x03\x61mt\x18\x02 \x01(\x03\x12\x10\n\x08\x61mt_msat\x18\x0c \x01(\x03\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12\x18\n\x10\x66inal_cltv_delta\x18\x04 \x01(\x05\x12\x14\n\x0cpayment_addr\x18\x14 \x01(\x0c\x12\x17\n\x0fpayment_request\x18\x05 \x01(\t\x12\x17\n\x0ftimeout_seconds\x18\x06 \x01(\x05\x12\x15\n\rfee_limit_sat\x18\x07 \x01(\x03\x12\x16\n\x0e\x66\x65\x65_limit_msat\x18\r \x01(\x03\x12\x1e\n\x10outgoing_chan_id\x18\x08 \x01(\x04\x42\x04\x18\x01\x30\x01\x12\x19\n\x11outgoing_chan_ids\x18\x13 \x03(\x04\x12\x17\n\x0flast_hop_pubkey\x18\x0e \x01(\x0c\x12\x12\n\ncltv_limit\x18\t \x01(\x05\x12%\n\x0broute_hints\x18\n \x03(\x0b\x32\x10.lnrpc.RouteHint\x12Q\n\x13\x64\x65st_custom_records\x18\x0b \x03(\x0b\x32\x34.routerrpc.SendPaymentRequest.DestCustomRecordsEntry\x12\x1a\n\x12\x61llow_self_payment\x18\x0f \x01(\x08\x12(\n\rdest_features\x18\x10 \x03(\x0e\x32\x11.lnrpc.FeatureBit\x12\x11\n\tmax_parts\x18\x11 \x01(\r\x12\x1b\n\x13no_inflight_updates\x18\x12 \x01(\x08\x12\x1b\n\x13max_shard_size_msat\x18\x15 \x01(\x04\x12\x0b\n\x03\x61mp\x18\x16 \x01(\x08\x12\x11\n\ttime_pref\x18\x17 \x01(\x01\x1a\x38\n\x16\x44\x65stCustomRecordsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x04\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"H\n\x13TrackPaymentRequest\x12\x14\n\x0cpayment_hash\x18\x01 \x01(\x0c\x12\x1b\n\x13no_inflight_updates\x18\x02 \x01(\x08\"3\n\x14TrackPaymentsRequest\x12\x1b\n\x13no_inflight_updates\x18\x01 \x01(\x08\"0\n\x0fRouteFeeRequest\x12\x0c\n\x04\x64\x65st\x18\x01 \x01(\x0c\x12\x0f\n\x07\x61mt_sat\x18\x02 \x01(\x03\"E\n\x10RouteFeeResponse\x12\x18\n\x10routing_fee_msat\x18\x01 \x01(\x03\x12\x17\n\x0ftime_lock_delay\x18\x02 \x01(\x03\"^\n\x12SendToRouteRequest\x12\x14\n\x0cpayment_hash\x18\x01 \x01(\x0c\x12\x1b\n\x05route\x18\x02 \x01(\x0b\x32\x0c.lnrpc.Route\x12\x15\n\rskip_temp_err\x18\x03 \x01(\x08\"H\n\x13SendToRouteResponse\x12\x10\n\x08preimage\x18\x01 \x01(\x0c\x12\x1f\n\x07\x66\x61ilure\x18\x02 \x01(\x0b\x32\x0e.lnrpc.Failure\"\x1c\n\x1aResetMissionControlRequest\"\x1d\n\x1bResetMissionControlResponse\"\x1c\n\x1aQueryMissionControlRequest\"J\n\x1bQueryMissionControlResponse\x12%\n\x05pairs\x18\x02 \x03(\x0b\x32\x16.routerrpc.PairHistoryJ\x04\x08\x01\x10\x02\"T\n\x1cXImportMissionControlRequest\x12%\n\x05pairs\x18\x01 \x03(\x0b\x32\x16.routerrpc.PairHistory\x12\r\n\x05\x66orce\x18\x02 \x01(\x08\"\x1f\n\x1dXImportMissionControlResponse\"o\n\x0bPairHistory\x12\x11\n\tnode_from\x18\x01 \x01(\x0c\x12\x0f\n\x07node_to\x18\x02 \x01(\x0c\x12$\n\x07history\x18\x07 \x01(\x0b\x32\x13.routerrpc.PairDataJ\x04\x08\x03\x10\x04J\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06J\x04\x08\x06\x10\x07\"\x99\x01\n\x08PairData\x12\x11\n\tfail_time\x18\x01 \x01(\x03\x12\x14\n\x0c\x66\x61il_amt_sat\x18\x02 \x01(\x03\x12\x15\n\rfail_amt_msat\x18\x04 \x01(\x03\x12\x14\n\x0csuccess_time\x18\x05 \x01(\x03\x12\x17\n\x0fsuccess_amt_sat\x18\x06 \x01(\x03\x12\x18\n\x10success_amt_msat\x18\x07 \x01(\x03J\x04\x08\x03\x10\x04\" \n\x1eGetMissionControlConfigRequest\"R\n\x1fGetMissionControlConfigResponse\x12/\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x1f.routerrpc.MissionControlConfig\"Q\n\x1eSetMissionControlConfigRequest\x12/\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x1f.routerrpc.MissionControlConfig\"!\n\x1fSetMissionControlConfigResponse\"\x93\x03\n\x14MissionControlConfig\x12\x1d\n\x11half_life_seconds\x18\x01 \x01(\x04\x42\x02\x18\x01\x12\x1b\n\x0fhop_probability\x18\x02 \x01(\x02\x42\x02\x18\x01\x12\x12\n\x06weight\x18\x03 \x01(\x02\x42\x02\x18\x01\x12\x1f\n\x17maximum_payment_results\x18\x04 \x01(\r\x12&\n\x1eminimum_failure_relax_interval\x18\x05 \x01(\x04\x12?\n\x05model\x18\x06 \x01(\x0e\x32\x30.routerrpc.MissionControlConfig.ProbabilityModel\x12/\n\x07\x61priori\x18\x07 \x01(\x0b\x32\x1c.routerrpc.AprioriParametersH\x00\x12/\n\x07\x62imodal\x18\x08 \x01(\x0b\x32\x1c.routerrpc.BimodalParametersH\x00\",\n\x10ProbabilityModel\x12\x0b\n\x07\x41PRIORI\x10\x00\x12\x0b\n\x07\x42IMODAL\x10\x01\x42\x11\n\x0f\x45stimatorConfig\"P\n\x11\x42imodalParameters\x12\x13\n\x0bnode_weight\x18\x01 \x01(\x01\x12\x12\n\nscale_msat\x18\x02 \x01(\x04\x12\x12\n\ndecay_time\x18\x03 \x01(\x04\"r\n\x11\x41prioriParameters\x12\x19\n\x11half_life_seconds\x18\x01 \x01(\x04\x12\x17\n\x0fhop_probability\x18\x02 \x01(\x01\x12\x0e\n\x06weight\x18\x03 \x01(\x01\x12\x19\n\x11\x63\x61pacity_fraction\x18\x04 \x01(\x01\"O\n\x17QueryProbabilityRequest\x12\x11\n\tfrom_node\x18\x01 \x01(\x0c\x12\x0f\n\x07to_node\x18\x02 \x01(\x0c\x12\x10\n\x08\x61mt_msat\x18\x03 \x01(\x03\"U\n\x18QueryProbabilityResponse\x12\x13\n\x0bprobability\x18\x01 \x01(\x01\x12$\n\x07history\x18\x02 \x01(\x0b\x32\x13.routerrpc.PairData\"\x88\x01\n\x11\x42uildRouteRequest\x12\x10\n\x08\x61mt_msat\x18\x01 \x01(\x03\x12\x18\n\x10\x66inal_cltv_delta\x18\x02 \x01(\x05\x12\x1c\n\x10outgoing_chan_id\x18\x03 \x01(\x04\x42\x02\x30\x01\x12\x13\n\x0bhop_pubkeys\x18\x04 \x03(\x0c\x12\x14\n\x0cpayment_addr\x18\x05 \x01(\x0c\"1\n\x12\x42uildRouteResponse\x12\x1b\n\x05route\x18\x01 \x01(\x0b\x32\x0c.lnrpc.Route\"\x1c\n\x1aSubscribeHtlcEventsRequest\"\xcb\x04\n\tHtlcEvent\x12\x1b\n\x13incoming_channel_id\x18\x01 \x01(\x04\x12\x1b\n\x13outgoing_channel_id\x18\x02 \x01(\x04\x12\x18\n\x10incoming_htlc_id\x18\x03 \x01(\x04\x12\x18\n\x10outgoing_htlc_id\x18\x04 \x01(\x04\x12\x14\n\x0ctimestamp_ns\x18\x05 \x01(\x04\x12\x32\n\nevent_type\x18\x06 \x01(\x0e\x32\x1e.routerrpc.HtlcEvent.EventType\x12\x30\n\rforward_event\x18\x07 \x01(\x0b\x32\x17.routerrpc.ForwardEventH\x00\x12\x39\n\x12\x66orward_fail_event\x18\x08 \x01(\x0b\x32\x1b.routerrpc.ForwardFailEventH\x00\x12.\n\x0csettle_event\x18\t \x01(\x0b\x32\x16.routerrpc.SettleEventH\x00\x12\x33\n\x0flink_fail_event\x18\n \x01(\x0b\x32\x18.routerrpc.LinkFailEventH\x00\x12\x36\n\x10subscribed_event\x18\x0b \x01(\x0b\x32\x1a.routerrpc.SubscribedEventH\x00\x12\x35\n\x10\x66inal_htlc_event\x18\x0c \x01(\x0b\x32\x19.routerrpc.FinalHtlcEventH\x00\"<\n\tEventType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x08\n\x04SEND\x10\x01\x12\x0b\n\x07RECEIVE\x10\x02\x12\x0b\n\x07\x46ORWARD\x10\x03\x42\x07\n\x05\x65vent\"v\n\x08HtlcInfo\x12\x19\n\x11incoming_timelock\x18\x01 \x01(\r\x12\x19\n\x11outgoing_timelock\x18\x02 \x01(\r\x12\x19\n\x11incoming_amt_msat\x18\x03 \x01(\x04\x12\x19\n\x11outgoing_amt_msat\x18\x04 \x01(\x04\"1\n\x0c\x46orwardEvent\x12!\n\x04info\x18\x01 \x01(\x0b\x32\x13.routerrpc.HtlcInfo\"\x12\n\x10\x46orwardFailEvent\"\x1f\n\x0bSettleEvent\x12\x10\n\x08preimage\x18\x01 \x01(\x0c\"3\n\x0e\x46inalHtlcEvent\x12\x0f\n\x07settled\x18\x01 \x01(\x08\x12\x10\n\x08offchain\x18\x02 \x01(\x08\"\x11\n\x0fSubscribedEvent\"\xae\x01\n\rLinkFailEvent\x12!\n\x04info\x18\x01 \x01(\x0b\x32\x13.routerrpc.HtlcInfo\x12\x30\n\x0cwire_failure\x18\x02 \x01(\x0e\x32\x1a.lnrpc.Failure.FailureCode\x12\x30\n\x0e\x66\x61ilure_detail\x18\x03 \x01(\x0e\x32\x18.routerrpc.FailureDetail\x12\x16\n\x0e\x66\x61ilure_string\x18\x04 \x01(\t\"r\n\rPaymentStatus\x12&\n\x05state\x18\x01 \x01(\x0e\x32\x17.routerrpc.PaymentState\x12\x10\n\x08preimage\x18\x02 \x01(\x0c\x12!\n\x05htlcs\x18\x04 \x03(\x0b\x32\x12.lnrpc.HTLCAttemptJ\x04\x08\x03\x10\x04\".\n\nCircuitKey\x12\x0f\n\x07\x63han_id\x18\x01 \x01(\x04\x12\x0f\n\x07htlc_id\x18\x02 \x01(\x04\"\xb1\x03\n\x1b\x46orwardHtlcInterceptRequest\x12\x33\n\x14incoming_circuit_key\x18\x01 \x01(\x0b\x32\x15.routerrpc.CircuitKey\x12\x1c\n\x14incoming_amount_msat\x18\x05 \x01(\x04\x12\x17\n\x0fincoming_expiry\x18\x06 \x01(\r\x12\x14\n\x0cpayment_hash\x18\x02 \x01(\x0c\x12\"\n\x1aoutgoing_requested_chan_id\x18\x07 \x01(\x04\x12\x1c\n\x14outgoing_amount_msat\x18\x03 \x01(\x04\x12\x17\n\x0foutgoing_expiry\x18\x04 \x01(\r\x12Q\n\x0e\x63ustom_records\x18\x08 \x03(\x0b\x32\x39.routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry\x12\x12\n\nonion_blob\x18\t \x01(\x0c\x12\x18\n\x10\x61uto_fail_height\x18\n \x01(\x05\x1a\x34\n\x12\x43ustomRecordsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x04\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"\xe5\x01\n\x1c\x46orwardHtlcInterceptResponse\x12\x33\n\x14incoming_circuit_key\x18\x01 \x01(\x0b\x32\x15.routerrpc.CircuitKey\x12\x33\n\x06\x61\x63tion\x18\x02 \x01(\x0e\x32#.routerrpc.ResolveHoldForwardAction\x12\x10\n\x08preimage\x18\x03 \x01(\x0c\x12\x17\n\x0f\x66\x61ilure_message\x18\x04 \x01(\x0c\x12\x30\n\x0c\x66\x61ilure_code\x18\x05 \x01(\x0e\x32\x1a.lnrpc.Failure.FailureCode\"o\n\x17UpdateChanStatusRequest\x12\'\n\nchan_point\x18\x01 \x01(\x0b\x32\x13.lnrpc.ChannelPoint\x12+\n\x06\x61\x63tion\x18\x02 \x01(\x0e\x32\x1b.routerrpc.ChanStatusAction\"\x1a\n\x18UpdateChanStatusResponse*\x81\x04\n\rFailureDetail\x12\x0b\n\x07UNKNOWN\x10\x00\x12\r\n\tNO_DETAIL\x10\x01\x12\x10\n\x0cONION_DECODE\x10\x02\x12\x15\n\x11LINK_NOT_ELIGIBLE\x10\x03\x12\x14\n\x10ON_CHAIN_TIMEOUT\x10\x04\x12\x14\n\x10HTLC_EXCEEDS_MAX\x10\x05\x12\x18\n\x14INSUFFICIENT_BALANCE\x10\x06\x12\x16\n\x12INCOMPLETE_FORWARD\x10\x07\x12\x13\n\x0fHTLC_ADD_FAILED\x10\x08\x12\x15\n\x11\x46ORWARDS_DISABLED\x10\t\x12\x14\n\x10INVOICE_CANCELED\x10\n\x12\x15\n\x11INVOICE_UNDERPAID\x10\x0b\x12\x1b\n\x17INVOICE_EXPIRY_TOO_SOON\x10\x0c\x12\x14\n\x10INVOICE_NOT_OPEN\x10\r\x12\x17\n\x13MPP_INVOICE_TIMEOUT\x10\x0e\x12\x14\n\x10\x41\x44\x44RESS_MISMATCH\x10\x0f\x12\x16\n\x12SET_TOTAL_MISMATCH\x10\x10\x12\x15\n\x11SET_TOTAL_TOO_LOW\x10\x11\x12\x10\n\x0cSET_OVERPAID\x10\x12\x12\x13\n\x0fUNKNOWN_INVOICE\x10\x13\x12\x13\n\x0fINVALID_KEYSEND\x10\x14\x12\x13\n\x0fMPP_IN_PROGRESS\x10\x15\x12\x12\n\x0e\x43IRCULAR_ROUTE\x10\x16*\xae\x01\n\x0cPaymentState\x12\r\n\tIN_FLIGHT\x10\x00\x12\r\n\tSUCCEEDED\x10\x01\x12\x12\n\x0e\x46\x41ILED_TIMEOUT\x10\x02\x12\x13\n\x0f\x46\x41ILED_NO_ROUTE\x10\x03\x12\x10\n\x0c\x46\x41ILED_ERROR\x10\x04\x12$\n FAILED_INCORRECT_PAYMENT_DETAILS\x10\x05\x12\x1f\n\x1b\x46\x41ILED_INSUFFICIENT_BALANCE\x10\x06*<\n\x18ResolveHoldForwardAction\x12\n\n\x06SETTLE\x10\x00\x12\x08\n\x04\x46\x41IL\x10\x01\x12\n\n\x06RESUME\x10\x02*5\n\x10\x43hanStatusAction\x12\n\n\x06\x45NABLE\x10\x00\x12\x0b\n\x07\x44ISABLE\x10\x01\x12\x08\n\x04\x41UTO\x10\x02\x32\xb5\x0c\n\x06Router\x12@\n\rSendPaymentV2\x12\x1d.routerrpc.SendPaymentRequest\x1a\x0e.lnrpc.Payment0\x01\x12\x42\n\x0eTrackPaymentV2\x12\x1e.routerrpc.TrackPaymentRequest\x1a\x0e.lnrpc.Payment0\x01\x12\x42\n\rTrackPayments\x12\x1f.routerrpc.TrackPaymentsRequest\x1a\x0e.lnrpc.Payment0\x01\x12K\n\x10\x45stimateRouteFee\x12\x1a.routerrpc.RouteFeeRequest\x1a\x1b.routerrpc.RouteFeeResponse\x12Q\n\x0bSendToRoute\x12\x1d.routerrpc.SendToRouteRequest\x1a\x1e.routerrpc.SendToRouteResponse\"\x03\x88\x02\x01\x12\x42\n\rSendToRouteV2\x12\x1d.routerrpc.SendToRouteRequest\x1a\x12.lnrpc.HTLCAttempt\x12\x64\n\x13ResetMissionControl\x12%.routerrpc.ResetMissionControlRequest\x1a&.routerrpc.ResetMissionControlResponse\x12\x64\n\x13QueryMissionControl\x12%.routerrpc.QueryMissionControlRequest\x1a&.routerrpc.QueryMissionControlResponse\x12j\n\x15XImportMissionControl\x12\'.routerrpc.XImportMissionControlRequest\x1a(.routerrpc.XImportMissionControlResponse\x12p\n\x17GetMissionControlConfig\x12).routerrpc.GetMissionControlConfigRequest\x1a*.routerrpc.GetMissionControlConfigResponse\x12p\n\x17SetMissionControlConfig\x12).routerrpc.SetMissionControlConfigRequest\x1a*.routerrpc.SetMissionControlConfigResponse\x12[\n\x10QueryProbability\x12\".routerrpc.QueryProbabilityRequest\x1a#.routerrpc.QueryProbabilityResponse\x12I\n\nBuildRoute\x12\x1c.routerrpc.BuildRouteRequest\x1a\x1d.routerrpc.BuildRouteResponse\x12T\n\x13SubscribeHtlcEvents\x12%.routerrpc.SubscribeHtlcEventsRequest\x1a\x14.routerrpc.HtlcEvent0\x01\x12M\n\x0bSendPayment\x12\x1d.routerrpc.SendPaymentRequest\x1a\x18.routerrpc.PaymentStatus\"\x03\x88\x02\x01\x30\x01\x12O\n\x0cTrackPayment\x12\x1e.routerrpc.TrackPaymentRequest\x1a\x18.routerrpc.PaymentStatus\"\x03\x88\x02\x01\x30\x01\x12\x66\n\x0fHtlcInterceptor\x12\'.routerrpc.ForwardHtlcInterceptResponse\x1a&.routerrpc.ForwardHtlcInterceptRequest(\x01\x30\x01\x12[\n\x10UpdateChanStatus\x12\".routerrpc.UpdateChanStatusRequest\x1a#.routerrpc.UpdateChanStatusResponseB1Z/github.com/lightningnetwork/lnd/lnrpc/routerrpcb\x06proto3') 18 | 19 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) 20 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'router_pb2', globals()) 21 | if _descriptor._USE_C_DESCRIPTORS == False: 22 | 23 | DESCRIPTOR._options = None 24 | DESCRIPTOR._serialized_options = b'Z/github.com/lightningnetwork/lnd/lnrpc/routerrpc' 25 | _SENDPAYMENTREQUEST_DESTCUSTOMRECORDSENTRY._options = None 26 | _SENDPAYMENTREQUEST_DESTCUSTOMRECORDSENTRY._serialized_options = b'8\001' 27 | _SENDPAYMENTREQUEST.fields_by_name['outgoing_chan_id']._options = None 28 | _SENDPAYMENTREQUEST.fields_by_name['outgoing_chan_id']._serialized_options = b'\030\0010\001' 29 | _MISSIONCONTROLCONFIG.fields_by_name['half_life_seconds']._options = None 30 | _MISSIONCONTROLCONFIG.fields_by_name['half_life_seconds']._serialized_options = b'\030\001' 31 | _MISSIONCONTROLCONFIG.fields_by_name['hop_probability']._options = None 32 | _MISSIONCONTROLCONFIG.fields_by_name['hop_probability']._serialized_options = b'\030\001' 33 | _MISSIONCONTROLCONFIG.fields_by_name['weight']._options = None 34 | _MISSIONCONTROLCONFIG.fields_by_name['weight']._serialized_options = b'\030\001' 35 | _BUILDROUTEREQUEST.fields_by_name['outgoing_chan_id']._options = None 36 | _BUILDROUTEREQUEST.fields_by_name['outgoing_chan_id']._serialized_options = b'0\001' 37 | _FORWARDHTLCINTERCEPTREQUEST_CUSTOMRECORDSENTRY._options = None 38 | _FORWARDHTLCINTERCEPTREQUEST_CUSTOMRECORDSENTRY._serialized_options = b'8\001' 39 | _ROUTER.methods_by_name['SendToRoute']._options = None 40 | _ROUTER.methods_by_name['SendToRoute']._serialized_options = b'\210\002\001' 41 | _ROUTER.methods_by_name['SendPayment']._options = None 42 | _ROUTER.methods_by_name['SendPayment']._serialized_options = b'\210\002\001' 43 | _ROUTER.methods_by_name['TrackPayment']._options = None 44 | _ROUTER.methods_by_name['TrackPayment']._serialized_options = b'\210\002\001' 45 | _FAILUREDETAIL._serialized_start=4980 46 | _FAILUREDETAIL._serialized_end=5493 47 | _PAYMENTSTATE._serialized_start=5496 48 | _PAYMENTSTATE._serialized_end=5670 49 | _RESOLVEHOLDFORWARDACTION._serialized_start=5672 50 | _RESOLVEHOLDFORWARDACTION._serialized_end=5732 51 | _CHANSTATUSACTION._serialized_start=5734 52 | _CHANSTATUSACTION._serialized_end=5787 53 | _SENDPAYMENTREQUEST._serialized_start=45 54 | _SENDPAYMENTREQUEST._serialized_end=740 55 | _SENDPAYMENTREQUEST_DESTCUSTOMRECORDSENTRY._serialized_start=684 56 | _SENDPAYMENTREQUEST_DESTCUSTOMRECORDSENTRY._serialized_end=740 57 | _TRACKPAYMENTREQUEST._serialized_start=742 58 | _TRACKPAYMENTREQUEST._serialized_end=814 59 | _TRACKPAYMENTSREQUEST._serialized_start=816 60 | _TRACKPAYMENTSREQUEST._serialized_end=867 61 | _ROUTEFEEREQUEST._serialized_start=869 62 | _ROUTEFEEREQUEST._serialized_end=917 63 | _ROUTEFEERESPONSE._serialized_start=919 64 | _ROUTEFEERESPONSE._serialized_end=988 65 | _SENDTOROUTEREQUEST._serialized_start=990 66 | _SENDTOROUTEREQUEST._serialized_end=1084 67 | _SENDTOROUTERESPONSE._serialized_start=1086 68 | _SENDTOROUTERESPONSE._serialized_end=1158 69 | _RESETMISSIONCONTROLREQUEST._serialized_start=1160 70 | _RESETMISSIONCONTROLREQUEST._serialized_end=1188 71 | _RESETMISSIONCONTROLRESPONSE._serialized_start=1190 72 | _RESETMISSIONCONTROLRESPONSE._serialized_end=1219 73 | _QUERYMISSIONCONTROLREQUEST._serialized_start=1221 74 | _QUERYMISSIONCONTROLREQUEST._serialized_end=1249 75 | _QUERYMISSIONCONTROLRESPONSE._serialized_start=1251 76 | _QUERYMISSIONCONTROLRESPONSE._serialized_end=1325 77 | _XIMPORTMISSIONCONTROLREQUEST._serialized_start=1327 78 | _XIMPORTMISSIONCONTROLREQUEST._serialized_end=1411 79 | _XIMPORTMISSIONCONTROLRESPONSE._serialized_start=1413 80 | _XIMPORTMISSIONCONTROLRESPONSE._serialized_end=1444 81 | _PAIRHISTORY._serialized_start=1446 82 | _PAIRHISTORY._serialized_end=1557 83 | _PAIRDATA._serialized_start=1560 84 | _PAIRDATA._serialized_end=1713 85 | _GETMISSIONCONTROLCONFIGREQUEST._serialized_start=1715 86 | _GETMISSIONCONTROLCONFIGREQUEST._serialized_end=1747 87 | _GETMISSIONCONTROLCONFIGRESPONSE._serialized_start=1749 88 | _GETMISSIONCONTROLCONFIGRESPONSE._serialized_end=1831 89 | _SETMISSIONCONTROLCONFIGREQUEST._serialized_start=1833 90 | _SETMISSIONCONTROLCONFIGREQUEST._serialized_end=1914 91 | _SETMISSIONCONTROLCONFIGRESPONSE._serialized_start=1916 92 | _SETMISSIONCONTROLCONFIGRESPONSE._serialized_end=1949 93 | _MISSIONCONTROLCONFIG._serialized_start=1952 94 | _MISSIONCONTROLCONFIG._serialized_end=2355 95 | _MISSIONCONTROLCONFIG_PROBABILITYMODEL._serialized_start=2292 96 | _MISSIONCONTROLCONFIG_PROBABILITYMODEL._serialized_end=2336 97 | _BIMODALPARAMETERS._serialized_start=2357 98 | _BIMODALPARAMETERS._serialized_end=2437 99 | _APRIORIPARAMETERS._serialized_start=2439 100 | _APRIORIPARAMETERS._serialized_end=2553 101 | _QUERYPROBABILITYREQUEST._serialized_start=2555 102 | _QUERYPROBABILITYREQUEST._serialized_end=2634 103 | _QUERYPROBABILITYRESPONSE._serialized_start=2636 104 | _QUERYPROBABILITYRESPONSE._serialized_end=2721 105 | _BUILDROUTEREQUEST._serialized_start=2724 106 | _BUILDROUTEREQUEST._serialized_end=2860 107 | _BUILDROUTERESPONSE._serialized_start=2862 108 | _BUILDROUTERESPONSE._serialized_end=2911 109 | _SUBSCRIBEHTLCEVENTSREQUEST._serialized_start=2913 110 | _SUBSCRIBEHTLCEVENTSREQUEST._serialized_end=2941 111 | _HTLCEVENT._serialized_start=2944 112 | _HTLCEVENT._serialized_end=3531 113 | _HTLCEVENT_EVENTTYPE._serialized_start=3462 114 | _HTLCEVENT_EVENTTYPE._serialized_end=3522 115 | _HTLCINFO._serialized_start=3533 116 | _HTLCINFO._serialized_end=3651 117 | _FORWARDEVENT._serialized_start=3653 118 | _FORWARDEVENT._serialized_end=3702 119 | _FORWARDFAILEVENT._serialized_start=3704 120 | _FORWARDFAILEVENT._serialized_end=3722 121 | _SETTLEEVENT._serialized_start=3724 122 | _SETTLEEVENT._serialized_end=3755 123 | _FINALHTLCEVENT._serialized_start=3757 124 | _FINALHTLCEVENT._serialized_end=3808 125 | _SUBSCRIBEDEVENT._serialized_start=3810 126 | _SUBSCRIBEDEVENT._serialized_end=3827 127 | _LINKFAILEVENT._serialized_start=3830 128 | _LINKFAILEVENT._serialized_end=4004 129 | _PAYMENTSTATUS._serialized_start=4006 130 | _PAYMENTSTATUS._serialized_end=4120 131 | _CIRCUITKEY._serialized_start=4122 132 | _CIRCUITKEY._serialized_end=4168 133 | _FORWARDHTLCINTERCEPTREQUEST._serialized_start=4171 134 | _FORWARDHTLCINTERCEPTREQUEST._serialized_end=4604 135 | _FORWARDHTLCINTERCEPTREQUEST_CUSTOMRECORDSENTRY._serialized_start=4552 136 | _FORWARDHTLCINTERCEPTREQUEST_CUSTOMRECORDSENTRY._serialized_end=4604 137 | _FORWARDHTLCINTERCEPTRESPONSE._serialized_start=4607 138 | _FORWARDHTLCINTERCEPTRESPONSE._serialized_end=4836 139 | _UPDATECHANSTATUSREQUEST._serialized_start=4838 140 | _UPDATECHANSTATUSREQUEST._serialized_end=4949 141 | _UPDATECHANSTATUSRESPONSE._serialized_start=4951 142 | _UPDATECHANSTATUSRESPONSE._serialized_end=4977 143 | _ROUTER._serialized_start=5790 144 | _ROUTER._serialized_end=7379 145 | # @@protoc_insertion_point(module_scope) 146 | -------------------------------------------------------------------------------- /protos/router_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | import lightning_pb2 as lightning__pb2 6 | import router_pb2 as router__pb2 7 | 8 | 9 | class RouterStub(object): 10 | """Router is a service that offers advanced interaction with the router 11 | subsystem of the daemon. 12 | """ 13 | 14 | def __init__(self, channel): 15 | """Constructor. 16 | 17 | Args: 18 | channel: A grpc.Channel. 19 | """ 20 | self.SendPaymentV2 = channel.unary_stream( 21 | '/routerrpc.Router/SendPaymentV2', 22 | request_serializer=router__pb2.SendPaymentRequest.SerializeToString, 23 | response_deserializer=lightning__pb2.Payment.FromString, 24 | ) 25 | self.TrackPaymentV2 = channel.unary_stream( 26 | '/routerrpc.Router/TrackPaymentV2', 27 | request_serializer=router__pb2.TrackPaymentRequest.SerializeToString, 28 | response_deserializer=lightning__pb2.Payment.FromString, 29 | ) 30 | self.TrackPayments = channel.unary_stream( 31 | '/routerrpc.Router/TrackPayments', 32 | request_serializer=router__pb2.TrackPaymentsRequest.SerializeToString, 33 | response_deserializer=lightning__pb2.Payment.FromString, 34 | ) 35 | self.EstimateRouteFee = channel.unary_unary( 36 | '/routerrpc.Router/EstimateRouteFee', 37 | request_serializer=router__pb2.RouteFeeRequest.SerializeToString, 38 | response_deserializer=router__pb2.RouteFeeResponse.FromString, 39 | ) 40 | self.SendToRoute = channel.unary_unary( 41 | '/routerrpc.Router/SendToRoute', 42 | request_serializer=router__pb2.SendToRouteRequest.SerializeToString, 43 | response_deserializer=router__pb2.SendToRouteResponse.FromString, 44 | ) 45 | self.SendToRouteV2 = channel.unary_unary( 46 | '/routerrpc.Router/SendToRouteV2', 47 | request_serializer=router__pb2.SendToRouteRequest.SerializeToString, 48 | response_deserializer=lightning__pb2.HTLCAttempt.FromString, 49 | ) 50 | self.ResetMissionControl = channel.unary_unary( 51 | '/routerrpc.Router/ResetMissionControl', 52 | request_serializer=router__pb2.ResetMissionControlRequest.SerializeToString, 53 | response_deserializer=router__pb2.ResetMissionControlResponse.FromString, 54 | ) 55 | self.QueryMissionControl = channel.unary_unary( 56 | '/routerrpc.Router/QueryMissionControl', 57 | request_serializer=router__pb2.QueryMissionControlRequest.SerializeToString, 58 | response_deserializer=router__pb2.QueryMissionControlResponse.FromString, 59 | ) 60 | self.XImportMissionControl = channel.unary_unary( 61 | '/routerrpc.Router/XImportMissionControl', 62 | request_serializer=router__pb2.XImportMissionControlRequest.SerializeToString, 63 | response_deserializer=router__pb2.XImportMissionControlResponse.FromString, 64 | ) 65 | self.GetMissionControlConfig = channel.unary_unary( 66 | '/routerrpc.Router/GetMissionControlConfig', 67 | request_serializer=router__pb2.GetMissionControlConfigRequest.SerializeToString, 68 | response_deserializer=router__pb2.GetMissionControlConfigResponse.FromString, 69 | ) 70 | self.SetMissionControlConfig = channel.unary_unary( 71 | '/routerrpc.Router/SetMissionControlConfig', 72 | request_serializer=router__pb2.SetMissionControlConfigRequest.SerializeToString, 73 | response_deserializer=router__pb2.SetMissionControlConfigResponse.FromString, 74 | ) 75 | self.QueryProbability = channel.unary_unary( 76 | '/routerrpc.Router/QueryProbability', 77 | request_serializer=router__pb2.QueryProbabilityRequest.SerializeToString, 78 | response_deserializer=router__pb2.QueryProbabilityResponse.FromString, 79 | ) 80 | self.BuildRoute = channel.unary_unary( 81 | '/routerrpc.Router/BuildRoute', 82 | request_serializer=router__pb2.BuildRouteRequest.SerializeToString, 83 | response_deserializer=router__pb2.BuildRouteResponse.FromString, 84 | ) 85 | self.SubscribeHtlcEvents = channel.unary_stream( 86 | '/routerrpc.Router/SubscribeHtlcEvents', 87 | request_serializer=router__pb2.SubscribeHtlcEventsRequest.SerializeToString, 88 | response_deserializer=router__pb2.HtlcEvent.FromString, 89 | ) 90 | self.SendPayment = channel.unary_stream( 91 | '/routerrpc.Router/SendPayment', 92 | request_serializer=router__pb2.SendPaymentRequest.SerializeToString, 93 | response_deserializer=router__pb2.PaymentStatus.FromString, 94 | ) 95 | self.TrackPayment = channel.unary_stream( 96 | '/routerrpc.Router/TrackPayment', 97 | request_serializer=router__pb2.TrackPaymentRequest.SerializeToString, 98 | response_deserializer=router__pb2.PaymentStatus.FromString, 99 | ) 100 | self.HtlcInterceptor = channel.stream_stream( 101 | '/routerrpc.Router/HtlcInterceptor', 102 | request_serializer=router__pb2.ForwardHtlcInterceptResponse.SerializeToString, 103 | response_deserializer=router__pb2.ForwardHtlcInterceptRequest.FromString, 104 | ) 105 | self.UpdateChanStatus = channel.unary_unary( 106 | '/routerrpc.Router/UpdateChanStatus', 107 | request_serializer=router__pb2.UpdateChanStatusRequest.SerializeToString, 108 | response_deserializer=router__pb2.UpdateChanStatusResponse.FromString, 109 | ) 110 | 111 | 112 | class RouterServicer(object): 113 | """Router is a service that offers advanced interaction with the router 114 | subsystem of the daemon. 115 | """ 116 | 117 | def SendPaymentV2(self, request, context): 118 | """ 119 | SendPaymentV2 attempts to route a payment described by the passed 120 | PaymentRequest to the final destination. The call returns a stream of 121 | payment updates. 122 | """ 123 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 124 | context.set_details('Method not implemented!') 125 | raise NotImplementedError('Method not implemented!') 126 | 127 | def TrackPaymentV2(self, request, context): 128 | """ 129 | TrackPaymentV2 returns an update stream for the payment identified by the 130 | payment hash. 131 | """ 132 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 133 | context.set_details('Method not implemented!') 134 | raise NotImplementedError('Method not implemented!') 135 | 136 | def TrackPayments(self, request, context): 137 | """ 138 | TrackPayments returns an update stream for every payment that is not in a 139 | terminal state. Note that if payments are in-flight while starting a new 140 | subscription, the start of the payment stream could produce out-of-order 141 | and/or duplicate events. In order to get updates for every in-flight 142 | payment attempt make sure to subscribe to this method before initiating any 143 | payments. 144 | """ 145 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 146 | context.set_details('Method not implemented!') 147 | raise NotImplementedError('Method not implemented!') 148 | 149 | def EstimateRouteFee(self, request, context): 150 | """ 151 | EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it 152 | may cost to send an HTLC to the target end destination. 153 | """ 154 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 155 | context.set_details('Method not implemented!') 156 | raise NotImplementedError('Method not implemented!') 157 | 158 | def SendToRoute(self, request, context): 159 | """ 160 | Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via 161 | the specified route. This method differs from SendPayment in that it 162 | allows users to specify a full route manually. This can be used for 163 | things like rebalancing, and atomic swaps. It differs from the newer 164 | SendToRouteV2 in that it doesn't return the full HTLC information. 165 | """ 166 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 167 | context.set_details('Method not implemented!') 168 | raise NotImplementedError('Method not implemented!') 169 | 170 | def SendToRouteV2(self, request, context): 171 | """ 172 | SendToRouteV2 attempts to make a payment via the specified route. This 173 | method differs from SendPayment in that it allows users to specify a full 174 | route manually. This can be used for things like rebalancing, and atomic 175 | swaps. 176 | """ 177 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 178 | context.set_details('Method not implemented!') 179 | raise NotImplementedError('Method not implemented!') 180 | 181 | def ResetMissionControl(self, request, context): 182 | """ 183 | ResetMissionControl clears all mission control state and starts with a clean 184 | slate. 185 | """ 186 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 187 | context.set_details('Method not implemented!') 188 | raise NotImplementedError('Method not implemented!') 189 | 190 | def QueryMissionControl(self, request, context): 191 | """ 192 | QueryMissionControl exposes the internal mission control state to callers. 193 | It is a development feature. 194 | """ 195 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 196 | context.set_details('Method not implemented!') 197 | raise NotImplementedError('Method not implemented!') 198 | 199 | def XImportMissionControl(self, request, context): 200 | """ 201 | XImportMissionControl is an experimental API that imports the state provided 202 | to the internal mission control's state, using all results which are more 203 | recent than our existing values. These values will only be imported 204 | in-memory, and will not be persisted across restarts. 205 | """ 206 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 207 | context.set_details('Method not implemented!') 208 | raise NotImplementedError('Method not implemented!') 209 | 210 | def GetMissionControlConfig(self, request, context): 211 | """ 212 | GetMissionControlConfig returns mission control's current config. 213 | """ 214 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 215 | context.set_details('Method not implemented!') 216 | raise NotImplementedError('Method not implemented!') 217 | 218 | def SetMissionControlConfig(self, request, context): 219 | """ 220 | SetMissionControlConfig will set mission control's config, if the config 221 | provided is valid. 222 | """ 223 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 224 | context.set_details('Method not implemented!') 225 | raise NotImplementedError('Method not implemented!') 226 | 227 | def QueryProbability(self, request, context): 228 | """ 229 | Deprecated. QueryProbability returns the current success probability 230 | estimate for a given node pair and amount. The call returns a zero success 231 | probability if no channel is available or if the amount violates min/max 232 | HTLC constraints. 233 | """ 234 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 235 | context.set_details('Method not implemented!') 236 | raise NotImplementedError('Method not implemented!') 237 | 238 | def BuildRoute(self, request, context): 239 | """ 240 | BuildRoute builds a fully specified route based on a list of hop public 241 | keys. It retrieves the relevant channel policies from the graph in order to 242 | calculate the correct fees and time locks. 243 | """ 244 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 245 | context.set_details('Method not implemented!') 246 | raise NotImplementedError('Method not implemented!') 247 | 248 | def SubscribeHtlcEvents(self, request, context): 249 | """ 250 | SubscribeHtlcEvents creates a uni-directional stream from the server to 251 | the client which delivers a stream of htlc events. 252 | """ 253 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 254 | context.set_details('Method not implemented!') 255 | raise NotImplementedError('Method not implemented!') 256 | 257 | def SendPayment(self, request, context): 258 | """ 259 | Deprecated, use SendPaymentV2. SendPayment attempts to route a payment 260 | described by the passed PaymentRequest to the final destination. The call 261 | returns a stream of payment status updates. 262 | """ 263 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 264 | context.set_details('Method not implemented!') 265 | raise NotImplementedError('Method not implemented!') 266 | 267 | def TrackPayment(self, request, context): 268 | """ 269 | Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for 270 | the payment identified by the payment hash. 271 | """ 272 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 273 | context.set_details('Method not implemented!') 274 | raise NotImplementedError('Method not implemented!') 275 | 276 | def HtlcInterceptor(self, request_iterator, context): 277 | """* 278 | HtlcInterceptor dispatches a bi-directional streaming RPC in which 279 | Forwarded HTLC requests are sent to the client and the client responds with 280 | a boolean that tells LND if this htlc should be intercepted. 281 | In case of interception, the htlc can be either settled, cancelled or 282 | resumed later by using the ResolveHoldForward endpoint. 283 | """ 284 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 285 | context.set_details('Method not implemented!') 286 | raise NotImplementedError('Method not implemented!') 287 | 288 | def UpdateChanStatus(self, request, context): 289 | """ 290 | UpdateChanStatus attempts to manually set the state of a channel 291 | (enabled, disabled, or auto). A manual "disable" request will cause the 292 | channel to stay disabled until a subsequent manual request of either 293 | "enable" or "auto". 294 | """ 295 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 296 | context.set_details('Method not implemented!') 297 | raise NotImplementedError('Method not implemented!') 298 | 299 | 300 | def add_RouterServicer_to_server(servicer, server): 301 | rpc_method_handlers = { 302 | 'SendPaymentV2': grpc.unary_stream_rpc_method_handler( 303 | servicer.SendPaymentV2, 304 | request_deserializer=router__pb2.SendPaymentRequest.FromString, 305 | response_serializer=lightning__pb2.Payment.SerializeToString, 306 | ), 307 | 'TrackPaymentV2': grpc.unary_stream_rpc_method_handler( 308 | servicer.TrackPaymentV2, 309 | request_deserializer=router__pb2.TrackPaymentRequest.FromString, 310 | response_serializer=lightning__pb2.Payment.SerializeToString, 311 | ), 312 | 'TrackPayments': grpc.unary_stream_rpc_method_handler( 313 | servicer.TrackPayments, 314 | request_deserializer=router__pb2.TrackPaymentsRequest.FromString, 315 | response_serializer=lightning__pb2.Payment.SerializeToString, 316 | ), 317 | 'EstimateRouteFee': grpc.unary_unary_rpc_method_handler( 318 | servicer.EstimateRouteFee, 319 | request_deserializer=router__pb2.RouteFeeRequest.FromString, 320 | response_serializer=router__pb2.RouteFeeResponse.SerializeToString, 321 | ), 322 | 'SendToRoute': grpc.unary_unary_rpc_method_handler( 323 | servicer.SendToRoute, 324 | request_deserializer=router__pb2.SendToRouteRequest.FromString, 325 | response_serializer=router__pb2.SendToRouteResponse.SerializeToString, 326 | ), 327 | 'SendToRouteV2': grpc.unary_unary_rpc_method_handler( 328 | servicer.SendToRouteV2, 329 | request_deserializer=router__pb2.SendToRouteRequest.FromString, 330 | response_serializer=lightning__pb2.HTLCAttempt.SerializeToString, 331 | ), 332 | 'ResetMissionControl': grpc.unary_unary_rpc_method_handler( 333 | servicer.ResetMissionControl, 334 | request_deserializer=router__pb2.ResetMissionControlRequest.FromString, 335 | response_serializer=router__pb2.ResetMissionControlResponse.SerializeToString, 336 | ), 337 | 'QueryMissionControl': grpc.unary_unary_rpc_method_handler( 338 | servicer.QueryMissionControl, 339 | request_deserializer=router__pb2.QueryMissionControlRequest.FromString, 340 | response_serializer=router__pb2.QueryMissionControlResponse.SerializeToString, 341 | ), 342 | 'XImportMissionControl': grpc.unary_unary_rpc_method_handler( 343 | servicer.XImportMissionControl, 344 | request_deserializer=router__pb2.XImportMissionControlRequest.FromString, 345 | response_serializer=router__pb2.XImportMissionControlResponse.SerializeToString, 346 | ), 347 | 'GetMissionControlConfig': grpc.unary_unary_rpc_method_handler( 348 | servicer.GetMissionControlConfig, 349 | request_deserializer=router__pb2.GetMissionControlConfigRequest.FromString, 350 | response_serializer=router__pb2.GetMissionControlConfigResponse.SerializeToString, 351 | ), 352 | 'SetMissionControlConfig': grpc.unary_unary_rpc_method_handler( 353 | servicer.SetMissionControlConfig, 354 | request_deserializer=router__pb2.SetMissionControlConfigRequest.FromString, 355 | response_serializer=router__pb2.SetMissionControlConfigResponse.SerializeToString, 356 | ), 357 | 'QueryProbability': grpc.unary_unary_rpc_method_handler( 358 | servicer.QueryProbability, 359 | request_deserializer=router__pb2.QueryProbabilityRequest.FromString, 360 | response_serializer=router__pb2.QueryProbabilityResponse.SerializeToString, 361 | ), 362 | 'BuildRoute': grpc.unary_unary_rpc_method_handler( 363 | servicer.BuildRoute, 364 | request_deserializer=router__pb2.BuildRouteRequest.FromString, 365 | response_serializer=router__pb2.BuildRouteResponse.SerializeToString, 366 | ), 367 | 'SubscribeHtlcEvents': grpc.unary_stream_rpc_method_handler( 368 | servicer.SubscribeHtlcEvents, 369 | request_deserializer=router__pb2.SubscribeHtlcEventsRequest.FromString, 370 | response_serializer=router__pb2.HtlcEvent.SerializeToString, 371 | ), 372 | 'SendPayment': grpc.unary_stream_rpc_method_handler( 373 | servicer.SendPayment, 374 | request_deserializer=router__pb2.SendPaymentRequest.FromString, 375 | response_serializer=router__pb2.PaymentStatus.SerializeToString, 376 | ), 377 | 'TrackPayment': grpc.unary_stream_rpc_method_handler( 378 | servicer.TrackPayment, 379 | request_deserializer=router__pb2.TrackPaymentRequest.FromString, 380 | response_serializer=router__pb2.PaymentStatus.SerializeToString, 381 | ), 382 | 'HtlcInterceptor': grpc.stream_stream_rpc_method_handler( 383 | servicer.HtlcInterceptor, 384 | request_deserializer=router__pb2.ForwardHtlcInterceptResponse.FromString, 385 | response_serializer=router__pb2.ForwardHtlcInterceptRequest.SerializeToString, 386 | ), 387 | 'UpdateChanStatus': grpc.unary_unary_rpc_method_handler( 388 | servicer.UpdateChanStatus, 389 | request_deserializer=router__pb2.UpdateChanStatusRequest.FromString, 390 | response_serializer=router__pb2.UpdateChanStatusResponse.SerializeToString, 391 | ), 392 | } 393 | generic_handler = grpc.method_handlers_generic_handler( 394 | 'routerrpc.Router', rpc_method_handlers) 395 | server.add_generic_rpc_handlers((generic_handler,)) 396 | 397 | 398 | # This class is part of an EXPERIMENTAL API. 399 | class Router(object): 400 | """Router is a service that offers advanced interaction with the router 401 | subsystem of the daemon. 402 | """ 403 | 404 | @staticmethod 405 | def SendPaymentV2(request, 406 | target, 407 | options=(), 408 | channel_credentials=None, 409 | call_credentials=None, 410 | insecure=False, 411 | compression=None, 412 | wait_for_ready=None, 413 | timeout=None, 414 | metadata=None): 415 | return grpc.experimental.unary_stream(request, target, '/routerrpc.Router/SendPaymentV2', 416 | router__pb2.SendPaymentRequest.SerializeToString, 417 | lightning__pb2.Payment.FromString, 418 | options, channel_credentials, 419 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 420 | 421 | @staticmethod 422 | def TrackPaymentV2(request, 423 | target, 424 | options=(), 425 | channel_credentials=None, 426 | call_credentials=None, 427 | insecure=False, 428 | compression=None, 429 | wait_for_ready=None, 430 | timeout=None, 431 | metadata=None): 432 | return grpc.experimental.unary_stream(request, target, '/routerrpc.Router/TrackPaymentV2', 433 | router__pb2.TrackPaymentRequest.SerializeToString, 434 | lightning__pb2.Payment.FromString, 435 | options, channel_credentials, 436 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 437 | 438 | @staticmethod 439 | def TrackPayments(request, 440 | target, 441 | options=(), 442 | channel_credentials=None, 443 | call_credentials=None, 444 | insecure=False, 445 | compression=None, 446 | wait_for_ready=None, 447 | timeout=None, 448 | metadata=None): 449 | return grpc.experimental.unary_stream(request, target, '/routerrpc.Router/TrackPayments', 450 | router__pb2.TrackPaymentsRequest.SerializeToString, 451 | lightning__pb2.Payment.FromString, 452 | options, channel_credentials, 453 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 454 | 455 | @staticmethod 456 | def EstimateRouteFee(request, 457 | target, 458 | options=(), 459 | channel_credentials=None, 460 | call_credentials=None, 461 | insecure=False, 462 | compression=None, 463 | wait_for_ready=None, 464 | timeout=None, 465 | metadata=None): 466 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/EstimateRouteFee', 467 | router__pb2.RouteFeeRequest.SerializeToString, 468 | router__pb2.RouteFeeResponse.FromString, 469 | options, channel_credentials, 470 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 471 | 472 | @staticmethod 473 | def SendToRoute(request, 474 | target, 475 | options=(), 476 | channel_credentials=None, 477 | call_credentials=None, 478 | insecure=False, 479 | compression=None, 480 | wait_for_ready=None, 481 | timeout=None, 482 | metadata=None): 483 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/SendToRoute', 484 | router__pb2.SendToRouteRequest.SerializeToString, 485 | router__pb2.SendToRouteResponse.FromString, 486 | options, channel_credentials, 487 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 488 | 489 | @staticmethod 490 | def SendToRouteV2(request, 491 | target, 492 | options=(), 493 | channel_credentials=None, 494 | call_credentials=None, 495 | insecure=False, 496 | compression=None, 497 | wait_for_ready=None, 498 | timeout=None, 499 | metadata=None): 500 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/SendToRouteV2', 501 | router__pb2.SendToRouteRequest.SerializeToString, 502 | lightning__pb2.HTLCAttempt.FromString, 503 | options, channel_credentials, 504 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 505 | 506 | @staticmethod 507 | def ResetMissionControl(request, 508 | target, 509 | options=(), 510 | channel_credentials=None, 511 | call_credentials=None, 512 | insecure=False, 513 | compression=None, 514 | wait_for_ready=None, 515 | timeout=None, 516 | metadata=None): 517 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/ResetMissionControl', 518 | router__pb2.ResetMissionControlRequest.SerializeToString, 519 | router__pb2.ResetMissionControlResponse.FromString, 520 | options, channel_credentials, 521 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 522 | 523 | @staticmethod 524 | def QueryMissionControl(request, 525 | target, 526 | options=(), 527 | channel_credentials=None, 528 | call_credentials=None, 529 | insecure=False, 530 | compression=None, 531 | wait_for_ready=None, 532 | timeout=None, 533 | metadata=None): 534 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/QueryMissionControl', 535 | router__pb2.QueryMissionControlRequest.SerializeToString, 536 | router__pb2.QueryMissionControlResponse.FromString, 537 | options, channel_credentials, 538 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 539 | 540 | @staticmethod 541 | def XImportMissionControl(request, 542 | target, 543 | options=(), 544 | channel_credentials=None, 545 | call_credentials=None, 546 | insecure=False, 547 | compression=None, 548 | wait_for_ready=None, 549 | timeout=None, 550 | metadata=None): 551 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/XImportMissionControl', 552 | router__pb2.XImportMissionControlRequest.SerializeToString, 553 | router__pb2.XImportMissionControlResponse.FromString, 554 | options, channel_credentials, 555 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 556 | 557 | @staticmethod 558 | def GetMissionControlConfig(request, 559 | target, 560 | options=(), 561 | channel_credentials=None, 562 | call_credentials=None, 563 | insecure=False, 564 | compression=None, 565 | wait_for_ready=None, 566 | timeout=None, 567 | metadata=None): 568 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/GetMissionControlConfig', 569 | router__pb2.GetMissionControlConfigRequest.SerializeToString, 570 | router__pb2.GetMissionControlConfigResponse.FromString, 571 | options, channel_credentials, 572 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 573 | 574 | @staticmethod 575 | def SetMissionControlConfig(request, 576 | target, 577 | options=(), 578 | channel_credentials=None, 579 | call_credentials=None, 580 | insecure=False, 581 | compression=None, 582 | wait_for_ready=None, 583 | timeout=None, 584 | metadata=None): 585 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/SetMissionControlConfig', 586 | router__pb2.SetMissionControlConfigRequest.SerializeToString, 587 | router__pb2.SetMissionControlConfigResponse.FromString, 588 | options, channel_credentials, 589 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 590 | 591 | @staticmethod 592 | def QueryProbability(request, 593 | target, 594 | options=(), 595 | channel_credentials=None, 596 | call_credentials=None, 597 | insecure=False, 598 | compression=None, 599 | wait_for_ready=None, 600 | timeout=None, 601 | metadata=None): 602 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/QueryProbability', 603 | router__pb2.QueryProbabilityRequest.SerializeToString, 604 | router__pb2.QueryProbabilityResponse.FromString, 605 | options, channel_credentials, 606 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 607 | 608 | @staticmethod 609 | def BuildRoute(request, 610 | target, 611 | options=(), 612 | channel_credentials=None, 613 | call_credentials=None, 614 | insecure=False, 615 | compression=None, 616 | wait_for_ready=None, 617 | timeout=None, 618 | metadata=None): 619 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/BuildRoute', 620 | router__pb2.BuildRouteRequest.SerializeToString, 621 | router__pb2.BuildRouteResponse.FromString, 622 | options, channel_credentials, 623 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 624 | 625 | @staticmethod 626 | def SubscribeHtlcEvents(request, 627 | target, 628 | options=(), 629 | channel_credentials=None, 630 | call_credentials=None, 631 | insecure=False, 632 | compression=None, 633 | wait_for_ready=None, 634 | timeout=None, 635 | metadata=None): 636 | return grpc.experimental.unary_stream(request, target, '/routerrpc.Router/SubscribeHtlcEvents', 637 | router__pb2.SubscribeHtlcEventsRequest.SerializeToString, 638 | router__pb2.HtlcEvent.FromString, 639 | options, channel_credentials, 640 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 641 | 642 | @staticmethod 643 | def SendPayment(request, 644 | target, 645 | options=(), 646 | channel_credentials=None, 647 | call_credentials=None, 648 | insecure=False, 649 | compression=None, 650 | wait_for_ready=None, 651 | timeout=None, 652 | metadata=None): 653 | return grpc.experimental.unary_stream(request, target, '/routerrpc.Router/SendPayment', 654 | router__pb2.SendPaymentRequest.SerializeToString, 655 | router__pb2.PaymentStatus.FromString, 656 | options, channel_credentials, 657 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 658 | 659 | @staticmethod 660 | def TrackPayment(request, 661 | target, 662 | options=(), 663 | channel_credentials=None, 664 | call_credentials=None, 665 | insecure=False, 666 | compression=None, 667 | wait_for_ready=None, 668 | timeout=None, 669 | metadata=None): 670 | return grpc.experimental.unary_stream(request, target, '/routerrpc.Router/TrackPayment', 671 | router__pb2.TrackPaymentRequest.SerializeToString, 672 | router__pb2.PaymentStatus.FromString, 673 | options, channel_credentials, 674 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 675 | 676 | @staticmethod 677 | def HtlcInterceptor(request_iterator, 678 | target, 679 | options=(), 680 | channel_credentials=None, 681 | call_credentials=None, 682 | insecure=False, 683 | compression=None, 684 | wait_for_ready=None, 685 | timeout=None, 686 | metadata=None): 687 | return grpc.experimental.stream_stream(request_iterator, target, '/routerrpc.Router/HtlcInterceptor', 688 | router__pb2.ForwardHtlcInterceptResponse.SerializeToString, 689 | router__pb2.ForwardHtlcInterceptRequest.FromString, 690 | options, channel_credentials, 691 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 692 | 693 | @staticmethod 694 | def UpdateChanStatus(request, 695 | target, 696 | options=(), 697 | channel_credentials=None, 698 | call_credentials=None, 699 | insecure=False, 700 | compression=None, 701 | wait_for_ready=None, 702 | timeout=None, 703 | metadata=None): 704 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/UpdateChanStatus', 705 | router__pb2.UpdateChanStatusRequest.SerializeToString, 706 | router__pb2.UpdateChanStatusResponse.FromString, 707 | options, channel_credentials, 708 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 709 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.8.4 2 | aiosignal==1.3.1 3 | anyio==3.7.0 4 | appnope==0.1.3 5 | argon2-cffi==21.3.0 6 | argon2-cffi-bindings==21.2.0 7 | arrow==1.2.3 8 | asttokens==2.2.1 9 | async-timeout==4.0.2 10 | attrs==23.1.0 11 | backcall==0.2.0 12 | beautifulsoup4==4.12.2 13 | bleach==6.0.0 14 | certifi==2023.5.7 15 | cffi==1.15.1 16 | charset-normalizer==3.1.0 17 | comm==0.1.3 18 | dataclasses-json==0.5.7 19 | debugpy==1.6.7 20 | decorator==5.1.1 21 | defusedxml==0.7.1 22 | exceptiongroup==1.1.1 23 | executing==1.2.0 24 | fastjsonschema==2.17.1 25 | fqdn==1.5.1 26 | frozenlist==1.3.3 27 | googleapis-common-protos==1.59.0 28 | greenlet==2.0.2 29 | grpcio==1.54.2 30 | grpcio-tools==1.54.2 31 | idna==3.4 32 | install==1.3.5 33 | ipykernel==6.23.2 34 | ipython==8.14.0 35 | ipython-genutils==0.2.0 36 | isoduration==20.11.0 37 | jedi==0.18.2 38 | Jinja2==3.1.2 39 | jsonpointer==2.4 40 | jsonschema==4.17.3 41 | jupyter-events==0.6.3 42 | jupyter_client==8.2.0 43 | jupyter_core==5.3.1 44 | jupyter_server==2.6.0 45 | jupyter_server_terminals==0.4.4 46 | jupyterlab-pygments==0.2.2 47 | langchain==0.1.13 48 | langchainhub==0.1.15 49 | langchain-community==0.0.29 50 | langchain-core==0.1.42 51 | langchain-openai==0.1.1 52 | MarkupSafe==2.1.3 53 | marshmallow==3.19.0 54 | marshmallow-enum==1.5.1 55 | matplotlib-inline==0.1.6 56 | mistune==3.0.1 57 | multidict==6.0.4 58 | mypy-extensions==1.0.0 59 | nbclassic==1.0.0 60 | nbclient==0.8.0 61 | nbconvert==7.6.0 62 | nbformat==5.9.0 63 | nest-asyncio==1.5.6 64 | notebook==6.5.4 65 | notebook_shim==0.2.3 66 | numexpr==2.8.4 67 | numpy==1.24.3 68 | openai>=1.10.0,<2.0.0 69 | openapi-schema-pydantic==1.2.4 70 | overrides==7.3.1 71 | packaging>=23.2,<24.0 72 | pandocfilters==1.5.0 73 | parso==0.8.3 74 | pexpect==4.8.0 75 | pickleshare==0.7.5 76 | platformdirs==2.6.2 77 | prometheus-client==0.17.0 78 | prompt-toolkit==3.0.38 79 | protobuf==4.23.1 80 | psutil==5.9.5 81 | ptyprocess==0.7.0 82 | pure-eval==0.2.2 83 | pycparser==2.21 84 | pydantic==1.10.7 85 | Pygments==2.15.1 86 | pymacaroons==0.13.0 87 | PyNaCl==1.5.0 88 | pyrsistent==0.19.3 89 | python-dateutil==2.8.2 90 | python-dotenv==1.0.1 91 | python-json-logger==2.0.7 92 | PyYAML==6.0 93 | pyzmq==25.1.0 94 | requests==2.30.0 95 | rfc3339-validator==0.1.4 96 | rfc3986-validator==0.1.1 97 | Send2Trash==1.8.2 98 | six==1.16.0 99 | sniffio==1.3.0 100 | soupsieve==2.4.1 101 | SQLAlchemy==2.0.13 102 | stack-data==0.6.2 103 | tenacity==8.2.2 104 | terminado==0.17.1 105 | tinycss2==1.2.1 106 | tornado==6.3.2 107 | tqdm==4.65.0 108 | traitlets==5.9.0 109 | typing-inspect==0.8.0 110 | typing-extensions>=4.7,<5 111 | uri-template==1.2.0 112 | urllib3==2.0.2 113 | wcwidth==0.2.6 114 | webcolors==1.13 115 | webencodings==0.5.1 116 | websocket-client==1.6.0 117 | yarl==1.9.2 -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | from langchain_core.runnables.utils import Output 2 | from lightning import LndNode 3 | from all_tools.bitcoin_tools import LndTools 4 | from L402 import L402APIChain 5 | from langchain import hub 6 | from all_tools import api_tools 7 | from langchain.schema import StrOutputParser 8 | from langchain.prompts import PromptTemplate 9 | from langchain_openai import OpenAI, ChatOpenAI 10 | from langchain.agents import AgentExecutor, create_structured_chat_agent 11 | from langchain.schema.runnable import RunnableMap 12 | from dotenv import dotenv_values 13 | 14 | config = dotenv_values(".env.shared") 15 | 16 | 17 | class LLMUtils: 18 | lnd_node: LndNode 19 | 20 | def __init__(self, lnd_node: LndNode): 21 | self.lnd_node = lnd_node 22 | self.llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) 23 | 24 | def api_chain_factory(self, api_docs: str, api_host: str): 25 | api_chain = L402APIChain.from_llm_and_api_docs( 26 | self.llm, 27 | api_docs, 28 | lightning_node=self.lnd_node, 29 | verbose=True, 30 | limit_to_domains=[api_host, "localhost"], 31 | ) 32 | return api_chain 33 | 34 | def get_target_api_chain(self) -> L402APIChain: 35 | 36 | target_host = config.get("TARGET_HOST", "unknown_host") 37 | 38 | # Add the specifics of the API to be worked with here 39 | API_DOCS = f"""BASE URL: {target_host} 40 | 41 | API Documentation 42 | 43 | Request: 44 | 45 | Response: 46 | 47 | """ 48 | target_api_chain = self.api_chain_factory( 49 | api_docs=API_DOCS, api_host=target_host 50 | ) 51 | return target_api_chain 52 | 53 | def get_target_api_tool(self): 54 | name = config.get("API_TOOL_NAME", "Default api tool name") 55 | description = config.get("API_TOOL_DESCRIPTION", "Default api tool description") 56 | target_api_chain = self.get_target_api_chain() 57 | target_api_tool = api_tools.api_tool_factory( 58 | api_chain=target_api_chain, name=name, description=description 59 | ) 60 | return target_api_tool 61 | 62 | def _get_agent_executor(self, tools): 63 | prompt = hub.pull("hwchase17/structured-chat-agent") 64 | agent = create_structured_chat_agent(llm=self.llm, prompt=prompt, tools=tools) 65 | agent_executor = AgentExecutor( 66 | agent=agent, 67 | tools=tools, 68 | verbose=True, 69 | handle_parsing_errors=True, 70 | return_intermediate_steps=True, 71 | ) 72 | return agent_executor 73 | 74 | def get_lnd_agent_executor(self): 75 | lnd_tools = LndTools.from_lnd_node(lnd_node=self.lnd_node).get_tools() 76 | agent_executor = self._get_agent_executor(tools=lnd_tools) 77 | return agent_executor 78 | 79 | def get_entry_point(self, additional_tools): 80 | lnd_tools = LndTools.from_lnd_node(lnd_node=self.lnd_node).get_tools() 81 | all_tools = lnd_tools + additional_tools 82 | agent_executor = self._get_agent_executor(tools=all_tools) 83 | return agent_executor 84 | 85 | def get_entry_point_v2(self): 86 | prompt = PromptTemplate.from_template( 87 | """If the input is about payments, balance, general info of specific Lighting node, respond with `LND`. If the input is about general info of Lighting node technology, Bitcoin or blockchain in general, respond with `BLOCKCHAIN`. 88 | If the input is about retreaving API data, respond with `API`. If the input is about the functionality if this tool or how this tool may help user, respond with `FAQ`. Otherwise, respond `OTHER` 89 | 90 | Question: {question}""" 91 | ) 92 | 93 | blockchain_llm_chain = ( 94 | PromptTemplate.from_template( 95 | """You are an expert in Blockchain technology.You have hure experience with Bitcoin and Lighting Network. Respond to the question: 96 | 97 | Question: {input}""" 98 | ) 99 | | ChatOpenAI() 100 | | StrOutputParser() 101 | ) 102 | target_host = config.get("TARGET_HOST", "missed") 103 | 104 | faq_str = f"""You are a tool designed to help users communicate with Lighting Network that is on top of the bitcoin blockchain. Also you are able to communicate with some websites API. One of them is: `{target_host}`. On this website you can find API data and services. You can also find other information. Respond with information about your features. 105 | """ 106 | faq_llm_chain = ( 107 | PromptTemplate.from_template( 108 | faq_str 109 | + """ 110 | 111 | Question: {input}""" 112 | ) 113 | | ChatOpenAI() 114 | | StrOutputParser() 115 | ) 116 | 117 | general_llm_chain = ( 118 | PromptTemplate.from_template( 119 | f"""Respond that you dont have answer for user query and provide information about your features based on this text: ###{faq_str}### 120 | 121 | Question: {input}""" 122 | ) 123 | | ChatOpenAI() 124 | | StrOutputParser() 125 | ) 126 | 127 | router_chain = prompt | ChatOpenAI() | StrOutputParser() 128 | 129 | target_api_chain = self.get_target_api_chain() 130 | agent_executor = self.get_lnd_agent_executor() 131 | 132 | # Add the routing logic - use the action key to route 133 | def select_chain(output): 134 | if output["action"] == "LND": 135 | return agent_executor 136 | elif output["action"] == "OTHER": 137 | return general_llm_chain 138 | elif output["action"] == "FAQ": 139 | return faq_llm_chain 140 | elif output["action"] == "BLOCKCHAIN": 141 | return blockchain_llm_chain 142 | elif output["action"] == "API": 143 | return target_api_chain 144 | else: 145 | raise ValueError 146 | 147 | chain = ( 148 | RunnableMap( 149 | {"action": router_chain, "input": {"question": lambda x: x["question"]}} 150 | ) 151 | | select_chain 152 | ) 153 | 154 | return chain 155 | --------------------------------------------------------------------------------