From 70cabbeef46c6fb10bb3909ce190e287beaad0bb Mon Sep 17 00:00:00 2001
From: Daniel Chew <cctdaniel@outlook.com>
Date: Thu, 4 Jan 2024 22:26:36 +0900
Subject: [PATCH 1/4] support pythtest-conformance and pythtest-crosschain

---
 pythclient/solana.py |  8 ++++++
 pythclient/utils.py  | 60 ++++++++++++++++++++++++--------------------
 setup.py             |  2 +-
 3 files changed, 42 insertions(+), 28 deletions(-)

diff --git a/pythclient/solana.py b/pythclient/solana.py
index e066d2f..e3c3e35 100644
--- a/pythclient/solana.py
+++ b/pythclient/solana.py
@@ -20,6 +20,8 @@
 TESTNET_ENDPOINT = "api.testnet.solana.com"
 MAINNET_ENDPOINT = "api.mainnet-beta.solana.com"
 PYTHNET_ENDPOINT = "pythnet.rpcpool.com"
+PYTHTEST_CROSSCHAIN_ENDPOINT = "api.pythtest.pyth.network"
+PYTHTEST_CONFORMANCE_ENDPOINT = "api.pythtest.pyth.network"
 
 SOLANA_DEVNET_WS_ENDPOINT = WS_PREFIX + "://" + DEVNET_ENDPOINT
 SOLANA_DEVNET_HTTP_ENDPOINT = HTTP_PREFIX + "://" + DEVNET_ENDPOINT
@@ -33,6 +35,12 @@
 PYTHNET_WS_ENDPOINT = WS_PREFIX + "://" + PYTHNET_ENDPOINT
 PYTHNET_HTTP_ENDPOINT = HTTP_PREFIX + "://" + PYTHNET_ENDPOINT
 
+PYTHTEST_CROSSCHAIN_WS_ENDPOINT = WS_PREFIX + "://" + PYTHTEST_CROSSCHAIN_ENDPOINT
+PYTHTEST_CROSSCHAIN_HTTP_ENDPOINT = HTTP_PREFIX + "://" + PYTHTEST_CROSSCHAIN_ENDPOINT
+
+PYTHTEST_CONFORMANCE_WS_ENDPOINT = WS_PREFIX + "://" + PYTHTEST_CONFORMANCE_ENDPOINT
+PYTHTEST_CONFORMANCE_HTTP_ENDPOINT = HTTP_PREFIX + "://" + PYTHTEST_CONFORMANCE_ENDPOINT
+
 class SolanaPublicKey:
     """
     Represents a Solana public key. This class is meant to be immutable.
diff --git a/pythclient/utils.py b/pythclient/utils.py
index 6d6752a..ee9764c 100644
--- a/pythclient/utils.py
+++ b/pythclient/utils.py
@@ -1,36 +1,42 @@
-import ast
-import dns.resolver
-from loguru import logger
 from typing import Optional
 
+from loguru import logger
+
 DEFAULT_VERSION = "v2"
 
 
 # Retrieving keys via DNS TXT records should not be considered secure and is provided as a convenience only.
 # Accounts should be stored locally and verified before being used for production.
-def get_key(network: str, type: str, version: str = DEFAULT_VERSION) -> Optional[str]:
+def get_key(network: str, type: str) -> Optional[str]:
     """
-    Get the program or mapping keys from dns TXT records.
-
-    Example dns records:
-
-        devnet-program-v2.pyth.network
-        mainnet-program-v2.pyth.network
-        testnet-mapping-v2.pyth.network
-        pythnet-mapping-v2.pyth.network
+    Get the program or mapping key.
+    :param network: The network to get the key for. Either "mainnet", "devnet", "testnet", "pythnet", "pythtest-conformance", or "pythtest-crosschain".
+    :param type: The type of key to get. Either "program" or "mapping".
     """
-    url = f"{network}-{type}-{version}.pyth.network"
-    try:
-        answer = dns.resolver.resolve(url, "TXT")
-    except dns.resolver.NXDOMAIN:
-        logger.error("TXT record for {} not found", url)
-        return ""
-    if len(answer) != 1:
-        logger.error("Invalid number of records returned for {}!", url)
-        return ""
-    # Example of the raw_key:
-    #     "program=FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH"
-    raw_key = ast.literal_eval(list(answer)[0].to_text())
-    # program=FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH"
-    _, key = raw_key.split("=", 1)
-    return key
+    if network == "pythnet":
+        program_key = "FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH"
+        mapping_key = "AHtgzX45WTKfkPG53L6WYhGEXwQkN1BVknET3sVsLL8J"
+    elif network == "mainnet":
+        program_key = "FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH"
+        mapping_key = "AHtgzX45WTKfkPG53L6WYhGEXwQkN1BVknET3sVsLL8J"
+    elif network == "devnet":
+        program_key = "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s"
+        mapping_key = "BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2"
+    elif network == "testnet":
+        program_key = "8tfDNiaEyrV6Q1U4DEXrEigs9DoDtkugzFbybENEbCDz"
+        mapping_key = "AFmdnt9ng1uVxqCmqwQJDAYC5cKTkw8gJKSM5PnzuF6z"
+    elif network == "pythtest-conformance":
+        program_key = "8tfDNiaEyrV6Q1U4DEXrEigs9DoDtkugzFbybENEbCDz"
+        mapping_key = "AFmdnt9ng1uVxqCmqwQJDAYC5cKTkw8gJKSM5PnzuF6z"
+    elif network == "pythtest-crosschain":
+        program_key = "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s"
+        mapping_key = "BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2"
+    else:
+        raise Exception(f"Unknown network: {network}")
+
+    if type == "program":
+        return program_key
+    elif type == "mapping":
+        return mapping_key
+    else:
+        raise Exception(f"Unknown type: {type}")
diff --git a/setup.py b/setup.py
index 51e0a81..bea9a0a 100644
--- a/setup.py
+++ b/setup.py
@@ -7,7 +7,7 @@
 
 setup(
     name='pythclient',
-    version='0.1.19',
+    version='0.1.20',
     packages=['pythclient'],
     author='Pyth Developers',
     author_email='contact@pyth.network',

From b65250d4a8862b8d49c23b2a62d84d685242874a Mon Sep 17 00:00:00 2001
From: Daniel Chew <cctdaniel@outlook.com>
Date: Thu, 4 Jan 2024 22:33:58 +0900
Subject: [PATCH 2/4] fix test

---
 tests/test_utils.py | 84 ++++++---------------------------------------
 1 file changed, 10 insertions(+), 74 deletions(-)

diff --git a/tests/test_utils.py b/tests/test_utils.py
index 357817b..642fd94 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,89 +1,25 @@
-from _pytest.logging import LogCaptureFixture
 import pytest
 
-from pytest_mock import MockerFixture
-
-from mock import Mock
-
-import dns.resolver
-import dns.rdatatype
-import dns.rdataclass
-import dns.message
-import dns.rrset
-import dns.flags
-
 from pythclient.utils import get_key
 
 
-@pytest.fixture()
-def answer_program() -> dns.resolver.Answer:
-    qname = dns.name.Name(labels=(b'devnet-program-v2', b'pyth', b'network', b''))
-    rdtype = dns.rdatatype.TXT
-    rdclass = dns.rdataclass.IN
-    response = dns.message.QueryMessage(id=0)
-    response.flags = dns.flags.QR
-    rrset_qn = dns.rrset.from_text(qname, 100, rdclass, rdtype)
-    rrset_ans = dns.rrset.from_text(qname, 100, rdclass, rdtype, '"program=gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s"')
-    response.question = [rrset_qn]
-    response.answer = [rrset_ans]
-    answer = dns.resolver.Answer(
-        qname=qname, rdtype=rdtype, rdclass=rdclass, response=response)
-    answer.rrset = rrset_ans
-    return answer
-
-
-@pytest.fixture()
-def answer_mapping() -> dns.resolver.Answer:
-    qname = dns.name.Name(labels=(b'devnet-mapping-v2', b'pyth', b'network', b''))
-    rdtype = dns.rdatatype.TXT
-    rdclass = dns.rdataclass.IN
-    response = dns.message.QueryMessage(id=0)
-    response.flags = dns.flags.QR
-    rrset_qn = dns.rrset.from_text(qname, 100, rdclass, rdtype)
-    rrset_ans = dns.rrset.from_text(qname, 100, rdclass, rdtype, '"mapping=BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2"')
-    response.question = [rrset_qn]
-    response.answer = [rrset_ans]
-    answer = dns.resolver.Answer(
-        qname=qname, rdtype=rdtype, rdclass=rdclass, response=response)
-    answer.rrset = rrset_ans
-    return answer
-
-
-@pytest.fixture()
-def mock_dns_resolver_resolve(mocker: MockerFixture) -> Mock:
-    mock = Mock()
-    mocker.patch('dns.resolver.resolve', side_effect=mock)
-    return mock
-
-
-def test_utils_get_program_key(mock_dns_resolver_resolve: Mock, answer_program: dns.resolver.Answer) -> None:
-    mock_dns_resolver_resolve.return_value = answer_program
+def test_utils_get_program_key() -> None:
     program_key = get_key("devnet", "program")
     assert program_key == "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s"
 
 
-def test_utils_get_mapping_key(mock_dns_resolver_resolve: Mock, answer_mapping: dns.resolver.Answer) -> None:
-    mock_dns_resolver_resolve.return_value = answer_mapping
+def test_utils_get_mapping_key() -> None:
     mapping_key = get_key("devnet", "mapping")
     assert mapping_key == "BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2"
 
 
-def test_utils_get_mapping_key_not_found(mock_dns_resolver_resolve: Mock,
-                                         answer_mapping: dns.resolver.Answer,
-                                         caplog: LogCaptureFixture) -> None:
-    mock_dns_resolver_resolve.side_effect = dns.resolver.NXDOMAIN
-    exc_message = f'TXT record for {str(answer_mapping.response.canonical_name())[:-1]} not found'
-    key = get_key("devnet", "mapping")
-    assert exc_message in caplog.text
-    assert key == ""
+def test_utils_invalid_network() -> None:
+    with pytest.raises(Exception) as e:
+        get_key("testdevnet", "mapping")
+    assert str(e.value) == "Unknown network: testdevnet"
 
 
-def test_utils_get_mapping_key_invalid_number(mock_dns_resolver_resolve: Mock,
-                                              answer_mapping: dns.resolver.Answer,
-                                              caplog: LogCaptureFixture) -> None:
-    answer_mapping.rrset = None
-    mock_dns_resolver_resolve.return_value = answer_mapping
-    exc_message = f'Invalid number of records returned for {str(answer_mapping.response.canonical_name())[:-1]}!'
-    key = get_key("devnet", "mapping")
-    assert exc_message in caplog.text
-    assert key == ""
+def test_utils_get_invalid_type() -> None:
+    with pytest.raises(Exception) as e:
+        get_key("devnet", "mappingprogram")
+    assert str(e.value) == "Unknown type: mappingprogram"

From 5ec552962dfdcceabf9d3b27071116d1682310e2 Mon Sep 17 00:00:00 2001
From: Daniel Chew <cctdaniel@outlook.com>
Date: Thu, 4 Jan 2024 23:39:22 +0900
Subject: [PATCH 3/4] address comments

---
 pythclient/utils.py | 62 ++++++++++++++++++++++-----------------------
 setup.py            |  2 +-
 2 files changed, 31 insertions(+), 33 deletions(-)

diff --git a/pythclient/utils.py b/pythclient/utils.py
index ee9764c..0f99290 100644
--- a/pythclient/utils.py
+++ b/pythclient/utils.py
@@ -1,42 +1,40 @@
 from typing import Optional
 
-from loguru import logger
 
-DEFAULT_VERSION = "v2"
-
-
-# Retrieving keys via DNS TXT records should not be considered secure and is provided as a convenience only.
-# Accounts should be stored locally and verified before being used for production.
 def get_key(network: str, type: str) -> Optional[str]:
     """
     Get the program or mapping key.
     :param network: The network to get the key for. Either "mainnet", "devnet", "testnet", "pythnet", "pythtest-conformance", or "pythtest-crosschain".
     :param type: The type of key to get. Either "program" or "mapping".
     """
-    if network == "pythnet":
-        program_key = "FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH"
-        mapping_key = "AHtgzX45WTKfkPG53L6WYhGEXwQkN1BVknET3sVsLL8J"
-    elif network == "mainnet":
-        program_key = "FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH"
-        mapping_key = "AHtgzX45WTKfkPG53L6WYhGEXwQkN1BVknET3sVsLL8J"
-    elif network == "devnet":
-        program_key = "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s"
-        mapping_key = "BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2"
-    elif network == "testnet":
-        program_key = "8tfDNiaEyrV6Q1U4DEXrEigs9DoDtkugzFbybENEbCDz"
-        mapping_key = "AFmdnt9ng1uVxqCmqwQJDAYC5cKTkw8gJKSM5PnzuF6z"
-    elif network == "pythtest-conformance":
-        program_key = "8tfDNiaEyrV6Q1U4DEXrEigs9DoDtkugzFbybENEbCDz"
-        mapping_key = "AFmdnt9ng1uVxqCmqwQJDAYC5cKTkw8gJKSM5PnzuF6z"
-    elif network == "pythtest-crosschain":
-        program_key = "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s"
-        mapping_key = "BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2"
-    else:
-        raise Exception(f"Unknown network: {network}")
+    keys = {
+        "pythnet": {
+            "program": "FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH",
+            "mapping": "AHtgzX45WTKfkPG53L6WYhGEXwQkN1BVknET3sVsLL8J",
+        },
+        "mainnet": {
+            "program": "FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH",
+            "mapping": "AHtgzX45WTKfkPG53L6WYhGEXwQkN1BVknET3sVsLL8J",
+        },
+        "devnet": {
+            "program": "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s",
+            "mapping": "BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2",
+        },
+        "testnet": {
+            "program": "8tfDNiaEyrV6Q1U4DEXrEigs9DoDtkugzFbybENEbCDz",
+            "mapping": "AFmdnt9ng1uVxqCmqwQJDAYC5cKTkw8gJKSM5PnzuF6z",
+        },
+        "pythtest-conformance": {
+            "program": "8tfDNiaEyrV6Q1U4DEXrEigs9DoDtkugzFbybENEbCDz",
+            "mapping": "AFmdnt9ng1uVxqCmqwQJDAYC5cKTkw8gJKSM5PnzuF6z",
+        },
+        "pythtest-crosschain": {
+            "program": "gSbePebfvPy7tRqimPoVecS2UsBvYv46ynrzWocc92s",
+            "mapping": "BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2",
+        },
+    }
 
-    if type == "program":
-        return program_key
-    elif type == "mapping":
-        return mapping_key
-    else:
-        raise Exception(f"Unknown type: {type}")
+    try:
+        return keys[network][type]
+    except KeyError:
+        raise Exception(f"Unknown network or type: {network}, {type}")
diff --git a/setup.py b/setup.py
index bea9a0a..fa5c65c 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
 from setuptools import setup
 
-requirements = ['aiodns', 'aiohttp>=3.7.4', 'backoff', 'base58', 'dnspython', 'flake8', 'loguru', 'typing-extensions', 'pytz', 'pycryptodome']
+requirements = ['aiodns', 'aiohttp>=3.7.4', 'backoff', 'base58', 'flake8', 'loguru', 'typing-extensions', 'pytz', 'pycryptodome']
 
 with open('README.md', 'r', encoding='utf-8') as fh:
     long_description = fh.read()

From 1adcc0d6c0c1c200d77c625bbbf6902563bb688a Mon Sep 17 00:00:00 2001
From: Daniel Chew <cctdaniel@outlook.com>
Date: Thu, 4 Jan 2024 23:41:04 +0900
Subject: [PATCH 4/4] fix test

---
 tests/test_utils.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tests/test_utils.py b/tests/test_utils.py
index 642fd94..f0fc86c 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -16,10 +16,10 @@ def test_utils_get_mapping_key() -> None:
 def test_utils_invalid_network() -> None:
     with pytest.raises(Exception) as e:
         get_key("testdevnet", "mapping")
-    assert str(e.value) == "Unknown network: testdevnet"
+    assert str(e.value) == "Unknown network or type: testdevnet, mapping"
 
 
 def test_utils_get_invalid_type() -> None:
     with pytest.raises(Exception) as e:
         get_key("devnet", "mappingprogram")
-    assert str(e.value) == "Unknown type: mappingprogram"
+    assert str(e.value) == "Unknown network or type: devnet, mappingprogram"