xrplf / xrpl-py Goto Github PK
View Code? Open in Web Editor NEWA Python library to interact with the XRP Ledger (XRPL) blockchain
License: ISC License
A Python library to interact with the XRP Ledger (XRPL) blockchain
License: ISC License
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.
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
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:
Account
(common field)Account
field nested inside the Signer objects in Signers
common fieldAccount
field nested in the SignerEntry objects in the SignerEntries field of SignerListSetDestination
in many transaction typesRegularKey
in SetRegularKeyAuthorize
and Unauthorize
in DepositPreauthCancelAfter
should not be less than the current time in seconds since the "Ripple Epoch".Some more type-specific examples (not an exhaustive list):
destination
is not the sender.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.)
flags
values provided.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)authorize
and unauthorize
do not match the sender's (classic) address.taker_pays
and taker_gets
are not the same currency+issuer (or not both XRP).signature
is supplied, public_key
is also supplied and vice-versa.paths
or send_max
.deliver_min
regular_key
contains a valid (classic) address that does not match the sender's classic address.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.
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
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.
I'm currently trying to create an airgapped transaction thingy:
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:
But it dint work since, i did this: ( I know this is a dumb solution, just a showcase on what I tried, help )
Does anyone know how I can fix this error of mine, thank you!
Conditional escrows use the PREIMAGE-SHA-256 crypto-condition format to define the binary format for conditions and fulfillments. It would be great to have quick access to native code that can secure create a fulfillment and derive the condition for use in EscrowCreate and EscrowFinish transactions.
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.
We have a better solution in xrpl.js now:
unsafeTransaction
for all transaction, and transaction
for all non-partial-payment transactionsThe "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.
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'>
Both xrpl.js and xrpl-py have two ways of instantiating their Wallet
class:
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:
Wallet()
constructor (make them optional, but still supported).fromSeed(seed)
class method to Wallet
. It can supply a default sequence number of 0 or something.send_reliable_submission method raises exception in cases where a transaction hash should have generated like terQUEUED. Thus, unable to get transaction hash, which is essential to query that transaction status later.
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.
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.
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.
>>> 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'
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.getTransaction()
shows a balance_changes
object that parses through the metadata to show how much currency (of varying types) was actually delivered.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
The Secret Numbers standard (XLS-12) defines an alternate format for an account seed or private key, which is used by XUMM wallet and possibly others. The Wallet class should support instantiation from an array or string of secret numbers.
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.
>>> import xrpl
>>> amount = xrpl.utils.xrp_to_drops(22)
AttributeError: module 'xrpl' has no attribute 'utils'
This line assumes that key engine_result
is present in the result:
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.
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.
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?
We should have models for ledger data, including the ledger header and all types of ledger objects that can be in the ledger's state data.
This would aid reading/exploring and processing ledger data as well as transaction metadata.
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
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.
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.
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.
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.
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?
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!
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 )
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 lambda
s.
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
pip3 install xrpl-py
For example if I run the following script with the websockets
package within a context manager managing the session, it closes quickly.
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__
xrpl-py/xrpl/asyncio/clients/async_websocket_client.py
Lines 58 to 65 in e5bbdf4
It led me to notice a few assert statements in the code that seemed pretty reasonable for conditional checks
xrpl-py/xrpl/asyncio/clients/websocket_base.py
Lines 84 to 90 in e5bbdf4
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
The current fee calculation in the autofill process hardcodes in the fee for AccountDelete
. This should be calculated from the ledger, for when the fee is changed.
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.
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:
searched_all
is true)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
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:
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:
xrpl.transaction.safe_sign_and_autofill_transaction
xrpl.transaction.safe_sign_transaction
Reasons I think this name is sufficient:
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
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:
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:
LastLedgerSequence
), fee options, path options, etc.Output:
LastLedgerSequence
field by default, as well as any other best practices, with options to explicitly configure/disable them.Sign Transaction (offline OK)
Input:
Output:
Submit-and-Verify (online)
It's not a bad thing to also have a simple submit function that returns the preliminary result only.
Input:
LastLedgerSequence
parameter.Output:
tesSUCCESS
.There are several ways in which the current version of xrpl-py DOES NOT work this way:
LastLedgerSequence
by default and filling Fee
and Sequence
.
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.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.
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.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.