GithubHelp home page GithubHelp logo

kevinheavey / solders Goto Github PK

View Code? Open in Web Editor NEW
179.0 4.0 20.0 4.85 MB

A high-performance Python toolkit for Solana, written in Rust

Home Page: https://kevinheavey.github.io/solders/

License: Apache License 2.0

Python 44.16% Rust 55.76% Makefile 0.08%
blockchain defi nft pyo3 python rust solana

solders's Introduction


PyPI version License: MIT

Solders

solders is a high-performance Python toolkit for Solana, written in Rust. It provides robust solutions to the following problems:

  • Core SDK stuff: keypairs, pubkeys, signing and serializing transactions - that sort of thing.
  • RPC stuff: building requests and parsing responses (no networking stuff - if you want help with that, solana-py is your friend).
  • Integration testing stuff: the solders.bankrun module is an alternative to solana-test-validator that's much more convenient and much faster. It's based on solana-program-test if you know that is.

What about solana-py?

solders and solana-py are good friends. solana-py uses solders under the hood extensively in its core API and RPC API. The main differences are:

  • solders doesn't have functions to actually interact with the RPC server (though solana-py does use the RPC code from solders).
  • solders doesn't provide SPL Token and SPL Memo clients.
  • solana-py may not have support for all the RPC requests and responses provided by solders.
  • solana-py doesn't have anything like the bankrun testing kit.

Since solana-py uses solders under the hood and they don't duplicate each other's features, you should just use whichever library you need.

Installation

pip install solders

Note: Requires Python >= 3.7.

Example Usage

>>> from solders.message import Message
>>> from solders.keypair import Keypair
>>> from solders.instruction import Instruction
>>> from solders.hash import Hash
>>> from solders.transaction import Transaction
>>> from solders.pubkey import Pubkey
>>> program_id = Pubkey.default()
>>> arbitrary_instruction_data = bytes([1])
>>> accounts = []
>>> instruction = Instruction(program_id, arbitrary_instruction_data, accounts)
>>> payer = Keypair()
>>> message = Message([instruction], payer.pubkey())
>>> blockhash = Hash.default()  # replace with a real blockhash
>>> tx = Transaction([payer], message, blockhash)

Development

Setup

  1. Install poetry
  2. Install dev dependencies:
poetry install
  1. Activate the poetry shell:
poetry shell

Testing

  1. Run maturin develop to compile the Rust code.
  2. Run make fmt, make lint, and make test.

solders's People

Contributors

hkey0 avatar kevinheavey avatar morgandri1 avatar ryder-69 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

solders's Issues

from_json & to_json doesn't work for MessageV0

test = MessageV0.default() assert MessageV0.from_json(test.to_json()) == test

Above gives error "ValueError: invalid type: integer 128, expected message prefix byte at line 1 column 4"

Transaction serialize not found

I met this error when calling send_bundle of jito-python library.
data=tx.serialize(),
^^^^^^^^^^^^
AttributeError: 'solders.transaction.Transaction' object has no attribute 'serialize'

Do we actually have a function to serialize Transaction class?

Base58 private key export?

Hello, I was wondering if there's any way (or any plan) to return a keypair as a base58 private key form (like can be imported into phantom and such)

Understanding how to handle RPC responses elegantly with this library

Hi, feel free to close this issue if more appropriately handled elsewhere.

I'm trying to understand the best way to use solders w/ the solana-py library.

We have a very common pattern that looks like this

from solana.rpc.api import Client
from solders.signature import Signature
c = Client("https://api.mainnet-beta.solana.com")
c.get_transaction(
    Signature.from_string(
        '2DUH6nXnS4EXCqPdgxGvAiLyemZ8WkB69VyjhiGgE3HZxsbXWdk8SuZbGCkyV7oN6b7DHHVggaB8QSCKp5YNk7QJ'
    ),
    'json'
)

This gives us a GetTransactionResp object.

We often want the memo, err (if any), instructions, etc. from the response, but it seems like every step of the way we have potential None to deal with. And since python does not have null chaining/coalescing, we end up having a lot of code like this

        result = transaction_resp.value
        if not result:
            return None

        meta = result.transaction.meta
        if not meta:
            return None

        if meta.err:
            return None 

        tx_message = result.transaction.transaction.message # this will fail because only some of the types this could return have a message

Is there a different/more canonical way we should be using types from this library?

Not enough signers when setting compute units

Getting this error when trying to set compute units:
solders.SignerError: not enough signers

Simple code example:

...

transaction = Transaction()
transaction.add(set_compute_unit_limit(1_000_000))
transaction.add(set_compute_unit_price(1_000))
transaction.add(transfer(TransferParams(
    from_pubkey=sender.pubkey(),
    to_pubkey=receiver.pubkey(),
    lamports=int(0.001*LAMPORT_PER_SOL))
    ))

signature = client.send_transaction(transaction, sender)

There is no error when removing the 2 instructions for setting compute units.

Feature Request: HD Wallet Support

Hello, Solders caregivers,

I am currently using the Solders library to manage wallets and signatures in Solana. I have found the library to be extremely useful for my needs. However, there is one feature that I believe could significantly improve its functionality: HD Wallet support.

Feature Description:
HD Wallets provide a more secure and organized way to manage multiple addresses and keys. With HD Wallets, users can create an address tree from a single seed, which makes it easier to manage a large number of addresses and increases security by enabling different addresses to be used for different transactions.

This feature will make it easier to transfer the wallets we have created to other wallets. Normally I would like to add this feature, but due to my busy schedule, I can't do it right now, so I decided to create this issue in case someone is interested.

Thank you for considering this feature request. I look forward to any discussion or feedback on this proposal.

Best regards.

jsonalias requirement

Small detail: jsonalias does not seem to have v0.1.1 available on pypi, nor does its repo have a Release/Tag, this makes building a conda package a tid bit challenging. Could you harmonize them (a Release/Tag of jsonalias 0.1.1 on its repo would work for conda purpose)

grpc interop

i'm working with grpc. wondering about a good way to interop with protobuf and solders. will default to rust and try using it with solders later.

from solders.transaction import VersionedTransaction
tx_obj = subscribe_update.transaction.transaction
type(tx_obj)
<class 'geyser_pb2.SubscribeUpdateTransactionInfo'>
(Pdb) deserialized = VersionedTransaction.from_bytes(str)
*** ValueError: io error: unexpected end of file
(Pdb) transaction = VersionedTransaction.from_bytes(transaction_bytes)

see rpcpool/yellowstone-grpc#346

bytes(solders.keypair.Keypair()) conversion is not obvious

Issue
As of now it is difficult to figure out how to treat the 32-bit Keypair().secret() field.
My first assumption was that it is a private key, but it is only a portion of the private key. bytes(Keypair()) is the private key. Type-casting Keypair to bytes is far from being obvious, in my opinion.

Suggested solution
add a private_key() or privkey() or to_bytes() method to solders.keypair.Keypair

Background
Myself, someone without any prior Solana background, it took me nearly 3 hours of research to finally arrive at this repo, and out of desperation - to try and learn something from the auto-tests.
Keypair docs do a good job of showing how to create an account with a private key. But do nothing to explain how to export a private key for later use.
Stack overflow and various media outlets are full of outdated information(or are written for JS libraries), most of it either referring to solana Keypair which featured .generate() and .private_key, among others, or are simply dead links to the docs. Not to mention that all of the info is on Solana SO, where I for example don't even have an account and can't comment and therefore can't ask for clarification or further pointers.
Long story short - I imagine most people new to Solana would also find themselves in a similar situation.
I have found a solution, but it was costly. Therefore I request that you bring back some form of a .private_key field, or even at least expose the ability to type-cast to bytes through some obvious field/method.

Panic output can't be prevented

Not sure how to title this. My code looks like this:

from solders.keypair import Keypair
try:
    Keypair.from_base58_string("test")
except:
    print("Invalid keypair")

Output:

called `Result::unwrap()` on an `Err` value: signature::Error { source: Some(candidate keypair byte array is too short) }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Invalid keypair

I expect only my print statement to be outputted, but the rust panic prints are still printed. I've tried redirecting stderr too.

Types for Structs with Serde Field Attributes

Hi,
I am trying to port over some code from Typescript to Python and I want to use the types provided by this library for it (the Solana RPC response types). In web3.js, there is a ParsedTransactionWithMeta type which is used in methods like getParsedTransaction. This is the getTransaction rpc method with encoding set to jsonParsed. I wanted a type corresponding to it in Python.

This library has a GetTransactionResp type however that is a bit different from the data I get back.

I took a look at the Solana code which that method calls to fetch data. The type for the data returned for jsonParsed encoding is EncodedConfirmedTransactionWithStatusMeta as also in the value param in the library here. However, that Rust struct has #[serde(flatten)] field attribute on transaction and so in the data returned from rpc (or web3), that type gets flattened. This unfortunately leads to a difference between the type provided in this library and the one corresponding to the data. I think I've seem code in the past here where they rename certain fields too so this could have impact there too.

Can I do anything about this? Any help will be very appreciated.

Thank you for your hard work!

How do I load keypair in my phantom wallet

# generate random keypair
keypair = Keypair()
secret = keypair.secret()

Now how do I use this secret to get a private key that I can import into my Phantom wallet?

I tried using b58encode but it doesn't work, I'm sure I'm missing something silly but not sure what

VersionedTransaction.from_json Not Working

I get the following error when trying to convert a tx to json and back

ValueError: invalid type: integer 128, expected message prefix byte at line 1 column 164

url = f'https://quote-api.jup.ag/v6/quote?inputMint=So11111111111111111111111111111111111111112&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&amount=1000000&slippageBps=1'
resp = requests.get(url)
quote = resp.json()
swap_transaction = requests.post(
    'https://quote-api.jup.ag/v6/swap',
    headers={'Content-Type': 'application/json'},
    json={
        'quoteResponse': quote,
        'userPublicKey': authority,
        'wrapAndUnwrapSol': True
    }
).json()
swapTransactionBuf = base64.b64decode(swap_transaction['swapTransaction'])
tx = VersionedTransaction.from_bytes(swapTransactionBuf)
t = tx.to_json()
t1 = VersionedTransaction.from_json(t)
   

`VersionTransaction.message` doesn't seem to contain the initial message byte, which prevents proper signing

I have an unsigned transaction message with these bytes:

AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAHCibUXsV2mzsJIvnE2gwNr+KfdyhHR5jyjkRbbnsGqAqrbpmrfEFlbO4+KjKolmWHzvZZb9rc1UMgak9Lohf1TbIuGahV4LYOAa+/QpNyN+z24Zpt5aOJKfYLZNemgIMEIwMGRm/lIRcy/+ytunLDm+e8jOW7xfcSayxDmzpAAAAAjJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+FkGm4hX/quBhPtof2NGGMA12sQ53BrrO1WYoPAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKnG+nrzvtutOj1l82qryXQxsbvkwtL24OR8pgIDRS9dYQR51S3tv2vF7NCdhFNKNK6ll1BDs2/QKyRlC7WEQ1lcqDfg9KjP9p0wygkxqsEwpYGnVWUr/AMYoE3FpEQfz4YHAwAFAsBcFQAEBgABAAUGBwAGAgABDAIAAACAlpgAAAAAAAcBAQERBAYAAgAIBgcACREHAAIOAA8KAQILDA0OBxANCSLlF8uXeuOtKgABAAAAAguAlpgAAAAAAL3fAwAAAAAAAQAABwMBAAABCQFX3lqkjZImQIEkZIQeRHTRorA35IRt35MRRVgR3QxiLARfYGFiA11eZQ==

    b = base64.b64decode(unsigned_tx_base64) # from snippet above
    raw_tx = solders_tx.VersionedTransaction.from_bytes(b)
    message = raw_tx.message
    signature = keypair.sign_message(message.to_json())
    signatures = [signature]

    if len(raw_tx.signatures) > 1:
        signatures.extend(list(raw_tx.signatures[1:]))

    tx = solders_tx.VersionedTransaction.populate(message, signatures)

But the resulting signature is wrong. I looked a bit deeper at this, and I see that the first byte of raw_tx.message is different from other sources (I was comparing against https://github.com/gagliardetto/solana-go).

> [i for i in  bytes(message)[:10]]

[1, 0, 7, 10, 38, 212, 94, 197, 118, 155]

When I'm comparing against the other library, where the transaction is submitted properly:

[128, 1, 0, 7, 10, 38, 212, 94, 197, 118, ...]

Any suggestions on the correct way to sign the message?

I want to get private key

payer = Keypair()
print(str(payer.pubkey()))
base58.b58encode(payer.secret().hex()).decode("utf-8")

get private key is incorrect, how to get correct private key

Unable to import metaplex metadata program PublicKey

Hi, I'm unable to import the metaplex metadata program PublicKey

How to reproduce

from solders.pubkey import Pubkey
Pubkey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s")

Error:

ValueError: expected a sequence of length 32 (got 43)

Why I am getting import problem?

Hi Kevin,

I have import problem for solders. I am using Python 3.11.0 also i have tried 3.7 but same problem.

image
image

Can you help me?

Thanks.

0.20.0 aarch64 wheels on PyPi seem to be bad

Hello, testing this lib on an ARM machine I'm getting the following error (this is not happening on an x86 container):

  File "/usr/local/lib/python3.12/site-packages/solana/rpc/api.py", line 11, in <module>
    from solders.rpc.responses import (
ModuleNotFoundError: No module named 'solders.rpc.responses'

Checking the wheel files on PyPI you can notice that the aarch64 wheels are suspiciously smaller than the wheels for other archs:

image

How to use LookupTables resolution?

So I try:

        keys = tx.value.transaction.transaction.message.account_keys
        if tx.value.transaction.transaction.message.address_table_lookups is not None:
            for tl in tx.value.transaction.transaction.message.address_table_lookups:
                lt = address_lookup_table_account.AddressLookupTable(tl.account_key, [])
                addrs = lt.lookup(tx.value.slot, tl.readonly_indexes + tl.writable_indexes, SLOT_HASHES)
                keys.extend(lt.addresses)

Yet sometimes it does not go after lt line silently and sometimes it throws:

lt = address_lookup_table_account.AddressLookupTable(tl.account_key, [])
TypeError: argument 'meta': 'Pubkey' object cannot be converted to 'LookupTableMeta'
python-BaseException

How to expand address table correctly?

solders.pubkey import Pubkey not working with verify function.

What versions does your solders library support?

Environment:
Python 3.10
solana-cli 1.17
solana==0.30.2
httpx==0.23.0
solders .19
python-telegram-bot==13

Telegram python bot script does not recognize
from solders.pubkey import Pubkey. Import "solders.pubkey" could not be resolved from source

I have dependencies on :

# Verify the signed message
try:
    username, phantom_wallet_address = signed_message.strip().split(' ', 1)  # Limit the split to 1 occurrence
    if Pubkey.verify(username.encode("utf-8"), signed_message, Pubkey(phantom_wallet_address)):

JSON parsing error on websocket_api.program_subscribe()

Hello,
I am reporting this while using solana-py==0.30.2 and solders==0.18.1
The reason I report this in solders and not solana-py is that the issue seem related directly to the JSON parsing, hence, solders.

I've taken the canonical example given on the documentation, and changed the log_subscribe call with program_subscribe.

Before any further investigation, you should check that the ID I provided is indeed supposed to be taken as a parameter for the program_subscribe method, as I am still a newbie with solana, and I may have taken it for a program address while it is not (but I believe it is, according to solscan).

Here is a full working example to reproduce :

import asyncio
from solders.pubkey import Pubkey
from solana.rpc.websocket_api import connect

LIQUIDITY_POOL_PROGRAM_ID_V4 = '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8'

async def main():
    program_id = Pubkey.from_string(LIQUIDITY_POOL_PROGRAM_ID_V4)

    async with connect("wss://api.mainnet-beta.solana.com") as websocket:

        await websocket.program_subscribe(program_id)
        first_resp = await websocket.recv()
        subscription_id = first_resp[0].result
        print("First rep is : ")
        print(first_resp)
        print()
        response = await websocket.recv()
        print(response)

        await websocket.program_unsubscribe(subscription_id)

asyncio.run(main())

When running this, I get a stacktrace.
In order to provide something that may help, I put a few print in _process_rpc_response, basically printing the raw parameter
The output of the program looks like that, stacktrace included :

Print in websocket_api, by phylante, for debug : 
{"jsonrpc":"2.0","result":230891,"id":1}

First rep is : 
[SubscriptionResult {
    jsonrpc: TwoPointOh,
    id: 1,
    result: 230891,
}]

Print in websocket_api, by phylante, for debug : 
{"jsonrpc":"2.0","method":"programNotification","params":{"result":{"context":{"slot":236633032},"value":{"pubkey":"7wZpAKYM1uygtosoF42V4a5tVLsrzpSN6Uedaxc6vGrQ","account":{"lamports":16258560,"data":"error: data too large for bs58 encoding","owner":"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8","executable":false,"rentEpoch":0,"space":2208}}},"subscription":230891}}

Traceback (most recent call last):
  File "/home/phylante/code/solana_new_listings/src/listen_for_account.py", line 23, in <module>
    asyncio.run(main())
  File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
    return future.result()
  File "/home/phylante/code/solana_new_listings/src/listen_for_account.py", line 18, in main
    response = await websocket.recv()
  File "/home/phylante/code/solana_new_listings/venv/lib/python3.10/site-packages/solana/rpc/websocket_api.py", line 108, in recv
    return self._process_rpc_response(cast(str, data))
  File "/home/phylante/code/solana_new_listings/venv/lib/python3.10/site-packages/solana/rpc/websocket_api.py", line 350, in _process_rpc_response
    parsed = parse_websocket_message(raw)
solders.SerdeJSONError: a list or single element

Running this code with the program ID "11111111111111111111111111111111" gives me some notifications, so I assume this is a bug with this particular message.

Thanks in advance for looking into this.

Pubkey not allowing to convert to string?

Getting this error:

AttributeError: 'solders.pubkey.Pubkey' object has no attribute 'string'

when my code is:

from solders.keypair import Keypair

kp = Keypair()
print(kp.pubkey().string())

This error is happening on both 0.14.0 and 0.17.0

can't import using secret key ?

I'm not sure if i'm doing this correctly but when I try to import the secret key from an existing keypair it doesn't work for me, though I tried converting it to base58 it still doesn't work. Any help is appreciated !

this is how I generated the keypair and the secret key :
mnemo = Mnemonic("english")
seed = mnemo.to_seed(mnemonic)
keypair = Keypair.from_seed(seed[:32])
secret_key = Keypair.secret()

when I try to import :

private_key_base58 = base58.b58encode(secret_key).decode('utf-8')

keypair = Keypair.from_base58_string(private_key_base58)

^^^ this throws the error :
pyo3_runtime.PanicException: called Result::unwrap() on an Err value: signature::Error { source: Some(Keypair must be 64 bytes in length) }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.