Giter Club home page Giter Club logo

xrpl-py's People

Contributors

bharathchari avatar ckeshava avatar ckniffen avatar connorjchen avatar dangell7 avatar dependabot[bot] avatar florent-uzio avatar intelliot avatar jst5000 avatar jungsooyun avatar justinr1234 avatar khancode avatar ledhed2222 avatar limpidcrypto avatar mayurbhandary avatar mduo13 avatar mukulljangid avatar mvadari avatar n3tc4t avatar natenichols avatar pdp2121 avatar rikublock avatar ryangyoung avatar shawnxie999 avatar tedkalaw avatar tlongwell avatar wojake avatar yyolk 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

xrpl-py's Issues

Better support for transaction flags

Many transactions have specific flags that affect how the transaction works. It's important to be able to set these flags when sending certain types of transactions, and when reading/processing certain types of transactions it's even more important to be able to check them to understand the functionality of the transaction (most importantly, Partial Payments, #196). Preferably, this could be done by setting and getting specific options as booleans by name.

The map of flags to names I wrote a long time ago is still valid and, I believe, up-to-date (any new transaction types not mentioned there don't have any flags).

Furthermore, any bits in the Flags field of a transaction that don't represent defined flags MUST be 0. We should enforce that requirement on transactions too.

At the transaction model level, I think there is no reason to directly expose the flags field if we can expose, say, a dict of flag names to bools instead.

Memo List Error

I wanted to provide list of memos to the payment.

Here is the code:

memos=[
  Memo(
      memo_data=lite_payment,
      memo_format="signed/payload+1",
      memo_type="liteacc/payment",
  ),
  Memo(
      memo_data=keypairs.sign(lite_payment, liteseckey),
      memo_format="signed/signature+1",
      memo_type="liteacc/signature",
  ),
  Memo(
      memo_data=litepubkey,
      memo_format="signed/publickey+1",
      memo_type="liteacc/publickey",
  )
]

The error I receive is:

memos expected a typing.Optional[typing.List[xrpl.models.transactions.transaction.Memo]], received a <class 'list'>

Would like to instantiate an arbitrary Transaction model from (possibly incomplete) JSON.

I've found the modern ripple-lib model of prepareTransaction(incomplete_json, options) to be a really useful flow to follow when using the XRP Ledger. It allows you to see the exact transaction instructions you're about to sign, so that you can do any amount of "smart" auto-filling of stuff before the signing and even give the user a chance to inspect the generated instructions before they're converted into a hard-to-read binary format.

I was hoping to do something similar to that, and any number of other things that are similar to that, by passing a JSON-like dictionary (possibly created by decoding actual stringified JSON) into a Transaction() constructor, but it turns out that's not possible for a variety of reasons.

So I wrote code to do this, but I'm not sure this is the right way to go about it, and even if it does, I'm not sure where it would fit in the package. Personally, I'd love to just do something like tx_object = Transaction({"Account":"rf1...",...}) and have it work, but the transaction constructor doesn't work. I would also settle for tx_object = Transaction.from_json(...) or something like that.

Anyway, here's my code, and maybe someone else can figure out where to put it.

import re
from typing import Any, Dict, Union
from xrpl.models.exceptions import XRPLModelException
import xrpl.models.transactions as txmodels

TX_TYPES = {t:getattr(txmodels, t) for t in
            txmodels.transaction.TransactionType}

def _camel_to_snake(field: str) -> str:
    """Transforms (upper or lower) camel case to snake case.
    For example, 'TransactionType' becomes 'transaction_type'.
    """
    words = re.split(r"(?=[A-Z])", field)
    lower_words = [word.lower() for word in words if word]
    return "_".join(lower_words)

def tx_json_to_tx_object(dictionary: Union[int, Dict[str,Any]]
) -> txmodels.transaction.Transaction:
    """
    Creates a Transaction object based on a JSON-like dictionary of keys in the
    JSON format used by the binary codec, or an actual JSON string.

    Args:
        dictionary: The dictionary or JSON string to be instantiated.

    Returns:
        A Transaction object instantiated from the input
    """
    if type(dictionary) == str:
      import json
      dictionary = json.loads(dictionary)

    formatted_dict = {
        _camel_to_snake(key): value for (key, value) in dictionary.items()
    }
    # one-off conversion cases for transaction field names
    if "check_i_d" in formatted_dict:
        formatted_dict["check_id"] = formatted_dict["check_i_d"]
        del formatted_dict["check_i_d"]
    if "invoice_i_d" in formatted_dict:
        formatted_dict["invoice_id"] = formatted_dict["invoice_i_d"]
        del formatted_dict["invoice_i_d"]

    try:
        tt = formatted_dict.pop("transaction_type")
    except KeyError:
        raise XRPLModelException("TransactionType is required")

    if tt not in TX_TYPES.keys():
        raise XRPLModelException(f"TransactionType '{tt}' not recognized")
    txo = TX_TYPES[tt](**formatted_dict)

    return txo

txnNotFound issue

When using send_reliable_submission , xrpl it sometimes returns txnNotFound error. This is due to the fact that it calls submit transaction and fetch result by hash in quick succession which fails to find that transaction sometimes ( XRPLF/rippled#2828 )

Add method to submit raw JSON to clients

When working with advanced features that aren't on the ledger yet (e.g. NFTs, hooks, sidechains), you'd need to create your own models. But you can't use new models in the existing system.

Concise alias for safe signing

The names of the signing functions are too long. In particular, this example of normal usage is 90 characters long, which is longer than most coding style guides will allow a single line to be.

signed = xrpl.transaction.safe_sign_and_autofill_transaction(transaction, wallet, client)

We could introduce a simple alias method that handles this much more concisely. The name xrpl.transaction.sign is sufficient and it can take the same three arguments as safe_sign_and_autofill_transaction but with the client being optional:

  • If the client is provided, it works the same as (probably calls) xrpl.transaction.safe_sign_and_autofill_transaction
  • If the client is omitted, it works the same as xrpl.transaction.safe_sign_transaction

Reasons I think this name is sufficient:

  • "sign" should always be safe. (Related: #223)
  • Having "transaction" in the function name is redundant because it's part of the transaction submodule already.
  • The "autofill" part can be implied by whether you provide a client. The function can handle both styles based on what you give it.

Expose all relevant modules and classes under `xrpl`

A lot of the sample code ends up having literally dozens of lines of from X import Y statements, which is a stylistic choice that not everyone appreciates.

An alternative is to have one import xrpl statement at the beginning and to refer to everything by its paths within the library. We could allow this style as well by adding relevant imports and listing the relevant submodules in the __all__ field of the __init__.py files at various levels of the module, starting with the very top of the module. That way you don't have to explicitly import all the different submodules and pieces of all of them unless you want to.

Improve transaction signing/submission flow

There are a lot of different ways of doing transaction signing and submission, but we've learned from experience what models work better than others, and the way I most recommend doing things is as follows:

  1. Prepare Transaction (online)

    The main point of this is that you can inspect what you're signing before you even go get your secret keys out of secure storage. For offline transaction construction, you have to skip this step and provide things like Fee and Sequence yourself. (The Wallet class in xrpl-py is well-equipped to handle the Sequence part at least.)

    Input:

    • Incomplete transaction instructions (JSON or otherwise)
    • network client
    • (optional) instructions for auto-filling, such as max ledger offset (e.g. a number to add to the latest validated ledger to get LastLedgerSequence), fee options, path options, etc.

    Output:

    • Complete transaction instructions (JSON or otherwise) with all required signing fields filled out, X-addresses converted to classic address & tag, and any other preprocessing.
      • Should provide a sensible LastLedgerSequence field by default, as well as any other best practices, with options to explicitly configure/disable them.
  2. Sign Transaction (offline OK)

    Input:

    • Complete transaction instructions (JSON or otherwise) with all required signing fields filled out
    • Seed or private key

    Output:

    • Signed transaction blob
    • (Optional) Signed transaction JSON.
    • Transaction ID (hash).
  3. Submit-and-Verify (online)

    It's not a bad thing to also have a simple submit function that returns the preliminary result only.

    Input:

    • Signed transaction blob (or, potentially, signed transaction in JSON format) with a LastLedgerSequence parameter.
    • (Optional) Minimum ledger index. If not provided, assume that the minimum ledger is the latest validated ledger +1 at time of submission. (Note: Not the current open ledger; during times of instability, there could be several closed but unvalidated ledgers, and a transaction could theoretically end up getting into a replacement for one of the closed ledgers.) This is useful because you can use this function to resubmit the signed transaction any number of times and it'll still work consistently and idempotently as long as you always provide the same minimum ledger index. To implement truly robust transaction submission, one would need to look up and save the original min ledger index alongside the signed blob before submitting the transaction at all.

    Output:

    • The transaction's final result code. Optionally it could raise an exception if the result code is anything other than tesSUCCESS.

There are several ways in which the current version of xrpl-py DOES NOT work this way:

  • What little auto-filling functionality exists is tied into signing. It should be separated out into a prepare function. Though, it's probably also fine if the X-address conversion happens during signing since that's deterministic and does not require a network connection.
  • The auto-filling functionality should be more robust, such as providing a LastLedgerSequence by default and filling Fee and Sequence.
  • The signing functionality should ensure that the transaction has all required fields so you don't end up signing and trying to submit an incomplete transaction. Submitting a signed but incomplete transaction is embarrassing and could be tricky for users to catch since the error only arises much later than the thing that caused it. Arguably it could also weaken security since it means you've created more signatures which gives people more data to use to try and break your keys.
  • send_reliable_submission() takes a wallet and unsigned transaction. That gives the impression that it might modify the transaction instructions and (re-)submit a replacement/modified version, which it absolutely MUST NOT do. (I don't think it does, but it's best if the function literally doesn't have what it would need to do that.) You can't use the existing function to implement truly reliable sending.

xrpl.transaction.get_transaction_from_hash() should take optional min_ledger and max_ledger params

The min_ledger and max_ledger params to the tx method help to reliably determine the outcome of a transaction. The get_transaction_from_hash() helper function should at least take min_ledger (it can choose max_ledger based on the LastLedgerSequence) and use them to more smartly categorize failures:

  • Transaction isn't in specified range (searched_all is true)
  • Server doesn't have sufficient ledger history, which can be further split into more results if you know the server's current max validated ledger:
    • Result isn't final (max_ledger is not yet validated)
    • Server is missing necessary history

Add a "kill switch" function for clients

Add a "kill switch" between the connection of a machine and a selected rippled node.

This may create a possibility for developers to add an "air-gapped wallet" and perhaps other features.

>connect node
>get wallet sequence
>get last ledger sequence
>end connection
>sign transaction
>copy signed transaction
>connect node
>send signed transaction securely

Maybe make it in:
xrpl.clients.end_connection , just a suggestion.

Protections against high fee values

A common mistake people make is fat-fingering the Fee value of a transaction, or letting a script auto-fill it during a brief moment of very high load. There have been several cases of people burning whole XRP (or even thousands of XRP!) to send payments for fractions of that value.

By default, ripple-lib won't let you sign any payment with a max fee higher than a default limit of 2 XRP. (You can configure it in the API constructor parameters, maxFeeXRP.) Even 2 XRP is a pretty high fee given that most transactions only need about 0.000010 XRP and even a special case like an EscrowFinish with a big fulfillment probably doesn't go above 0.001000 XRP.

The only big exception is AccountDelete, which requires a full 5 XRP. But deleting accounts is (and probably should be) a rare, unusual operation so it makes sense to require the user to use a special override to sign a transaction with a Fee value that high.

In short, We should raise an error when constructing or signing a transaction whose fee value is more than 2 whole XRP unless the user explicitly specifies an option to allow a higher maximum fee.

Signed Transaction & Reliable Submition, need support

I'm currently trying to create an airgapped transaction thingy:
image

As you all know, when someone signs a transaction, it'll look something like this:
Payment(account='AccountGoesHere', transaction_type=<TransactionType.PAYMENT: 'Payment'>, fee='10', sequence=Sequence, account_txn_id=None, flags=0, last_ledger_sequence=Ledger_Sequence, memos=None, signers=None, source_tag=None, signing_pub_key='PubKeySignGoesHere', txn_signature='TransactionSigGoesHere', amount='Amount', destination='DestinationAccount', destination_tag=None, invoice_id=None, paths=None, send_max=None, deliver_min=None)

I tried doing this:

  1. Enter Seed
  2. Sign transaction
  3. Copy and paste to another code that'll send the signed transaction to XRPL node

But it dint work since, i did this: ( I know this is a dumb solution, just a showcase on what I tried, help )
image

Does anyone know how I can fix this error of mine, thank you!

Issued Currency Tutorial

Hey guys,

I'm trying to follow along the tutorial here and I'm receiving an error when sending the payment tx on step 9.

The error I receive is:

{'send_max': 'A currency conversion requires a send_max value.'}

I then added send_max as the value/3840. Then I get this error:

tecPATH_DRY: Path could not send partial amount.

Here is what the lines are after steps 1 to 8.

COLD:
{'account': 'r9JTgGHEe898ecnhfTm5gULGi9LKXhNgkP', 'balance': '0', 'currency': 'CSC', 'limit': '0', 'limit_peer': '10000000000', 'no_ripple': True, 'no_ripple_peer': False, 'quality_in': 0, 'quality_out': 0}

HOT:
{'account': 'r4WujFh1qwpTaQa2FXFT9gJ4oPo7S6tohb', 'balance': '0', 'currency': 'CSC', 'limit': '10000000000', 'limit_peer': '0', 'no_ripple': False, 'no_ripple_peer': True, 'quality_in': 0, 'quality_out': 0}

Thanks for your help.

Use @property for Response.is_successful (or just make it an attribute)

Can you spot what's wrong with the following code?

prelim_result = xrpl.transaction.submit_transaction_blob(blob, client)
print("Preliminary transaction result:", prelim_result)
if not prelim_result.is_successful:
    exit("Submit failed.")

It's not obvious, but is_successful is a method, so it'll always evaluate as truthy; you'll never run the exit() call here. The correct syntax would've been if not prelim_result.is_successful().

We could make is_successful a property using the @property decorator, or maybe just evaluate it at the time the model is instantiated. With that change, result.is_successful would return the correct boolean value. If someone got it wrong and tried result.is_successful() that would raise an error (TypeError: 'bool' object is not callable), which is much better than the sneaky "always true" bug from the above example.

Sugar methods should raise on non-success response.

The "sugar" methods in the xrpl.transaction module should raise an exception when the server returns a error response rather than just silently returning a Response model object with is_successful() = False. (The methods in the xrpl.account module already do.) Possibly all responses should do that.

It's weird and tiresome to have to check is_successful() on every call if you're doing a bunch of calls back to back; I'd rather just put a whole block of calls in a try/except and handle network errors or what-have-you all at once.

prelim_result = xrpl.transaction.submit_transaction_blob(signed, client)
print("Preliminary transaction result:", prelim_result)
if not prelim_result.is_successful():
    exit("Submit failed.")
tx_id = prelim_result.result["tx_json"]["hash"]

If applied to all responses, this would render #192 moot.

Add logging capabilities to the clients

There's no way to automatically log information for debugging purposes from the client. We should add an optional param to the clients that takes a logging method.

Non Standard Currency Code TrustSet not working.

I found this code from @ledhed2222 at this issue #259.

This works as is.

# I had to change some of the import paths
from xrpl.clients import JsonRpcClient
from xrpl.models.amounts import IssuedCurrencyAmount
from xrpl.models.transactions import Payment, TrustSet
from xrpl.transaction import safe_sign_and_submit_transaction
from xrpl.wallet import Wallet, generate_faucet_wallet

CLIENT = JsonRpcClient("https://s.altnet.rippletest.net:51234/")

ISSUING_WALLET = generate_faucet_wallet(CLIENT,debug=True)
RECEIVING_WALLET = generate_faucet_wallet(CLIENT,debug=True)

# create a trust line from receiver to issuer. in this case, 
# the receiver trusts the issuer for up to $1,000,000 USD
safe_sign_and_submit_transaction(
  TrustSet(
    account=RECEIVING_WALLET.classic_address,
    limit_amount=IssuedCurrencyAmount(
      issuer=ISSUING_WALLET.classic_address,
      currency="USD",
      value="1000000",
   ),
  ),
  RECEIVING_WALLET,
  CLIENT,
)

# now, issue some USD from issuer to receiver
safe_sign_and_submit_transaction(
  Payment(
    account=ISSUING_WALLET.classic_address,
    amount=IssuedCurrencyAmount(
      issuer=ISSUING_WALLET.classic_address,
      currency="USD",
      value="100",
    ),
    destination=RECEIVING_WALLET.classic_address,
  ),
  ISSUING_WALLET,
  CLIENT,
)

And gives this output:

Attempting to fund address r3giEShN7EPdkhx4AyJRnDr9v9mW9xt49B
Faucet fund successful.
Attempting to fund address rLv15mLwVRhRFUGSuhCHd7Hu5XnqmsQrEs
Faucet fund successful.
Response(status=<ResponseStatus.SUCCESS: 'success'>, result={'accepted': True, 'account_sequence_available': 18616526, 'account_sequence_next': 18616526, 'applied': True, 'broadcast': True, 'engine_result': 'tesSUCCESS', 'engine_result_code': 0, 'engine_result_message': 'The transaction was applied. Only final in a validated ledger.', 'kept': True, 'open_ledger_cost': '10', 'queued': False, 'tx_blob': '120000220000000024011C10CD201B011C10E661D5038D7EA4C680000000000000000000000000005553440000000000545236BEAEF61FA33A6258F545614BF58B57571368400000000000000A7321ED37EDB3F903B2FB6433B105C19FEB636185B5CEC18C26708D6B04F61B8A29FB637440DF40FAD216C8566F1C696CD9F3C6F02E5300D44E27CFE1FDB99FBDFA7EF0886DC0B4C9DB10A6A827BC09045300209C1E5F6E14C1DF9D73CD8960D752AB9FD6078114545236BEAEF61FA33A6258F545614BF58B5757138314DA9AC65CBCEF2AB812BCA5CEE9E180AF0A31E4C2', 'tx_json': {'Account': 'r3giEShN7EPdkhx4AyJRnDr9v9mW9xt49B', 'Amount': {'currency': 'USD', 'issuer': 'r3giEShN7EPdkhx4AyJRnDr9v9mW9xt49B', 'value': '100'}, 'Destination': 'rLv15mLwVRhRFUGSuhCHd7Hu5XnqmsQrEs', 'Fee': '10', 'Flags': 0, 'LastLedgerSequence': 18616550, 'Sequence': 18616525, 'SigningPubKey': 'ED37EDB3F903B2FB6433B105C19FEB636185B5CEC18C26708D6B04F61B8A29FB63', 'TransactionType': 'Payment', 'TxnSignature': 'DF40FAD216C8566F1C696CD9F3C6F02E5300D44E27CFE1FDB99FBDFA7EF0886DC0B4C9DB10A6A827BC09045300209C1E5F6E14C1DF9D73CD8960D752AB9FD607', 'hash': '0E5E087C9F8A83037C2EB97FE42105C4652C9BA404E30082E50BF2457484FDE5'}, 'validated_ledger_index': 18616530}, id=None, type=<ResponseType.RESPONSE: 'response'>)

However when I replace the currency code with the example nonstandard currency code at https://xrpl.org/currency-formats.html#currency-codes

from xrpl.clients import JsonRpcClient
from xrpl.models.amounts import IssuedCurrencyAmount
from xrpl.models.transactions import Payment, TrustSet
from xrpl.transaction import safe_sign_and_submit_transaction
from xrpl.wallet import Wallet, generate_faucet_wallet

non_standard_currency_code = "0158415500000000C1F76FF6ECB0BAC600000000"

CLIENT = JsonRpcClient("https://s.altnet.rippletest.net:51234/")

ISSUING_WALLET = generate_faucet_wallet(CLIENT,debug=True)
RECEIVING_WALLET = generate_faucet_wallet(CLIENT,debug=True)

# create a trust line from receiver to issuer. in this case, 
# the receiver trusts the issuer for up to $1,000,000 USD
safe_sign_and_submit_transaction(
  TrustSet(
    account=RECEIVING_WALLET.classic_address,
    limit_amount=IssuedCurrencyAmount(
      issuer=ISSUING_WALLET.classic_address,
      currency=non_standard_currency_code,
      value="1000000",
   ),
  ),
  RECEIVING_WALLET,
  CLIENT,
)

# now, issue some USD from issuer to receiver
safe_sign_and_submit_transaction(
  Payment(
    account=ISSUING_WALLET.classic_address,
    amount=IssuedCurrencyAmount(
      issuer=ISSUING_WALLET.classic_address,
      currency=non_standard_currency_code,
      value="100",
    ),
    destination=RECEIVING_WALLET.classic_address,
  ),
  ISSUING_WALLET,
  CLIENT,
)

I get this error:

Attempting to fund address rMUNabsbHeHVDkLhXvbYKEHMcuLpAoazfc
Faucet fund successful.
Attempting to fund address rGDUq8RcWMgYku4Ri8LHDSddTu818ifWo6
Faucet fund successful.
UnicodeDecodeError: 'ascii' codec can't decode byte 0xec in position 0: ordinal not in range(128)

I tried
non_standard_currency_code = u"0158415500000000C1F76FF6ECB0BAC600000000"

and
non_standard_currency_code = "0158415500000000C1F76FF6ECB0BAC600000000".encode('utf-8')

Neither fixed it.

Does anyone know how to create a TrustSet and Payment with a nonstandard currency code?

Partial Payments protections

Messing up Partial Payments is one of the costliest, common mistakes people make when building on the XRP Ledger.

People look at the "Amount" field of a successful transaction and assume that's how much it delivered, when actually, it may have delivered a minuscule amount instead.

ripple-lib attempts to protect against this in a couple ways, which we should adapt or improve on:

  • getTransaction() and getTransactions() remove the field (specification.destination.amount in the ripple-lib specific format) from the parsed transaction response. You have to pass an option (includeRawTransaction) to get the original version with the potentially-misleading raw field.
  • The outcome of getTransaction() shows a balance_changes object that parses through the metadata to show how much currency (of varying types) was actually delivered.
  • It doesn't have any protections when calling the rippled APIs directly using .request(methodname, options).

One thing we could do fairly safely is to rename the field from Amount field of Payments to DeliverMax in some or all cases. The JSON format is not really "canonical" anyway, only the binary format, so you can call the fields whatever you want as long as you know what they correspond to.

See also: XRPLF/rippled#3484

Wallet creation should default to Ed25519

There are lots of reasons to prefer Ed25519 over secp256k1 signatures. The default for rippled's wallet creation commands is secp256k1 only because Ed25519 didn't exist at the time. For a brand-new interface that doesn't have to maintain backwards compatibility with old seeds, we should default to the better algorithm instead, and that means Ed25519:

  • 20-30ร— faster computations (creating or verifying signatures)
  • More secure against side-channel and padding attacks
  • Harder to screw up the implementation

Remove models for signing using a rippled server

The request models to sign a transaction using a rippled server are unnecessary and can be used unsafely. To reduce confusion, we should remove them. The rippled sign method is admin-only by default and deprecated anyway.

The following should be removed:

Furthermore, the model xrpl.models.requests.Submit should be an alias for xrpl.models.requests.SubmitOnly.

More/earlier validity checks built into the Transaction models

I briefly mentioned in #189 that transaction signing doesn't check that all required fields are specified, but there are a lot of other checks for validity that we could implement in the transaction models to help users catch errors soonerโ€”before successfully signing the serialized transaction instructions, preferably even sooner.

Many of the checks would be dependent on the transaction type, with the goal of catching malformed transactions or improper arguments. In particular, we should probably check many of the arguments passed (and raise TypeErrors as appropriate), especially if they could create potentially valid transactions with different values than intended. (For example, ripple-lib once had a bug where passing in a float as the number of drops could get serialized as a much larger number of drops because it got treated as an integer.) There are type hints in the definitions, but Python doesn't use those at runtime, so right now something like this doesn't even raise an error(!!!):

>>> from xrpl.models.transactions import Payment
>>> Payment(account=[1,2,3], fee=False, memos="not how memos works", amount="blue", destination=(lambda x:x))

Payment(account=[1, 2, 3], transaction_type=<TransactionType.PAYMENT: 'Payment'>, fee=False, sequence=None, 
account_txn_id=None, flags=0, last_ledger_sequence=None, memos='not how memos works', signers=None, 
source_tag=None, signing_pub_key=None, txn_signature=None, amount='blue', destination=<function <lambda> at 
0x7f0606274b80>, destination_tag=None, invoice_id=None, paths=None, send_max=None, deliver_min=None)

The Payment transaction type already has some checks along these lines, but it could have some more, and many of the other transaction types are much sparser on validity checks. Some examples:

  • All fields that take an address should be checked to be a valid address (base58 or X-address). This includes (not an exhaustive list):
    • Account (common field)
    • Account field nested inside the Signer objects in Signers common field
    • Account field nested in the SignerEntry objects in the SignerEntries field of SignerListSet
    • Destination in many transaction types
    • RegularKey in SetRegularKey
    • Authorize and Unauthorize in DepositPreauth
  • A transaction's flags field must have zeroes for any bits that are not associated with a protocol-defined flag (See also: #204). tfFullyCanonicalSig is defined for all transactions, and the other transaction flags are all type-specific. The AccountSet, Payment, OfferCreate, PaymentChannelClaim, and TrustSet types all have their own flags. (Also, the EnableAmendment pseudo-transaction type.)
  • All fields that take hexadecimal should be checked to contain only characters that are valid in hex (upper or lower). This includes any field whose internal type is Blob, Hash128, or Hash256. Hash160 is a special case since it's mainly used for currency codes.
  • Fields that take unsigned int values should be checked for range (for example, UInt32 values should not be over 2ยณยฒ).
  • Currency codes have various restrictions we should check.
  • Fields that represent dates should not be in the past. For example, CancelAfter should not be less than the current time in seconds since the "Ripple Epoch".
  • The fee amount is a nonzero amount of XRP.

Some more type-specific examples (not an exhaustive list):

  • AccountDelete should check that the destination is not the sender.
  • AccountSet should check that the values passed to set_flag and clear_flag are not equal, and any value provided is one of the protocol-defined AccountSet Flags. (Preferably we could provide the valid flags as an Enum.)
    • Also, any values provided in these fields should not contradict any transaction flags values provided.
  • AccountSet should check that the kessage_key field is exactly 33 bytes of hexadecimal and the first byte must be 0xED, 0x02, or 0x03. (meaning, it's probably a valid Ed25519 or secp256k1 public key in compressed form)
  • DepositPreauth should check that authorize and unauthorize do not match the sender's (classic) address.
  • OfferCreate should check that the taker_pays and taker_gets are not the same currency+issuer (or not both XRP).
  • PaymentChannelClaim should check that, if signature is supplied, public_key is also supplied and vice-versa.
  • Payment should check that direct-XRP-to-XRP payments don't provide paths or send_max.
  • Payment should check that only partial payments can use deliver_min
  • SetRegularKey should check that the regular_key contains a valid (classic) address that does not match the sender's classic address.
  • SignerListSet should check that the signer weights add up to at least the quorum and the quorum is not 0 unless signer_entries is omitted (for deleting the list)

All the checks I've listed here can be performed offline. It's out of scope for this ticket, but a future feature could implement an "online" checker (called explicitly with a client instance) that confirms things based on the state of the network, like whether the destination of a payment (or accountdelete/escrow/etc.) requires destination tags, allows XRP, requires deposit authorization; whether an account meets the conditions to be deleted; whether a provided channel ID, check ID, etc. exists in the ledger, and so on.

`engine_result` key not always present

This line assumes that key engine_result is present in the result:

if result["engine_result"] != "tesSUCCESS":

If the transaction fails due to it being invalid, then there is an error key but no engine_result key:

> /Users/mhamilton/Development/xrpl-py-master/xrpl/transaction/reliable_submission.py(87)send_reliable_submission()
-> result = cast(Dict[str, Any], submit_response.result)
(Pdb) n
> /Users/mhamilton/Development/xrpl-py-master/xrpl/transaction/reliable_submission.py(89)send_reliable_submission()
-> if result["engine_result"] != "tesSUCCESS":
(Pdb) result
{'error': 'invalidTransaction', 'error_exception': "Field 'Sequence' is required but missing.", 'error_message': None, 'request': {'command': 'submit', 'fail_hard': False, 'tx_blob': '1200142200000000201B00F3B87963D5438D7EA4C68000000000000000000000000000545448000000000020DC6E24D317DBB61E3AB21B6EDA3CD385722895732103C1A9F40969639BBC16C5D357EC8A536C2D092E8D3CFE61D89C877D15A4118E337446304402207BF6C565F0FC6EBB03D02D207B90473E3913C5A7AF291F161EA9F51F3959C73F02201C20479D8CA5D2E1D336B1877227217BB0765416168EFAD46B96CC191946DA9D811486BFF88F6A34F0F7661009EBD618ECD5223E47E5'}}
(Pdb) n
KeyError: 'engine_result'
> /Users/mhamilton/Development/xrpl-py-master/xrpl/transaction/reliable_submission.py(89)send_reliable_submission()
-> if result["engine_result"] != "tesSUCCESS":

It needs to check for error key as well (maybe first?) and throw meaningful error if that is present.

WebSocket client with subscription support

One of the most common operations in using the XRP Ledger is waiting for something to happen. The WebSocket interface makes this convenient with subscription streams; it would be great to be able to instantiate a WebSocket client from Python and then subscribe to accounts, ledgers, etc.

Better partial payment handling

We have a better solution in xrpl.js now:

  • Add warnings to the rippled "warnings" field in responses
  • WON'T DO: For transaction streams: have one stream called unsafeTransaction for all transaction, and transaction for all non-partial-payment transactions
  • Print console.warns for partial payments

Memo input format

Can you give me an example of entering a memo? I get an error.

memo = xrpl.models.transactions.transaction.Memo(memo_data='72656e74',
memo_type='687474703a2f2f6578616d706c652e636f6d2f6d656d6f2f67656e65726963')

tx_payment = xrpl.models.transactions.Payment(account=account, amount=xrpl.utils.xrp_to_drops(10),
destination='rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe', sequence=sequence,memos=memo)

tx_payment_signed = xrpl.transaction.safe_sign_and_autofill_transaction(tx_payment, wallet, client)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-130-8a68d5e42e22> in <module>
----> 1 tx_payment_signed = xrpl.transaction.safe_sign_and_autofill_transaction(tx_payment, wallet, client)

***\lib\site-packages\xrpl\models\base_model.py in _from_dict_single_param(cls, param, param_type, param_value)
    140                     # try to use this Union-ed type to process param_value
    141                     return cls._from_dict_single_param(
--> 142                         param, param_type_option, param_value
    143                     )
    144                 except XRPLModelException:

***\lib\site-packages\xrpl\models\base_model.py in _from_dict_single_param(cls, param, param_type, param_value)
    120             # param_type is Any
    121             return param_value
--> 122         print(param_type.__reduce__()[1][0])
    123         if param_type.__reduce__()[1][0] == List:
    124             # param_type is a List

TypeError: descriptor '__reduce__' of 'object' object needs an argument

Async client takes a while to close the connection

For example if I run the following script with the websockets package within a context manager managing the session, it closes quickly.

https://gist.github.com/yyolk/42b67ec5f1b1a17e273b5497e9eaad88#file-01__websockets__xrpl_stat_table-py

If I run the same code, with xrpl-py which is set up to handle the websocket identically, the websocket does close but it takes a while.

https://gist.github.com/yyolk/42b67ec5f1b1a17e273b5497e9eaad88#file-02__xrpl-py__xrpl_stat_table-py

My first instinct was to see where there was a difference in how websockets handled it and how xrpl-py does. For the most part it's identical, following the dunder method for __aexit__ to close() when called from the AsyncWebsocketClient.__aexit__

async def __aexit__(
self: AsyncWebsocketClient,
_exc_type: Type[BaseException],
_exc_val: BaseException,
_trace: TracebackType,
) -> None:
"""Exits an async context after closing itself."""
await self.close()

It led me to notice a few assert statements in the code that seemed pretty reasonable for conditional checks

async def _do_close(self: WebsocketBase) -> None:
"""Closes the connection."""
if not self.is_open():
return
assert self._handler_task is not None # mypy
assert self._websocket is not None # mypy
assert self._messages is not None # mypy

However if I run with python -O or in optimized mode, which will drop all assert statements, I do see the socket program close as swiftly as the one with websockets which I've seen very little difference in it's implementation. The only thing that stuck out to me at first glance was using the convenience of asyncio.shield(...) over spinning up a background thread and these assert statements.

I did a quick search and saw we only had asserts in a few places and tried dropping it here: https://github.com/yyolk/xrpl-py/tree/patch-20210705

-O command line arg: https://docs.python.org/3/using/cmdline.html#cmdoption-o

Add other currencies' faucet

Add other currencies' faucet in xrpl-py as functions,

faucets such as BTC, USD, EUR and ETH

xrpl.wallet.btc_faucet
xrpl.wallet.usd_faucet
xrpl.wallet.eur_faucet
xrpl.wallet.eth_faucet

This will increase liquidity on the testnet's DeX and enables developers to test Funds and the DeX.

Cannot delete TrustLine

Hello,

I cannot delete my TrustLine XAU/r9Dr5xwkeLegBeXq6ujinjSBLQzQ1zQGjH (Ripple Singapore).
This is my tx I sent:

tx = TrustSet(
    account="rG1ZxXD3z8KKekVdxr6iVB3LZKrxGxrCRx",
    limit_amount=IssuedCurrencyAmount(
        currency="XAU",
        issuer="r9Dr5xwkeLegBeXq6ujinjSBLQzQ1zQGjH",
        value="0"
    )
)

As you can see here https://bithomp.com/explorer/rG1ZxXD3z8KKekVdxr6iVB3LZKrxGxrCRx the transaction succeeded (multiple times) but the trust line is not deleted. It worked for many lines before except for that one. I also do not hold any positive balance for that trust line.

Thanks!

Utility function to get balance changes from transaction metadata

The getBalanceChanges() method of xrpl.js makes it much easier for beginners to reliably get information about how a transaction affected their balances of XRP and tokens. Doing it "by hand" is error-prone and confusing since it involves parsing a bunch of metadata, which has some sneaky edge cases, and RippleState nodes in particular, where the sign of the balance depends on which accounts are the high/low nodes.

It would be great to have a comparable function in xrpl-py.

It would also be nice to do getBalances() (which calls account_info and account_lines and does a little processing on the results) as well.

Raspberry Pi cant install module

Why can't I install xrpl.py module in Rasbian (Raspberry Pi)?

Error:

Could not find a version that satisfies the requirement xrpl.py (from versions : )
No matching distribution found for xrpl.py

How do i fix this issue?

Support

Hello,

I have a couple of questions related to creating and signing transactions for accounts/wallets which were created by Ledger Nano or Xumm. Is there a Slack, Telegram, or Discord channel to discuss?

Thanks,
Justin

Subscribe Order Book Web Socket Error

Not sure if I am using the web socket correctly. I didn't see a test for this use case either, but maybe I missed it

Here is my code:

from xrpl.clients import WebsocketClient
from xrpl.models.requests import (
    Subscribe,
)
from xrpl.models.requests.subscribe import (
    SubscribeBook,
)
import xrpl.core.binarycodec.types.currency as currency


# Fetch
# [START] Fetch
def fetch(issuer, cur):
    """fetch."""
    url = "wss://s.altnet.rippletest.net/"
    sellBook = SubscribeBook(
        taker_gets=currency.Currency.from_value('XRP'),
        taker_pays=currency.Currency.from_value(cur),
        taker=issuer
    )
    print(sellBook)
    req = Subscribe(
        books=[sellBook]
    )
    with WebsocketClient(url) as client:
        client.send(req)
        for message in client:
            print(message)
# [END] Fetch

Here is my error:

Object of type Currency is not JSON serializable

The entire trace:

Traceback (most recent call last):
  File "/Users/denisangell/projects/xarket-maker/main.py", line 27, in <module>
    response = fetch_open_orders(issuer, currency)
  File "/Users/denisangell/projects/xarket-maker/open_orders.py", line 32, in fetch
    client.send(req)
  File "/Users/denisangell/.virtualenvs/xarket-market/lib/python3.9/site-packages/xrpl/clients/websocket_client.py", line 194, in send
    run_coroutine_threadsafe(self._do_send(request), self._loop).result()
  File "/usr/local/Cellar/[email protected]/3.9.5/Frameworks/Python.framework/Versions/3.9/lib/python3.9/concurrent/futures/_base.py", line 445, in result
    return self.__get_result()
  File "/usr/local/Cellar/[email protected]/3.9.5/Frameworks/Python.framework/Versions/3.9/lib/python3.9/concurrent/futures/_base.py", line 390, in __get_result
    raise self._exception
  File "/Users/denisangell/.virtualenvs/xarket-market/lib/python3.9/site-packages/xrpl/asyncio/clients/websocket_base.py", line 162, in _do_send
    await self._do_send_no_future(request)
  File "/Users/denisangell/.virtualenvs/xarket-market/lib/python3.9/site-packages/xrpl/asyncio/clients/websocket_base.py", line 153, in _do_send_no_future
    json.dumps(
  File "/usr/local/Cellar/[email protected]/3.9.5/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "/usr/local/Cellar/[email protected]/3.9.5/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/local/Cellar/[email protected]/3.9.5/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/usr/local/Cellar/[email protected]/3.9.5/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Currency is not JSON serializable

How to disable Rippling

I cant find a way to disable rippling even if I specify the following flags 2097152, 1048576 it does nothing, please help.
does the sequence have anything to do with it?

code:

from xrpl.transaction import safe_sign_and_autofill_transaction, send_reliable_submission
from xrpl.models.amounts.issued_currency_amount import IssuedCurrencyAmount
from xrpl.models.transactions import Payment, TrustSet

# connecting to a production server
mainnet_url = "https://s.altnet.rippletest.net:51234"
client = JsonRpcClient(mainnet_url)

SEED = "๐Ÿ”‘"
from xrpl.wallet import Wallet
test_wallet = Wallet(seed=SEED, sequence=16237283)
addr = test_wallet.classic_address
print(test_wallet.seed)
cur = IssuedCurrencyAmount(currency="USD", issuer = "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq", value="10000")

my_payment = TrustSet(account=addr, limit_amount=cur, flags=[2097152, 1048576])
signed_tx = safe_sign_and_autofill_transaction(
    my_payment,
    test_wallet,
    client)

prelim_result = send_reliable_submission(signed_tx, client)

print(prelim_result.result)

thanks alot

generate_faucet_wallet() can stall

I've noticed that generate_faucet_wallet() has started stalling until it fails with a timeout (40 seconds).

I suspect the sleep() loop has a race condition that can cause it to loop forever, but I'm not sure entirely where.

Wallet class usage is reversed from xrpl.js

Both xrpl.js and xrpl-py have two ways of instantiating their Wallet class:

  • from an existing seed
  • generating a new random seed

In both cases, one of these options is the default constructor and the other is a class method. However, which is which is reversed between the libraries.

Library xrpl-py xrpl.js
existing seed Wallet(seed, sequence) Wallet.fromSeed(seed)
new random seed Wallet.create() new Wallet()

I think the xrpl.js way is better, and it might help people move through the ecosystem if the methods were named and used similarly.

To do that, we would:

  • Deprecate the arguments to the default Wallet() constructor (make them optional, but still supported)
  • Add a .fromSeed(seed) class method to Wallet. It can supply a default sequence number of 0 or something.

Pseudo-transaction models

To be able to instantiate arbitrary transactions from the ledger, you need to also be able to recognize and handle pseudo-transactions.

We should have models for these and a way to instantiate "any transaction you may have found in a ledger" into the appropriate model. That way people could monitor all ledger transactions, and interact with them in a Pythonic way.

Wallet doesn't support regular key

When signing in xrpl-py, you need to use a wallet object if you want to use any of the sugar. However, the wallet object doesn't support regular keys; you can't change the signing key to a regular key.

WebSocket Sync client: support attaching event handlers, making a request with callback

The way xrpl.js lets you do requests and event handlers with the WebSocket client is pretty handy. It would be nice if xrpl-py's sync WebSocket client supported something similar, with .request(cmd, callback) and .on(event_type, callback) methods.

I implemented a basic form of this as follows, though it's not quite production ready (it doesn't support multiple .on() callbacks nor .off(), it doesn't enforce types, etc.):

class SmartWSClient(xrpl.clients.WebsocketClient):
    def __init__(self, *args, **kwargs):
        self._handlers = {}
        self._pending_requests = {}
        self._id = 0
        super().__init__(*args, **kwargs)

    def on(self, event_type, callback):
        """
        Map a callback function to a type of event message from the connected
        server. Only supports one callback function per event type.
        """
        self._handlers[event_type] = callback

    def request(self, req_dict, callback):
        if "id" not in req_dict:
            req_dict["id"] = f"__auto_{self._id}"
            self._id += 1
        # Work around https://github.com/XRPLF/xrpl-py/issues/288
        req_dict["method"] = req_dict["command"]
        del req_dict["command"]

        req = xrpl.models.requests.request.Request.from_xrpl(req_dict)
        req.validate()
        self._pending_requests[req.id] = callback
        self.send(req)

    def handle_messages(self):
        for message in self:
            if message.get("type") == "response":
                if message.get("status") == "success":
                    # Remove "status": "success", since it's confusing. We raise on errors anyway.
                    del message["status"]
                else:
                    raise Exception("Unsuccessful response:", message)

                msg_id = message.get("id")
                if msg_id in self._pending_requests:
                    self._pending_requests[msg_id](message)
                    del self._pending_requests[msg_id]
                else:
                    raise Exception("Response to unknown request:", message)

            elif message.get("type") in self._handlers:
                self._handlers[message.get("type")](message)

To use this, you do something like this:

with SmartWSClient(self.ws_url) as client:
    # Subscribe to ledger updates
    client.request({
            "command": "subscribe",
            "streams": ["ledger"],
            "accounts": ["rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe"]
        },
        lambda message: print("Got subscribe response, message["result"])
    )
    client.on("ledgerClosed", lambda message: print("Ledger closed:", message))
    client.on("transaction", lambda message: print("Got transaction notif:", message))

    # Look up our balance right away
    client.request({
            "command": "account_info",
            "account": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
            "ledger_index": "validated"
        },
        lambda m: print("Faucet balance in drops:", m["result"]["account_data"]["Balance"])
    )
    # Start looping through messages received. This runs indefinitely.
    client.handle_messages()

You can of course define more detailed handlers and pass them by name instead of using lambdas.

safe_sign_transaction breaks on X-address with omitted destination tag [v1.2.0]

Summary

The safe_sign_transaction(payment, wallet) method should succeed if the payment has a Destination that is an X-address with a tag and no DestinationTag field (it should fill in the field automatically), but it fails with a KeyError instead.

Steps to Reproduce

>>> import xrpl
>>> w = xrpl.wallet.Wallet.create()
>>> p = xrpl.models.transactions.Payment(account=w.classic_address, sequence=1, amount=xrpl.utils.xrp_to_drops(20), fee="10", flags=0, destination=xrpl.core.addresscodec.classic_address_to_xaddress("rDUNw4eWCFCJpuL4ikbnczYhTh2hZdZ9Uf", tag=1, is_test_network=True))
>>> xrpl.transaction.safe_sign_transaction(p, w)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.9/site-packages/xrpl/transaction/main.py", line 86, in safe_sign_transaction
    return asyncio.run(
  File "/usr/lib/python3.9/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/usr/lib/python3.9/site-packages/xrpl/asyncio/transaction/main.py", line 79, in safe_sign_transaction
    transaction_json = _prepare_transaction(transaction, wallet)
  File "/usr/lib/python3.9/site-packages/xrpl/asyncio/transaction/main.py", line 169, in _prepare_transaction
    _validate_account_xaddress(transaction_json, "Destination", "DestinationTag")
  File "/usr/lib/python3.9/site-packages/xrpl/asyncio/transaction/main.py", line 209, in _validate_account_xaddress
    if json[tag_field] and json[tag_field] != tag:
KeyError: 'DestinationTag'

Add Cross-Currency Payments

Add functions that supports "cross-currency payments", this essentially means exchanging fiat-to-fiat using XRP,

Cross-currency payments that exchange two issued currencies automatically use XRP, when it decreases the cost of the payment, by connecting order books to deepen the pool of available liquidity. For example, a payment sending from USD to MXN automatically converts USD to XRP and then XRP to MXN if doing so is cheaper than converting USD to MXN directly.

If we really think about it, if Alice wants to exchange her MXN to JPY, there is a very small and a non-competitive orderbook since there is little to no demand for JPY/MXN, Alice can utilize cross-currency payments, which has more liquidity:

MXN -> XRP: XRP -> JPY

Since there is liquidity in both MXN/XRP and JPY/XRP, Alice can exchange her MXN to JPY in seconds cheaply,

TLDR: Add cross-currency payment functions
Add function in:

- xrpl.transaction.**cross_currency**
   > from_currency
       = The initial currency being used to exchange to "to_currency" using (from_currency)/XRP. 
   > to_currency
       = The currency chosen to receive from the cross-currency payment

Refer to: https://xrpl.org/cross-currency-payments.html

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.