diff --git a/nbs/00_core.ipynb b/nbs/00_core.ipynb index 604093e..1a94e3a 100644 --- a/nbs/00_core.ipynb +++ b/nbs/00_core.ipynb @@ -2,6 +2,7 @@ "cells": [ { "cell_type": "markdown", + "id": "7616869b", "metadata": {}, "source": [ "# core\n", @@ -12,6 +13,7 @@ { "cell_type": "code", "execution_count": null, + "id": "a3f1e597", "metadata": {}, "outputs": [], "source": [ @@ -21,6 +23,7 @@ { "cell_type": "code", "execution_count": null, + "id": "5d6ff9f6", "metadata": {}, "outputs": [], "source": [ @@ -31,6 +34,7 @@ { "cell_type": "code", "execution_count": null, + "id": "f6795eb7", "metadata": {}, "outputs": [], "source": [ @@ -54,6 +58,7 @@ { "cell_type": "code", "execution_count": null, + "id": "36cd8ff2", "metadata": {}, "outputs": [], "source": [ @@ -68,6 +73,7 @@ { "cell_type": "code", "execution_count": null, + "id": "61aa0953", "metadata": {}, "outputs": [], "source": [ @@ -78,6 +84,7 @@ { "cell_type": "code", "execution_count": null, + "id": "10947bfa", "metadata": {}, "outputs": [], "source": [ @@ -92,6 +99,7 @@ }, { "cell_type": "markdown", + "id": "79326eab", "metadata": {}, "source": [ "### Sherlock\n", @@ -102,6 +110,7 @@ { "cell_type": "code", "execution_count": null, + "id": "3378affc", "metadata": {}, "outputs": [], "source": [ @@ -136,6 +145,7 @@ { "cell_type": "code", "execution_count": null, + "id": "9ec1f8b8", "metadata": {}, "outputs": [ { @@ -157,6 +167,7 @@ { "cell_type": "code", "execution_count": null, + "id": "df5a2663", "metadata": {}, "outputs": [], "source": [ @@ -168,6 +179,7 @@ { "cell_type": "code", "execution_count": null, + "id": "4951767d", "metadata": {}, "outputs": [ { @@ -190,6 +202,7 @@ }, { "cell_type": "markdown", + "id": "f2af90da", "metadata": {}, "source": [ "### Me \n", @@ -200,6 +213,7 @@ { "cell_type": "code", "execution_count": null, + "id": "c59b8c63", "metadata": {}, "outputs": [], "source": [ @@ -210,6 +224,7 @@ { "cell_type": "code", "execution_count": null, + "id": "df62eb25", "metadata": {}, "outputs": [], "source": [ @@ -220,6 +235,7 @@ { "cell_type": "code", "execution_count": null, + "id": "d7386a4b", "metadata": {}, "outputs": [ { @@ -244,6 +260,7 @@ { "cell_type": "code", "execution_count": null, + "id": "a9d9a42b", "metadata": {}, "outputs": [], "source": [ @@ -259,6 +276,7 @@ { "cell_type": "code", "execution_count": null, + "id": "138b58df", "metadata": {}, "outputs": [], "source": [ @@ -277,6 +295,7 @@ { "cell_type": "code", "execution_count": null, + "id": "9fef18ee", "metadata": {}, "outputs": [ { @@ -298,6 +317,7 @@ }, { "cell_type": "markdown", + "id": "032a79a3", "metadata": {}, "source": [ "\n", @@ -306,6 +326,7 @@ }, { "cell_type": "markdown", + "id": "07af20fd", "metadata": {}, "source": [ "### Claim account\n", @@ -318,6 +339,7 @@ { "cell_type": "code", "execution_count": null, + "id": "7f0c25d1", "metadata": {}, "outputs": [], "source": [ @@ -332,6 +354,7 @@ { "cell_type": "code", "execution_count": null, + "id": "01a8c6da", "metadata": {}, "outputs": [], "source": [ @@ -354,6 +377,7 @@ }, { "cell_type": "markdown", + "id": "b52d1382", "metadata": {}, "source": [ "### Search domains\n", @@ -364,6 +388,7 @@ { "cell_type": "code", "execution_count": null, + "id": "e1cd7991", "metadata": {}, "outputs": [ { @@ -462,6 +487,7 @@ { "cell_type": "code", "execution_count": null, + "id": "f7f8ccd3", "metadata": {}, "outputs": [], "source": [ @@ -478,6 +504,7 @@ { "cell_type": "code", "execution_count": null, + "id": "38d2e89c", "metadata": {}, "outputs": [], "source": [ @@ -509,6 +536,7 @@ { "cell_type": "code", "execution_count": null, + "id": "639495a9", "metadata": {}, "outputs": [ { @@ -540,6 +568,7 @@ }, { "cell_type": "markdown", + "id": "3622ab55", "metadata": {}, "source": [ "### Contact Information\n", @@ -550,6 +579,7 @@ { "cell_type": "code", "execution_count": null, + "id": "d5a4a17d", "metadata": {}, "outputs": [], "source": [ @@ -573,6 +603,7 @@ { "cell_type": "code", "execution_count": null, + "id": "e054eba2", "metadata": {}, "outputs": [], "source": [ @@ -624,6 +655,7 @@ { "cell_type": "code", "execution_count": null, + "id": "c5a039a2", "metadata": {}, "outputs": [ { @@ -655,6 +687,7 @@ { "cell_type": "code", "execution_count": null, + "id": "2b5c3272", "metadata": {}, "outputs": [], "source": [ @@ -699,6 +732,7 @@ { "cell_type": "code", "execution_count": null, + "id": "f400a08e", "metadata": {}, "outputs": [ { @@ -732,6 +766,7 @@ { "cell_type": "code", "execution_count": null, + "id": "66488176", "metadata": {}, "outputs": [ { @@ -762,6 +797,7 @@ { "cell_type": "code", "execution_count": null, + "id": "c977809d", "metadata": {}, "outputs": [ { @@ -790,6 +826,7 @@ { "cell_type": "code", "execution_count": null, + "id": "7605125f", "metadata": {}, "outputs": [], "source": [ @@ -806,6 +843,7 @@ }, { "cell_type": "markdown", + "id": "accabd8a", "metadata": {}, "source": [ "### Purchase a domain\n", @@ -819,6 +857,7 @@ { "cell_type": "code", "execution_count": null, + "id": "e265c5df", "metadata": {}, "outputs": [], "source": [ @@ -829,6 +868,15 @@ { "cell_type": "code", "execution_count": null, + "id": "81bf423d", + "metadata": {}, + "outputs": [], + "source": "#| export\nget_x402_offers_endpoint = f\"{API_URL}/api/v0/domains/purchase-x402\"" + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0f8cf00a", "metadata": {}, "outputs": [], "source": [ @@ -844,6 +892,7 @@ { "cell_type": "code", "execution_count": null, + "id": "2359b1f1", "metadata": {}, "outputs": [ { @@ -875,6 +924,7 @@ { "cell_type": "code", "execution_count": null, + "id": "51ffed95", "metadata": {}, "outputs": [ { @@ -906,6 +956,7 @@ { "cell_type": "code", "execution_count": null, + "id": "a8e00833", "metadata": {}, "outputs": [], "source": [ @@ -952,6 +1003,7 @@ }, { "cell_type": "markdown", + "id": "b6577b1d", "metadata": {}, "source": [ "Requesting a purchase will return a list of available offers and payment methods. " @@ -960,6 +1012,7 @@ { "cell_type": "code", "execution_count": null, + "id": "a2113d89", "metadata": {}, "outputs": [ { @@ -990,6 +1043,7 @@ }, { "cell_type": "markdown", + "id": "afa37fdd", "metadata": {}, "source": [ "In order to pay for the domain you will have to request the payment details of the offer you want to pay for. " @@ -998,6 +1052,7 @@ { "cell_type": "code", "execution_count": null, + "id": "9d9e15be", "metadata": {}, "outputs": [ { @@ -1027,6 +1082,7 @@ { "cell_type": "code", "execution_count": null, + "id": "c6a8fda2", "metadata": {}, "outputs": [], "source": [ @@ -1051,6 +1107,7 @@ { "cell_type": "code", "execution_count": null, + "id": "f5db74c0", "metadata": {}, "outputs": [ { @@ -1075,6 +1132,7 @@ { "cell_type": "code", "execution_count": null, + "id": "7cc9224f", "metadata": {}, "outputs": [], "source": [ @@ -1113,6 +1171,45 @@ }, { "cell_type": "markdown", + "id": "78f33a3b", + "metadata": {}, + "source": "### X402 Purchase\n\nThe X402 purchase flow uses the [X402 protocol](https://www.x402.org/) (USDC on Base blockchain via Coinbase CDP). The flow has two steps:\n\n1. Get X402 payment requirements for a domain (returns 402 with `PaymentRequired` schema)\n2. Complete the purchase by providing a `PAYMENT-SIGNATURE` header (produced externally, e.g. via the `x402` Python library)" + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ae75eb3a", + "metadata": {}, + "outputs": [], + "source": "#| export\n@patch\ndef get_x402_purchase_offers(self: Sherlock,\n sid: str, # search id\n domain: str, # domain\n c: Contact): # contact information\n \"Request X402 payment requirements for a domain purchase.\"\n if not c or not c.is_valid(): raise ValueError(\"Contact information is required\")\n r = httpx.post(get_x402_offers_endpoint,\n json=_get_offers_payload(domain, c, sid),\n headers=_mk_headers(self.atok))\n return _handle_response(r)" + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82a42963", + "metadata": {}, + "outputs": [], + "source": "#| export\n#| hide\n\n@patch\ndef _get_x402_purchase_offers(self: Sherlock,\n sid: str, # search id\n domain: str): # domain\n \"\"\"Request X402 payment requirements for a domain purchase.\n\n This method posts to the X402 purchase endpoint without a payment signature,\n returning a 402 response with the X402 PaymentRequired schema.\n\n The response includes:\n - `x402Version`: The version of the X402 protocol.\n - `error`: Error message (if any).\n - `accepts`: A list of accepted payment schemes, each containing:\n - `scheme`: Payment scheme identifier.\n - `network`: Blockchain network (e.g., 'base').\n - `asset`: Payment asset (e.g., USDC contract address).\n - `payTo`: Recipient address.\n - `amount`: Payment amount.\n - `resource`: The resource being purchased.\n \"\"\"\n contact = Contact(**self.get_contact_information())\n if not contact or not contact.is_valid(): raise ValueError(\"Contact information is required\")\n return self.get_x402_purchase_offers(sid, domain, contact)" + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c5399872", + "metadata": {}, + "outputs": [], + "source": "#| export\n\n@patch\ndef purchase_x402(self: Sherlock,\n sid: str, # search id\n domain: str, # domain\n payment_signature: str, # PAYMENT-SIGNATURE header value\n c: Contact): # contact information\n \"Complete an X402 domain purchase with a payment signature.\"\n if not c or not c.is_valid(): raise ValueError(\"Contact information is required\")\n headers = _mk_headers(self.atok)\n headers[\"PAYMENT-SIGNATURE\"] = payment_signature\n r = httpx.post(get_x402_offers_endpoint,\n json=_get_offers_payload(domain, c, sid),\n headers=headers)\n return _handle_response(r)" + }, + { + "cell_type": "code", + "execution_count": null, + "id": "622c22ef", + "metadata": {}, + "outputs": [], + "source": "#| export\n#| hide\n\n@patch\ndef _purchase_x402(self: Sherlock,\n sid: str, # search id\n domain: str, # domain\n payment_signature: str): # PAYMENT-SIGNATURE header value\n \"\"\"Complete an X402 domain purchase with a pre-built payment signature.\n\n This method posts to the X402 purchase endpoint with the PAYMENT-SIGNATURE header,\n completing the purchase. The caller is responsible for producing the signature\n (e.g., using the x402 Python library or receiving it from another party).\n\n sid: Search ID from a previous search request\n domain: Domain name to purchase\n payment_signature: The PAYMENT-SIGNATURE header value for X402 payment authorization\n\n Returns on success:\n - `offer_id`: The offer identifier.\n - `domain`: The purchased domain name.\n - `state`: The purchase state.\n \"\"\"\n contact = Contact(**self.get_contact_information())\n if not contact or not contact.is_valid(): raise ValueError(\"Contact information is required\")\n return self.purchase_x402(sid, domain, payment_signature, contact)" + }, + { + "cell_type": "markdown", + "id": "c59e8cd7", "metadata": {}, "source": [ "## DNS methods\n" @@ -1121,6 +1218,7 @@ { "cell_type": "code", "execution_count": null, + "id": "798fe3f2", "metadata": {}, "outputs": [], "source": [ @@ -1136,6 +1234,7 @@ { "cell_type": "code", "execution_count": null, + "id": "01b0d2a1", "metadata": {}, "outputs": [], "source": [ @@ -1164,6 +1263,7 @@ { "cell_type": "code", "execution_count": null, + "id": "b00424ef", "metadata": {}, "outputs": [ { @@ -1211,6 +1311,7 @@ { "cell_type": "code", "execution_count": null, + "id": "eb965836", "metadata": {}, "outputs": [], "source": [ @@ -1228,6 +1329,7 @@ { "cell_type": "code", "execution_count": null, + "id": "0839d870", "metadata": {}, "outputs": [], "source": [ @@ -1252,6 +1354,7 @@ { "cell_type": "code", "execution_count": null, + "id": "51db92bf", "metadata": {}, "outputs": [ { @@ -1274,6 +1377,7 @@ { "cell_type": "code", "execution_count": null, + "id": "ef3c93bb", "metadata": {}, "outputs": [], "source": [ @@ -1291,6 +1395,7 @@ { "cell_type": "code", "execution_count": null, + "id": "c994d66e", "metadata": {}, "outputs": [], "source": [ @@ -1318,6 +1423,7 @@ { "cell_type": "code", "execution_count": null, + "id": "6a1e79fd", "metadata": {}, "outputs": [ { @@ -1345,6 +1451,7 @@ { "cell_type": "code", "execution_count": null, + "id": "d8006916", "metadata": {}, "outputs": [], "source": [ @@ -1366,6 +1473,7 @@ { "cell_type": "code", "execution_count": null, + "id": "fb9de40f", "metadata": {}, "outputs": [ { @@ -1401,6 +1509,7 @@ { "cell_type": "code", "execution_count": null, + "id": "6725daa9", "metadata": {}, "outputs": [], "source": [ @@ -1429,6 +1538,7 @@ { "cell_type": "code", "execution_count": null, + "id": "e5cade8a", "metadata": {}, "outputs": [ { @@ -1459,6 +1569,7 @@ { "cell_type": "code", "execution_count": null, + "id": "0f52bb61", "metadata": {}, "outputs": [], "source": [ @@ -1483,6 +1594,7 @@ { "cell_type": "code", "execution_count": null, + "id": "b4b15877", "metadata": {}, "outputs": [ { @@ -1518,6 +1630,7 @@ { "cell_type": "code", "execution_count": null, + "id": "b11b54e7", "metadata": {}, "outputs": [], "source": [ @@ -1550,6 +1663,7 @@ { "cell_type": "code", "execution_count": null, + "id": "43095772", "metadata": {}, "outputs": [], "source": [ @@ -1568,6 +1682,7 @@ { "cell_type": "code", "execution_count": null, + "id": "6b2441bb", "metadata": {}, "outputs": [ { @@ -1588,6 +1703,7 @@ { "cell_type": "code", "execution_count": null, + "id": "ae33b5e9", "metadata": {}, "outputs": [], "source": [ @@ -1609,6 +1725,7 @@ }, { "cell_type": "markdown", + "id": "8fed5477", "metadata": {}, "source": [ "We expose Sherlock's core functionality as tools for AI agents. Note that payment handling for L402 offers requires additional tools like `fewsats.Client().pay`." @@ -1617,32 +1734,15 @@ { "cell_type": "code", "execution_count": null, + "id": "da6e4a70", "metadata": {}, "outputs": [], - "source": [ - "#| export\n", - "\n", - "@patch\n", - "def as_tools(self:Sherlock):\n", - " \"Return the Sherlock class as a list of tools ready for agents to use\"\n", - " return L([\n", - " self._me,\n", - " self._set_contact_information,\n", - " self._get_contact_information,\n", - " self._search, \n", - " self._request_payment_details,\n", - " self._domains,\n", - " self._update_nameservers,\n", - " self._dns_records,\n", - " self._create_dns_record,\n", - " self._update_dns_record,\n", - " self._delete_dns_record,\n", - " ])" - ] + "source": "#| export\n\n@patch\ndef as_tools(self:Sherlock):\n \"Return the Sherlock class as a list of tools ready for agents to use\"\n return L([\n self._me,\n self._set_contact_information,\n self._get_contact_information,\n self._search,\n self._request_payment_details,\n self._get_x402_purchase_offers,\n self._purchase_x402,\n self._domains,\n self._update_nameservers,\n self._dns_records,\n self._create_dns_record,\n self._update_dns_record,\n self._delete_dns_record,\n ])" }, { "cell_type": "code", "execution_count": null, + "id": "bea00757", "metadata": {}, "outputs": [ { @@ -1662,6 +1762,7 @@ }, { "cell_type": "markdown", + "id": "b6c03a61", "metadata": {}, "source": [ "## CLI" @@ -1670,6 +1771,7 @@ { "cell_type": "code", "execution_count": null, + "id": "8f9bee58", "metadata": {}, "outputs": [], "source": [ @@ -1681,31 +1783,14 @@ { "cell_type": "code", "execution_count": null, + "id": "9eeaff4e", "metadata": {}, "outputs": [], - "source": [ - "#| export\n", - "\n", - "@patch\n", - "def as_cli(self:Sherlock):\n", - " \"Return the Sherlock class as a list of tools ready for agents to use\"\n", - " return L([\n", - " self.me,\n", - " self.set_contact_information,\n", - " self.get_contact_information,\n", - " self.search,\n", - " self.request_payment_details,\n", - " self.domains,\n", - " self.update_nameservers,\n", - " self.dns_records,\n", - " self.create_dns,\n", - " self.update_dns,\n", - " self.delete_dns,\n", - " ])" - ] + "source": "#| export\n\n@patch\ndef as_cli(self:Sherlock):\n \"Return the Sherlock class as a list of tools ready for agents to use\"\n return L([\n self.me,\n self.set_contact_information,\n self.get_contact_information,\n self.search,\n self.request_payment_details,\n self.get_x402_purchase_offers,\n self.purchase_x402,\n self.domains,\n self.update_nameservers,\n self.dns_records,\n self.create_dns,\n self.update_dns,\n self.delete_dns,\n ])" }, { "cell_type": "markdown", + "id": "5b176ece", "metadata": {}, "source": [ "You can use the Sherlock class as a CLI tool.\n", @@ -1737,6 +1822,7 @@ { "cell_type": "code", "execution_count": null, + "id": "1224fa7d", "metadata": {}, "outputs": [], "source": [ @@ -1763,6 +1849,7 @@ { "cell_type": "code", "execution_count": null, + "id": "6e87853a", "metadata": {}, "outputs": [], "source": [ @@ -1779,5 +1866,5 @@ } }, "nbformat": 4, - "nbformat_minor": 4 + "nbformat_minor": 5 } diff --git a/nbs/01_crypto.ipynb b/nbs/01_crypto.ipynb index 6595421..410ab64 100644 --- a/nbs/01_crypto.ipynb +++ b/nbs/01_crypto.ipynb @@ -2,6 +2,7 @@ "cells": [ { "cell_type": "markdown", + "id": "20c64935", "metadata": {}, "source": [ "# crypto\n", @@ -12,6 +13,7 @@ { "cell_type": "code", "execution_count": null, + "id": "13df2d76", "metadata": {}, "outputs": [], "source": [ @@ -21,6 +23,7 @@ { "cell_type": "code", "execution_count": null, + "id": "b49deaff", "metadata": {}, "outputs": [], "source": [ @@ -31,6 +34,7 @@ { "cell_type": "code", "execution_count": null, + "id": "394cf179", "metadata": {}, "outputs": [ { @@ -52,6 +56,7 @@ }, { "cell_type": "markdown", + "id": "d6c0b6de", "metadata": {}, "source": [ "Generating a private & public key pair from scratch." @@ -60,6 +65,7 @@ { "cell_type": "code", "execution_count": null, + "id": "70704956", "metadata": {}, "outputs": [ { @@ -81,6 +87,7 @@ { "cell_type": "code", "execution_count": null, + "id": "a3f507fa", "metadata": {}, "outputs": [ { @@ -103,6 +110,7 @@ { "cell_type": "code", "execution_count": null, + "id": "d6a41091", "metadata": {}, "outputs": [], "source": [ @@ -116,6 +124,7 @@ { "cell_type": "code", "execution_count": null, + "id": "ce63c4f3", "metadata": {}, "outputs": [ { @@ -136,6 +145,7 @@ }, { "cell_type": "markdown", + "id": "ad92b030", "metadata": {}, "source": [ "Generating a `pk` instance and a public key from a private key in hex.\n" @@ -144,6 +154,7 @@ { "cell_type": "code", "execution_count": null, + "id": "df7cc7b3", "metadata": {}, "outputs": [ { @@ -166,6 +177,7 @@ { "cell_type": "code", "execution_count": null, + "id": "e6146c91", "metadata": {}, "outputs": [], "source": [ @@ -182,6 +194,7 @@ { "cell_type": "code", "execution_count": null, + "id": "9abd3af2", "metadata": {}, "outputs": [ { @@ -202,12 +215,14 @@ }, { "cell_type": "markdown", + "id": "e4df2fbe", "metadata": {}, "source": [] }, { "cell_type": "code", "execution_count": null, + "id": "f5df6b3e", "metadata": {}, "outputs": [], "source": [ @@ -224,5 +239,5 @@ } }, "nbformat": 4, - "nbformat_minor": 4 + "nbformat_minor": 5 } diff --git a/nbs/02_auth.ipynb b/nbs/02_auth.ipynb index 8989660..812a901 100644 --- a/nbs/02_auth.ipynb +++ b/nbs/02_auth.ipynb @@ -2,6 +2,7 @@ "cells": [ { "cell_type": "markdown", + "id": "f9a84312", "metadata": {}, "source": [ "# auth\n", @@ -12,6 +13,7 @@ { "cell_type": "code", "execution_count": null, + "id": "8c4385be", "metadata": {}, "outputs": [], "source": [ @@ -21,6 +23,7 @@ { "cell_type": "code", "execution_count": null, + "id": "1b012888", "metadata": {}, "outputs": [], "source": [ @@ -31,6 +34,7 @@ { "cell_type": "code", "execution_count": null, + "id": "92558fbd", "metadata": {}, "outputs": [], "source": [ @@ -44,6 +48,7 @@ { "cell_type": "code", "execution_count": null, + "id": "5461f789", "metadata": {}, "outputs": [], "source": [ @@ -60,6 +65,7 @@ }, { "cell_type": "markdown", + "id": "008c47bc", "metadata": {}, "source": [ "## Authentication flow\n", @@ -78,6 +84,7 @@ }, { "cell_type": "markdown", + "id": "ae04b799", "metadata": {}, "source": [ "### Get challenge" @@ -86,6 +93,7 @@ { "cell_type": "code", "execution_count": null, + "id": "e4fc78dc", "metadata": {}, "outputs": [], "source": [ @@ -95,6 +103,7 @@ { "cell_type": "code", "execution_count": null, + "id": "299c3b8c", "metadata": {}, "outputs": [ { @@ -118,6 +127,7 @@ { "cell_type": "code", "execution_count": null, + "id": "232356bd", "metadata": {}, "outputs": [], "source": [ @@ -139,6 +149,7 @@ { "cell_type": "code", "execution_count": null, + "id": "24fd507f", "metadata": {}, "outputs": [ { @@ -160,6 +171,7 @@ }, { "cell_type": "markdown", + "id": "7ef1e0f9", "metadata": {}, "source": [ "### Sign challenge\n", @@ -170,6 +182,7 @@ { "cell_type": "code", "execution_count": null, + "id": "4ebdc30e", "metadata": {}, "outputs": [ { @@ -191,6 +204,7 @@ { "cell_type": "code", "execution_count": null, + "id": "fb59ba1b", "metadata": {}, "outputs": [], "source": [ @@ -204,6 +218,7 @@ }, { "cell_type": "markdown", + "id": "c6154a48", "metadata": {}, "source": [ "### Submit challenge" @@ -212,6 +227,7 @@ { "cell_type": "code", "execution_count": null, + "id": "eb524ebf", "metadata": {}, "outputs": [ { @@ -239,6 +255,7 @@ { "cell_type": "code", "execution_count": null, + "id": "e5f6db60", "metadata": {}, "outputs": [], "source": [ @@ -260,6 +277,7 @@ }, { "cell_type": "markdown", + "id": "9c2170a3", "metadata": {}, "source": [ "Challenges can be used only once." @@ -267,6 +285,7 @@ }, { "cell_type": "markdown", + "id": "2ddd174e", "metadata": {}, "source": [ "### Authenticate\n", @@ -277,6 +296,7 @@ { "cell_type": "code", "execution_count": null, + "id": "61136fdb", "metadata": {}, "outputs": [], "source": [ @@ -294,6 +314,7 @@ { "cell_type": "code", "execution_count": null, + "id": "0ceb6709", "metadata": {}, "outputs": [ { @@ -315,6 +336,7 @@ }, { "cell_type": "markdown", + "id": "691d3a73", "metadata": {}, "source": [ "### Linking an email to an Agent\n", @@ -327,6 +349,7 @@ { "cell_type": "code", "execution_count": null, + "id": "a2e21cde", "metadata": {}, "outputs": [], "source": [ @@ -350,5 +373,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 5 } diff --git a/nbs/03_config.ipynb b/nbs/03_config.ipynb index 5deb9ab..8bfbf8c 100644 --- a/nbs/03_config.ipynb +++ b/nbs/03_config.ipynb @@ -2,6 +2,7 @@ "cells": [ { "cell_type": "markdown", + "id": "4be1d7e6", "metadata": {}, "source": [ "# config\n", @@ -12,6 +13,7 @@ { "cell_type": "code", "execution_count": null, + "id": "0b3b1556", "metadata": {}, "outputs": [], "source": [ @@ -21,6 +23,7 @@ { "cell_type": "code", "execution_count": null, + "id": "b2d6a21f", "metadata": {}, "outputs": [], "source": [ @@ -32,6 +35,7 @@ { "cell_type": "code", "execution_count": null, + "id": "1c011cc5", "metadata": {}, "outputs": [], "source": [ @@ -46,6 +50,7 @@ { "cell_type": "code", "execution_count": null, + "id": "1dd6d00b", "metadata": {}, "outputs": [ { @@ -73,6 +78,7 @@ { "cell_type": "code", "execution_count": null, + "id": "b18176c3", "metadata": {}, "outputs": [], "source": [ @@ -95,6 +101,7 @@ { "cell_type": "code", "execution_count": null, + "id": "1e2405ba", "metadata": {}, "outputs": [ { @@ -117,6 +124,7 @@ { "cell_type": "code", "execution_count": null, + "id": "a220f07f", "metadata": {}, "outputs": [ { @@ -137,6 +145,7 @@ { "cell_type": "code", "execution_count": null, + "id": "3e5e61f1", "metadata": {}, "outputs": [ { @@ -158,6 +167,7 @@ { "cell_type": "code", "execution_count": null, + "id": "b1cda4d2", "metadata": {}, "outputs": [], "source": [ @@ -167,6 +177,7 @@ { "cell_type": "code", "execution_count": null, + "id": "83f360f2", "metadata": {}, "outputs": [], "source": [ @@ -183,5 +194,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 5 } diff --git a/nbs/index.ipynb b/nbs/index.ipynb index aedc739..e539511 100644 --- a/nbs/index.ipynb +++ b/nbs/index.ipynb @@ -3,6 +3,7 @@ { "cell_type": "code", "execution_count": null, + "id": "2014c0c4", "metadata": {}, "outputs": [], "source": [ @@ -12,6 +13,7 @@ }, { "cell_type": "markdown", + "id": "cbba875c", "metadata": {}, "source": [ "# Sherlock Domains Python SDK\n", @@ -21,6 +23,7 @@ }, { "cell_type": "markdown", + "id": "cd70b0f2", "metadata": {}, "source": [ "### Installation" @@ -28,6 +31,7 @@ }, { "cell_type": "markdown", + "id": "171400e9", "metadata": {}, "source": [ "Install latest from the GitHub [repository][repo]:\n", @@ -47,6 +51,7 @@ }, { "cell_type": "markdown", + "id": "4773953e", "metadata": {}, "source": [ "## How to use" @@ -54,6 +59,7 @@ }, { "cell_type": "markdown", + "id": "c3e502af", "metadata": {}, "source": [ "Create a Sherlock instance with a private key for the agent to use. If no key is provided, a new one will be generated and saved to the default config file." @@ -62,6 +68,7 @@ { "cell_type": "code", "execution_count": null, + "id": "eb1c307c", "metadata": {}, "outputs": [ { @@ -82,6 +89,7 @@ }, { "cell_type": "markdown", + "id": "c850ade4", "metadata": {}, "source": [ "You can search for a domain and request to purchase it. Purchasing a domain requires contact information as mandated by ICANN." @@ -90,6 +98,7 @@ { "cell_type": "code", "execution_count": null, + "id": "8c7bf64e", "metadata": {}, "outputs": [ { @@ -121,6 +130,7 @@ }, { "cell_type": "markdown", + "id": "c58d0bb1", "metadata": {}, "source": [ "Contact information is needed for registering a new domain with the ICANN." @@ -129,6 +139,7 @@ { "cell_type": "code", "execution_count": null, + "id": "2c05787b", "metadata": {}, "outputs": [ { @@ -168,6 +179,7 @@ }, { "cell_type": "markdown", + "id": "535b5d8a", "metadata": {}, "source": [ "Finalizing a purchase involves a payment. Sherlock Domains currently supports two payment methods: Credit Card (`credit_card`) and the Lightning Netowrk (`lightning`).\n", @@ -179,6 +191,7 @@ { "cell_type": "code", "execution_count": null, + "id": "2fdab17b", "metadata": {}, "outputs": [ { @@ -200,6 +213,7 @@ }, { "cell_type": "markdown", + "id": "cbe20acb", "metadata": {}, "source": [ "You can now use the checkout URL to complete the purchase and the domain will be registered to your agent.\n", @@ -210,6 +224,7 @@ { "cell_type": "code", "execution_count": null, + "id": "0eefbb0c", "metadata": {}, "outputs": [ { @@ -237,6 +252,7 @@ }, { "cell_type": "markdown", + "id": "412fe32a", "metadata": {}, "source": [ "Below is a list of all the tools that the client offers to manage the domains and purchases." @@ -245,6 +261,7 @@ { "cell_type": "code", "execution_count": null, + "id": "d7475d01", "metadata": {}, "outputs": [ { @@ -264,6 +281,7 @@ }, { "cell_type": "markdown", + "id": "6b000e65", "metadata": {}, "source": [ "## AI agents" @@ -271,6 +289,7 @@ }, { "cell_type": "markdown", + "id": "cb831d6c", "metadata": {}, "source": [ "We will show how to enable your AI assistant to handle payments using [Claudette](https://claudette.answer.ai), Answer.ai convenient wrapper for Claude. You'll need to export your `ANTHROPIC_API_KEY`." @@ -279,6 +298,7 @@ { "cell_type": "code", "execution_count": null, + "id": "f55958a3", "metadata": {}, "outputs": [], "source": [ @@ -288,6 +308,7 @@ { "cell_type": "code", "execution_count": null, + "id": "08faa614", "metadata": {}, "outputs": [ { @@ -307,6 +328,7 @@ }, { "cell_type": "markdown", + "id": "d97d028a", "metadata": {}, "source": [ "Create a Sherlock instance with a public & private key for the agent to use." @@ -314,6 +336,7 @@ }, { "cell_type": "markdown", + "id": "9cdc62ea", "metadata": {}, "source": [ "Sherlock supports returning all the tools with `s.as_tools()`." @@ -322,6 +345,7 @@ { "cell_type": "code", "execution_count": null, + "id": "11ec9f28", "metadata": {}, "outputs": [ { @@ -391,5 +415,5 @@ } }, "nbformat": 4, - "nbformat_minor": 4 + "nbformat_minor": 5 } diff --git a/settings.ini b/settings.ini index 6c742fb..8f350ab 100644 --- a/settings.ini +++ b/settings.ini @@ -1,7 +1,7 @@ [DEFAULT] repo = sherlock-python lib_name = sherlock-domains -version = 0.1.7 +version = 0.1.8 min_python = 3.7 license = apache2 black_formatting = False diff --git a/setup.py b/setup.py index 21b001e..bb91cea 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,5 @@ -from pkg_resources import parse_version from configparser import ConfigParser import setuptools, shlex -assert parse_version(setuptools.__version__)>=parse_version('36.2') # note: all settings are in settings.ini; edit there, not here config = ConfigParser(delimiters=['=']) diff --git a/sherlock/__init__.py b/sherlock/__init__.py index f1380ee..9cb17e7 100644 --- a/sherlock/__init__.py +++ b/sherlock/__init__.py @@ -1 +1 @@ -__version__ = "0.1.7" +__version__ = "0.1.8" diff --git a/sherlock/_modidx.py b/sherlock/_modidx.py index 145d89f..ba8baad 100644 --- a/sherlock/_modidx.py +++ b/sherlock/_modidx.py @@ -33,7 +33,10 @@ 'sherlock/core.py'), 'sherlock.core.Sherlock._get_purchase_offers': ( 'core.html#sherlock._get_purchase_offers', 'sherlock/core.py'), + 'sherlock.core.Sherlock._get_x402_purchase_offers': ( 'core.html#sherlock._get_x402_purchase_offers', + 'sherlock/core.py'), 'sherlock.core.Sherlock._me': ('core.html#sherlock._me', 'sherlock/core.py'), + 'sherlock.core.Sherlock._purchase_x402': ('core.html#sherlock._purchase_x402', 'sherlock/core.py'), 'sherlock.core.Sherlock._request_payment_details': ( 'core.html#sherlock._request_payment_details', 'sherlock/core.py'), 'sherlock.core.Sherlock._search': ('core.html#sherlock._search', 'sherlock/core.py'), @@ -52,7 +55,10 @@ 'sherlock/core.py'), 'sherlock.core.Sherlock.get_payment_details': ('core.html#sherlock.get_payment_details', 'sherlock/core.py'), 'sherlock.core.Sherlock.get_purchase_offers': ('core.html#sherlock.get_purchase_offers', 'sherlock/core.py'), + 'sherlock.core.Sherlock.get_x402_purchase_offers': ( 'core.html#sherlock.get_x402_purchase_offers', + 'sherlock/core.py'), 'sherlock.core.Sherlock.me': ('core.html#sherlock.me', 'sherlock/core.py'), + 'sherlock.core.Sherlock.purchase_x402': ('core.html#sherlock.purchase_x402', 'sherlock/core.py'), 'sherlock.core.Sherlock.request_payment_details': ( 'core.html#sherlock.request_payment_details', 'sherlock/core.py'), 'sherlock.core.Sherlock.search': ('core.html#sherlock.search', 'sherlock/core.py'), diff --git a/sherlock/auth.py b/sherlock/auth.py index d4f2941..aeab76e 100644 --- a/sherlock/auth.py +++ b/sherlock/auth.py @@ -2,16 +2,16 @@ # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_auth.ipynb. -# %% auto 0 +# %% auto #0 __all__ = ['authenticate', 'link_account_to_email'] -# %% ../nbs/02_auth.ipynb 3 +# %% ../nbs/02_auth.ipynb #97763843 import httpx from cryptography.hazmat.primitives.asymmetric import ed25519 from .crypto import * -# %% ../nbs/02_auth.ipynb 9 +# %% ../nbs/02_auth.ipynb #9eb6a443 def _handle_response(r): "Process response: raise for status and return json if possible." r.raise_for_status() @@ -24,13 +24,13 @@ def _get_challenge(pub_key: str, # public key r = httpx.post(f"{base_url}/api/v0/auth/challenge", json={"public_key": pub_key}) return _handle_response(r)['challenge'] -# %% ../nbs/02_auth.ipynb 13 +# %% ../nbs/02_auth.ipynb #0a195ede def _sign_challenge(pk: ed25519.Ed25519PrivateKey, c: str): # challenge "Sign a challenge with a private key" return pk.sign(bytes.fromhex(c)).hex() -# %% ../nbs/02_auth.ipynb 16 +# %% ../nbs/02_auth.ipynb #a7d1d573 def _submit_challenge(pub: str, # public key c: str, # challenge sig: str, # signature @@ -44,7 +44,7 @@ def _submit_challenge(pub: str, # public key r = _handle_response(r) return r['access'], r['refresh'] -# %% ../nbs/02_auth.ipynb 19 +# %% ../nbs/02_auth.ipynb #e282597a def authenticate(priv: ed25519.Ed25519PrivateKey, # private key base_url: str = "https://api.sherlockdomains.com"): # base url "Authenticate with the server and return access and refresh tokens" @@ -53,7 +53,7 @@ def authenticate(priv: ed25519.Ed25519PrivateKey, # private key sig = _sign_challenge(priv, c) return _submit_challenge(pub, c, sig, base_url) -# %% ../nbs/02_auth.ipynb 22 +# %% ../nbs/02_auth.ipynb #0f20175e def link_account_to_email(email: str, auth_token: str, base_url: str = "https://api.sherlockdomains.com") -> None: r = httpx.post( f"{base_url}/api/v0/auth/email-link", diff --git a/sherlock/config.py b/sherlock/config.py index 3d1ca74..cd07ea8 100644 --- a/sherlock/config.py +++ b/sherlock/config.py @@ -2,17 +2,17 @@ # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/03_config.ipynb. -# %% auto 0 +# %% auto #0 __all__ = ['SherlockConfig', 'get_cfg', 'save_cfg'] -# %% ../nbs/03_config.ipynb 3 +# %% ../nbs/03_config.ipynb #5acbff57 from fastcore.all import * from fastcore.xdg import * from typing import get_type_hints from dataclasses import dataclass import json -# %% ../nbs/03_config.ipynb 4 +# %% ../nbs/03_config.ipynb #c4dc098f @dataclass class SherlockConfig: priv: str = '' # private key (hex) @@ -20,7 +20,7 @@ class SherlockConfig: def _cfg_path(): return xdg_config_home() / 'sherlock' / 'sherlock.conf' _cfg_path() -# %% ../nbs/03_config.ipynb 5 +# %% ../nbs/03_config.ipynb #2a10607f def get_cfg(path = None): "Get config from XDG config dir, creating if needed" if path is None: path = _cfg_path() diff --git a/sherlock/core.py b/sherlock/core.py index 9e482c0..d333d3d 100644 --- a/sherlock/core.py +++ b/sherlock/core.py @@ -2,10 +2,10 @@ # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb. -# %% auto 0 -__all__ = ['API_URL', 'me_endpoint', 'get_offers_endpoint', 'Sherlock', 'Contact', 'main'] +# %% auto #0 +__all__ = ['API_URL', 'me_endpoint', 'get_offers_endpoint', 'get_x402_offers_endpoint', 'Sherlock', 'Contact', 'main'] -# %% ../nbs/00_core.ipynb 3 +# %% ../nbs/00_core.ipynb #47039d1e import os from typing import Dict, Any import httpx, json, time @@ -22,17 +22,17 @@ from .crypto import from_pk_hex, generate_keys, priv_key_hex -# %% ../nbs/00_core.ipynb 5 +# %% ../nbs/00_core.ipynb #5b05a737 API_URL = os.getenv('SHERLOCK_API_URL', "https://api.sherlockdomains.com") -# %% ../nbs/00_core.ipynb 6 +# %% ../nbs/00_core.ipynb #e97f2176 def _handle_response(r): "Process response: raise for status and return json if possible. 402 status is expected for payment required." if r.status_code != 402: r.raise_for_status() try: return r.json() except: return r -# %% ../nbs/00_core.ipynb 8 +# %% ../nbs/00_core.ipynb #f3506f5e class Sherlock: "Sherlock client class to interact with the Sherlock API." def __init__(self, @@ -58,20 +58,20 @@ def _authenticate(self): def __str__(self): return f"Sherlock(pubkey={self.pub})" __repr__ = __str__ -# %% ../nbs/00_core.ipynb 13 +# %% ../nbs/00_core.ipynb #6c4e15ba me_endpoint = f"{API_URL}/api/v0/auth/me" -# %% ../nbs/00_core.ipynb 14 +# %% ../nbs/00_core.ipynb #83b83705 def _mk_headers(tok): return {"Authorization": f"Bearer {tok}"} -# %% ../nbs/00_core.ipynb 16 +# %% ../nbs/00_core.ipynb #e8715d62 @patch def me(self: Sherlock): "Get authenticated user information" r = httpx.get(me_endpoint, headers=_mk_headers(self.atok)) return _handle_response(r) -# %% ../nbs/00_core.ipynb 17 +# %% ../nbs/00_core.ipynb #8cf21345 @patch def _me(self: Sherlock): """ @@ -80,13 +80,13 @@ def _me(self: Sherlock): """ return self.me() -# %% ../nbs/00_core.ipynb 21 +# %% ../nbs/00_core.ipynb #70514f43 @patch def claim_account(self: Sherlock, email: str): "Claim an account by linking an email address" return link_account_to_email(email, self.atok) -# %% ../nbs/00_core.ipynb 22 +# %% ../nbs/00_core.ipynb #9b78d73e @patch def _claim_account(self: Sherlock, email: str): """ @@ -101,7 +101,7 @@ def _claim_account(self: Sherlock, email: str): -# %% ../nbs/00_core.ipynb 25 +# %% ../nbs/00_core.ipynb #29f701fa @patch def search(self: Sherlock, q: str): # query @@ -109,7 +109,7 @@ def search(self: Sherlock, r = httpx.get(f"{API_URL}/api/v0/domains/search", params={"query": q}) return _handle_response(r) -# %% ../nbs/00_core.ipynb 26 +# %% ../nbs/00_core.ipynb #2f1cb1bb @patch def _search(self: Sherlock, q: str): @@ -131,7 +131,7 @@ def _search(self: Sherlock, return self.search(q) -# %% ../nbs/00_core.ipynb 29 +# %% ../nbs/00_core.ipynb #01b7e91e class Contact(fc.BasicRepr): "Contact information for a domain purchase" first_name: str @@ -148,7 +148,7 @@ def asdict(self): return self.__dict__['__stored_args__'] def from_dict(d): return Contact(**d) if d else None -# %% ../nbs/00_core.ipynb 30 +# %% ../nbs/00_core.ipynb #45ab5702 @patch def is_valid(self: Contact): "Check if the contact information is valid" @@ -191,7 +191,7 @@ def get_contact_information(self: Sherlock): return _handle_response(r) -# %% ../nbs/00_core.ipynb 32 +# %% ../nbs/00_core.ipynb #7ee67f06 @patch def _set_contact_information(self: Sherlock, first_name: str = '', @@ -227,17 +227,20 @@ def _get_contact_information(self: Sherlock): return self.get_contact_information() -# %% ../nbs/00_core.ipynb 38 +# %% ../nbs/00_core.ipynb #30964e16 get_offers_endpoint = f"{API_URL}/api/v0/domains/purchase" -# %% ../nbs/00_core.ipynb 39 +# %% ../nbs/00_core.ipynb #f1d2f941 +get_x402_offers_endpoint = f"{API_URL}/api/v0/domains/purchase-x402" + +# %% ../nbs/00_core.ipynb #6d055e4b def _get_offers_payload(domain: str, # domain contact: Contact, # contact sid: str): # search id "Make a purchase payload" return {"domain": domain, "contact_information": contact.asdict(), "search_id": sid} -# %% ../nbs/00_core.ipynb 42 +# %% ../nbs/00_core.ipynb #679e96f4 @patch def get_purchase_offers(self: Sherlock, sid: str, # search id @@ -278,7 +281,7 @@ def _get_purchase_offers(self: Sherlock, -# %% ../nbs/00_core.ipynb 47 +# %% ../nbs/00_core.ipynb #0d704ab2 @patch def get_payment_details(self: Sherlock, prurl: str, # payment request url @@ -295,7 +298,7 @@ def get_payment_details(self: Sherlock, return _handle_response(r) -# %% ../nbs/00_core.ipynb 49 +# %% ../nbs/00_core.ipynb #42cfc012 @patch def request_payment_details(self: Sherlock, sid: str, # search id @@ -327,14 +330,93 @@ def _request_payment_details(self: Sherlock, return self.request_payment_details(sid, domain, payment_method, contact) -# %% ../nbs/00_core.ipynb 51 +# %% ../nbs/00_core.ipynb #c7b0ece2 +@patch +def get_x402_purchase_offers(self: Sherlock, + sid: str, # search id + domain: str, # domain + c: Contact): # contact information + "Request X402 payment requirements for a domain purchase." + if not c or not c.is_valid(): raise ValueError("Contact information is required") + r = httpx.post(get_x402_offers_endpoint, + json=_get_offers_payload(domain, c, sid), + headers=_mk_headers(self.atok)) + return _handle_response(r) + +# %% ../nbs/00_core.ipynb #38605ab9 +@patch +def _get_x402_purchase_offers(self: Sherlock, + sid: str, # search id + domain: str): # domain + """Request X402 payment requirements for a domain purchase. + + This method posts to the X402 purchase endpoint without a payment signature, + returning a 402 response with the X402 PaymentRequired schema. + + The response includes: + - `x402Version`: The version of the X402 protocol. + - `error`: Error message (if any). + - `accepts`: A list of accepted payment schemes, each containing: + - `scheme`: Payment scheme identifier. + - `network`: Blockchain network (e.g., 'base'). + - `asset`: Payment asset (e.g., USDC contract address). + - `payTo`: Recipient address. + - `amount`: Payment amount. + - `resource`: The resource being purchased. + """ + contact = Contact(**self.get_contact_information()) + if not contact or not contact.is_valid(): raise ValueError("Contact information is required") + return self.get_x402_purchase_offers(sid, domain, contact) + +# %% ../nbs/00_core.ipynb #5aa15999 +@patch +def purchase_x402(self: Sherlock, + sid: str, # search id + domain: str, # domain + payment_signature: str, # PAYMENT-SIGNATURE header value + c: Contact): # contact information + "Complete an X402 domain purchase with a payment signature." + if not c or not c.is_valid(): raise ValueError("Contact information is required") + headers = _mk_headers(self.atok) + headers["PAYMENT-SIGNATURE"] = payment_signature + r = httpx.post(get_x402_offers_endpoint, + json=_get_offers_payload(domain, c, sid), + headers=headers) + return _handle_response(r) + +# %% ../nbs/00_core.ipynb #1559ad4e +@patch +def _purchase_x402(self: Sherlock, + sid: str, # search id + domain: str, # domain + payment_signature: str): # PAYMENT-SIGNATURE header value + """Complete an X402 domain purchase with a pre-built payment signature. + + This method posts to the X402 purchase endpoint with the PAYMENT-SIGNATURE header, + completing the purchase. The caller is responsible for producing the signature + (e.g., using the x402 Python library or receiving it from another party). + + sid: Search ID from a previous search request + domain: Domain name to purchase + payment_signature: The PAYMENT-SIGNATURE header value for X402 payment authorization + + Returns on success: + - `offer_id`: The offer identifier. + - `domain`: The purchased domain name. + - `state`: The purchase state. + """ + contact = Contact(**self.get_contact_information()) + if not contact or not contact.is_valid(): raise ValueError("Contact information is required") + return self.purchase_x402(sid, domain, payment_signature, contact) + +# %% ../nbs/00_core.ipynb #ba509af9 @patch def domains(self:Sherlock): "List of domains owned by the authenticated user" r = httpx.get(f"{API_URL}/api/v0/domains/domains", headers=_mk_headers(self.atok)) return _handle_response(r) -# %% ../nbs/00_core.ipynb 52 +# %% ../nbs/00_core.ipynb #4d166b63 @patch def _domains(self:Sherlock): """ @@ -354,7 +436,7 @@ def _domains(self:Sherlock): return self.domains() -# %% ../nbs/00_core.ipynb 54 +# %% ../nbs/00_core.ipynb #bab0ec3a @patch def update_nameservers(self:Sherlock, domain_id: str, # domain id @@ -363,7 +445,7 @@ def update_nameservers(self:Sherlock, r = httpx.patch(f"{API_URL}/api/v0/domains/{domain_id}/nameservers", json={"nameservers": nameservers}, headers=_mk_headers(self.atok)) return _handle_response(r) -# %% ../nbs/00_core.ipynb 55 +# %% ../nbs/00_core.ipynb #253a7ae0 @patch def _update_nameservers(self:Sherlock, domain_id: str, @@ -378,7 +460,7 @@ def _update_nameservers(self:Sherlock, """ return self.update_nameservers(domain_id, nameservers) -# %% ../nbs/00_core.ipynb 57 +# %% ../nbs/00_core.ipynb #c2931c6f @patch def dns_records(self:Sherlock, domain_id: str): # domain id @@ -387,7 +469,7 @@ def dns_records(self:Sherlock, headers=_mk_headers(self.atok)) return _handle_response(r) -# %% ../nbs/00_core.ipynb 58 +# %% ../nbs/00_core.ipynb #bf0cfb67 @patch def _dns_records(self:Sherlock, domain_id: str): @@ -405,7 +487,7 @@ def _dns_records(self:Sherlock, """ return self.dns_records(domain_id) -# %% ../nbs/00_core.ipynb 60 +# %% ../nbs/00_core.ipynb #673aff53 @patch def create_dns(self:Sherlock, domain_id: str, # domain id @@ -419,7 +501,7 @@ def create_dns(self:Sherlock, headers=_mk_headers(self.atok), json=data) return _handle_response(r) -# %% ../nbs/00_core.ipynb 62 +# %% ../nbs/00_core.ipynb #1d3daf3e @patch def _create_dns_record(self:Sherlock, domain_id: str, # domain id @@ -439,7 +521,7 @@ def _create_dns_record(self:Sherlock, return self.create_dns(domain_id, type, name, value, ttl) -# %% ../nbs/00_core.ipynb 64 +# %% ../nbs/00_core.ipynb #7434a3f3 @patch def update_dns(self:Sherlock, domain_id: str, # domain id @@ -455,7 +537,7 @@ def update_dns(self:Sherlock, headers=_mk_headers(self.atok), json=data) return _handle_response(r) -# %% ../nbs/00_core.ipynb 66 +# %% ../nbs/00_core.ipynb #58530c9e @patch def _update_dns_record(self:Sherlock, domain_id: str, # domain id @@ -479,7 +561,7 @@ def _update_dns_record(self:Sherlock, return self.update_dns(domain_id, record_id, type, name, value, ttl) -# %% ../nbs/00_core.ipynb 67 +# %% ../nbs/00_core.ipynb #8e37ec11 @patch def delete_dns(self:Sherlock, domain_id: str, # domain id @@ -489,7 +571,7 @@ def delete_dns(self:Sherlock, headers=_mk_headers(self.atok)) return _handle_response(r) -# %% ../nbs/00_core.ipynb 69 +# %% ../nbs/00_core.ipynb #30881439 @patch def _delete_dns_record(self:Sherlock, domain_id: str, # domain id @@ -503,7 +585,7 @@ def _delete_dns_record(self:Sherlock, return self.delete_dns(domain_id, record_id) -# %% ../nbs/00_core.ipynb 71 +# %% ../nbs/00_core.ipynb #dbd9d091 @patch def as_tools(self:Sherlock): "Return the Sherlock class as a list of tools ready for agents to use" @@ -511,8 +593,10 @@ def as_tools(self:Sherlock): self._me, self._set_contact_information, self._get_contact_information, - self._search, + self._search, self._request_payment_details, + self._get_x402_purchase_offers, + self._purchase_x402, self._domains, self._update_nameservers, self._dns_records, @@ -521,11 +605,11 @@ def as_tools(self:Sherlock): self._delete_dns_record, ]) -# %% ../nbs/00_core.ipynb 74 +# %% ../nbs/00_core.ipynb #42d75195 from inspect import signature, Parameter import argparse -# %% ../nbs/00_core.ipynb 75 +# %% ../nbs/00_core.ipynb #9208ac74 @patch def as_cli(self:Sherlock): "Return the Sherlock class as a list of tools ready for agents to use" @@ -535,6 +619,8 @@ def as_cli(self:Sherlock): self.get_contact_information, self.search, self.request_payment_details, + self.get_x402_purchase_offers, + self.purchase_x402, self.domains, self.update_nameservers, self.dns_records, @@ -543,7 +629,7 @@ def as_cli(self:Sherlock): self.delete_dns, ]) -# %% ../nbs/00_core.ipynb 77 +# %% ../nbs/00_core.ipynb #3d3b4435 def main(): "CLI interface for Sherlock" parser = argparse.ArgumentParser() diff --git a/sherlock/crypto.py b/sherlock/crypto.py index c5b8532..a1fea89 100644 --- a/sherlock/crypto.py +++ b/sherlock/crypto.py @@ -2,21 +2,21 @@ # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_crypto.ipynb. -# %% auto 0 +# %% auto #0 __all__ = ['generate_keys', 'from_pk_hex', 'priv_key_hex'] -# %% ../nbs/01_crypto.ipynb 3 +# %% ../nbs/01_crypto.ipynb #dffd2cc1 from cryptography.hazmat.primitives.asymmetric import ed25519 import secrets; secrets.token_hex(32) -# %% ../nbs/01_crypto.ipynb 7 +# %% ../nbs/01_crypto.ipynb #8128d49e def generate_keys(): pk = ed25519.Ed25519PrivateKey.generate() pub = pk.public_key().public_bytes_raw().hex() return pk, pub -# %% ../nbs/01_crypto.ipynb 11 +# %% ../nbs/01_crypto.ipynb #eccbd593 def from_pk_hex(priv): pk = ed25519.Ed25519PrivateKey.from_private_bytes(bytes.fromhex(priv)) return pk, pk.public_key().public_bytes_raw().hex()