Repository: michaelhly/solana-py (language: python)

Issue:
Add NATIVE_DECIMALS & LAMPORTS_PER_SOL constants
Hello @michaelhly!

I've added the LAMPORTS_PER_SOL and DEFAULT_DECIMALS constants to the library. 

Given the popularity of this library and frequent reuse of these in scripts, it seemed beneficial to centralize them to the library to reduce redundancy and potential errors. This change can help make many codebases cleaner and more maintainable. 

Thank you for considering this addition!


Discussion / hints:

Fix patch:
diff --git a/src/solana/constants.py b/src/solana/constants.py
new file mode 100644
index 00000000..887bc5de
--- /dev/null
+++ b/src/solana/constants.py
@@ -0,0 +1,30 @@
+"""Solana constants."""
+
+from solders.pubkey import Pubkey
+
+LAMPORTS_PER_SOL: int = 1_000_000_000
+"""Number of lamports per SOL, where 1 SOL equals 1 billion lamports."""
+
+SYSTEM_PROGRAM_ID = Pubkey.from_string("11111111111111111111111111111111")
+"""Program ID for the System Program."""
+
+CONFIG_PROGRAM_ID: Pubkey = Pubkey.from_string("Config1111111111111111111111111111111111111")
+"""Program ID for the Config Program."""
+
+STAKE_PROGRAM_ID: Pubkey = Pubkey.from_string("Stake11111111111111111111111111111111111111")
+"""Program ID for the Stake Program."""
+
+VOTE_PROGRAM_ID: Pubkey = Pubkey.from_string("Vote111111111111111111111111111111111111111")
+"""Program ID for the Vote Program."""
+
+ADDRESS_LOOKUP_TABLE_PROGRAM_ID: Pubkey = Pubkey.from_string("AddressLookupTab1e1111111111111111111111111")
+"""Program ID for the Address Lookup Table Program."""
+
+BPF_LOADER_PROGRAM_ID: Pubkey = Pubkey.from_string("BPFLoaderUpgradeab1e11111111111111111111111")
+"""Program ID for the BPF Loader Program."""
+
+ED25519_PROGRAM_ID: Pubkey = Pubkey.from_string("Ed25519SigVerify111111111111111111111111111")
+"""Program ID for the Ed25519 Program."""
+
+SECP256K1_PROGRAM_ID: Pubkey = Pubkey.from_string("KeccakSecp256k11111111111111111111111111111")
+"""Program ID for the Secp256k1 Program."""
diff --git a/src/solana/vote_program.py b/src/solana/vote_program.py
index e1f5f1a8..cd661a98 100644
--- a/src/solana/vote_program.py
+++ b/src/solana/vote_program.py
@@ -6,12 +6,9 @@
 from solders.instruction import AccountMeta, Instruction
 from solders.pubkey import Pubkey
 
+from solana.constants import VOTE_PROGRAM_ID
 from solana._layouts.vote_instructions import VOTE_INSTRUCTIONS_LAYOUT, InstructionType
 
-VOTE_PROGRAM_ID: Pubkey = Pubkey.from_string("Vote111111111111111111111111111111111111111")
-"""Public key that identifies the Vote program."""
-
-
 # Instrection Params
 class WithdrawFromVoteAccountParams(NamedTuple):
     """Transfer SOL from vote account to identity."""
diff --git a/src/spl/token/constants.py b/src/spl/token/constants.py
index 4b81de4c..fa2de4de 100644
--- a/src/spl/token/constants.py
+++ b/src/spl/token/constants.py
@@ -24,3 +24,6 @@
 other Token program token type and can be useful when being called from other programs that interact
 with the Token Program's interface.
 """
+
+NATIVE_DECIMALS: int = 9
+"""Number of decimals for SOL and the  Wrapped SOL mint."""


Test patch:
diff --git a/tests/integration/test_async_http_client.py b/tests/integration/test_async_http_client.py
index babd7bc7..e77d6ec1 100644
--- a/tests/integration/test_async_http_client.py
+++ b/tests/integration/test_async_http_client.py
@@ -12,6 +12,7 @@
 from solders.transaction import VersionedTransaction
 from spl.token.constants import WRAPPED_SOL_MINT
 
+from solana.constants import VOTE_PROGRAM_ID
 from solana.rpc.async_api import AsyncClient
 from solana.rpc.commitment import Confirmed, Finalized, Processed
 from solana.rpc.core import RPCException, TransactionExpiredBlockheightExceededError
@@ -360,9 +361,7 @@ async def test_get_blocks(test_http_client_async):
 @pytest.mark.integration
 async def test_get_signatures_for_address(test_http_client_async: AsyncClient):
     """Test get signatures for addresses."""
-    resp = await test_http_client_async.get_signatures_for_address(
-        Pubkey.from_string("Vote111111111111111111111111111111111111111"), limit=1, commitment=Confirmed
-    )
+    resp = await test_http_client_async.get_signatures_for_address(VOTE_PROGRAM_ID, limit=1, commitment=Confirmed)
     assert_valid_response(resp)
 
 
diff --git a/tests/integration/test_http_client.py b/tests/integration/test_http_client.py
index d947281b..57af1224 100644
--- a/tests/integration/test_http_client.py
+++ b/tests/integration/test_http_client.py
@@ -10,13 +10,14 @@
 from solders.rpc.requests import GetBlockHeight, GetFirstAvailableBlock
 from solders.rpc.responses import GetBlockHeightResp, GetFirstAvailableBlockResp, Resp
 from solders.transaction import VersionedTransaction
-from spl.token.constants import WRAPPED_SOL_MINT
 
+from solana.constants import VOTE_PROGRAM_ID
 from solana.rpc.api import Client
 from solana.rpc.commitment import Confirmed, Finalized, Processed
 from solana.rpc.core import RPCException, TransactionExpiredBlockheightExceededError
 from solana.rpc.types import DataSliceOpts, TxOpts
 from solana.transaction import Transaction
+from spl.token.constants import WRAPPED_SOL_MINT
 
 from ..utils import AIRDROP_AMOUNT, assert_valid_response
 
@@ -344,9 +345,7 @@ def test_get_blocks(test_http_client: Client):
 @pytest.mark.integration
 def test_get_signatures_for_address(test_http_client: Client):
     """Test get signatures for addresses."""
-    resp = test_http_client.get_signatures_for_address(
-        Pubkey.from_string("Vote111111111111111111111111111111111111111"), limit=1, commitment=Confirmed
-    )
+    resp = test_http_client.get_signatures_for_address(VOTE_PROGRAM_ID, limit=1, commitment=Confirmed)
     assert_valid_response(resp)
 
 
diff --git a/tests/unit/test_async_client.py b/tests/unit/test_async_client.py
index d413e302..d299b3b0 100644
--- a/tests/unit/test_async_client.py
+++ b/tests/unit/test_async_client.py
@@ -9,6 +9,7 @@
 from solders.rpc.requests import GetSignaturesForAddress
 from solders.signature import Signature
 
+from solana.constants import SYSTEM_PROGRAM_ID
 from solana.exceptions import SolanaRpcException
 from solana.rpc.commitment import Finalized
 
@@ -26,7 +27,7 @@ async def test_async_client_http_exception(unit_test_http_client_async):
 def test_client_address_sig_args_no_commitment(unit_test_http_client_async):
     """Test generating getSignaturesForAddressBody."""
     expected = GetSignaturesForAddress(
-        Pubkey.from_string("11111111111111111111111111111111"),
+        SYSTEM_PROGRAM_ID,
         RpcSignaturesForAddressConfig(
             limit=5, before=Signature.default(), until=Signature.default(), commitment=CommitmentLevel.Processed
         ),
@@ -39,7 +40,7 @@ def test_client_address_sig_args_no_commitment(unit_test_http_client_async):
 
 def test_client_address_sig_args_with_commitment(unit_test_http_client_async):
     expected = GetSignaturesForAddress(
-        Pubkey.from_string("11111111111111111111111111111111"),
+        SYSTEM_PROGRAM_ID,
         RpcSignaturesForAddressConfig(limit=5, commitment=CommitmentLevel.Finalized),
     )
     actual = unit_test_http_client_async._get_signatures_for_address_body(
diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py
index 07e0b0ea..02d02f37 100644
--- a/tests/unit/test_client.py
+++ b/tests/unit/test_client.py
@@ -9,6 +9,7 @@
 from solders.rpc.requests import GetSignaturesForAddress
 from solders.signature import Signature
 
+from solana.constants import SYSTEM_PROGRAM_ID
 from solana.exceptions import SolanaRpcException
 from solana.rpc.commitment import Finalized
 
@@ -26,7 +27,7 @@ def test_client_http_exception(unit_test_http_client):
 def test_client_address_sig_args_no_commitment(unit_test_http_client):
     """Test generating getSignaturesForAddress body."""
     expected = GetSignaturesForAddress(
-        Pubkey.from_string("11111111111111111111111111111111"),
+        SYSTEM_PROGRAM_ID,
         RpcSignaturesForAddressConfig(
             limit=5, before=Signature.default(), until=Signature.default(), commitment=CommitmentLevel.Processed
         ),
@@ -39,7 +40,7 @@ def test_client_address_sig_args_no_commitment(unit_test_http_client):
 
 def test_client_address_sig_args_with_commitment(unit_test_http_client):
     expected = GetSignaturesForAddress(
-        Pubkey.from_string("11111111111111111111111111111111"),
+        SYSTEM_PROGRAM_ID,
         RpcSignaturesForAddressConfig(limit=5, commitment=CommitmentLevel.Finalized),
     )
     actual = unit_test_http_client._get_signatures_for_address_body(Pubkey([0] * 31 + [0]), None, None, 5, Finalized)