Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Construction of Wallet throws an "Invalid Seed" error, if the secret is not decode-able.
- Rectify the incorrect usage of a transaction flag name: Update `TF_NO_DIRECT_RIPPLE` to `TF_NO_RIPPLE_DIRECT`

### Added
- Add a utility function `generate_escrow_cryptoconditions` to generate cryptographic condition and fulfillment for conditional escrows

## [2.5.0] - 2023-11-30

### Added
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ websockets = "^11.0"
Deprecated = "^1.2.13"
types-Deprecated = "^1.2.9"
pycryptodome = "^3.16.0"
cryptoconditions = "0.8.1"

[tool.poetry.dev-dependencies]
flake8 = "^3.8.4"
Expand Down
40 changes: 40 additions & 0 deletions snippets/send_escrow.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Example of how we can set up an escrow"""
from datetime import datetime
from os import urandom
from time import sleep

from xrpl.account import get_balance
from xrpl.clients import JsonRpcClient
from xrpl.models import AccountObjects, EscrowCreate, EscrowFinish
from xrpl.models.transactions.escrow_create import generate_escrow_cryptoconditions
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Import is correctly structured, but function needs to be exposed

Based on the verification:

  • The generate_escrow_cryptoconditions function is defined in xrpl/models/transactions/escrow_create.py
  • The function is newly added according to the CHANGELOG.md
  • The function is not currently exposed in the package's public API through xrpl/models/transactions/__init__.py

To make this function properly accessible for external usage, it should be:

  1. Imported in xrpl/models/transactions/__init__.py
  2. Added to the __all__ list in the same file
🔗 Analysis chain

Verify import accessibility.
Ensure generate_escrow_cryptoconditions is properly exposed in the package’s __init__.py if needed for external usage.


Let me gather more information about the package structure and imports.


Let me check one more thing to verify if this is a newly added function.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if generate_escrow_cryptoconditions is imported in __init__.py or directly in other modules
rg --heading --context 5 "generate_escrow_cryptoconditions"

Length of output: 2311


Script:

#!/bin/bash
# Check if the function is exposed in __init__.py files
fd "__init__.py" | xargs rg "generate_escrow_cryptoconditions"

# Check the module structure
fd . --type f --extension py | grep -v "test" | grep "xrpl"

Length of output: 8835


Script:

#!/bin/bash
# Check the content of the models/transactions/__init__.py to see if the function is exposed
cat xrpl/models/transactions/__init__.py

Length of output: 6651

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Developers can use the new function as from xrpl.models.transactions.escrow_create import generate_escrow_cryptoconditions.

Is it really necessary to expose this function through the __init__.py file? Would that have any costs in terms of the size of the xrpl-py python package?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ckeshava, exposing functions in __init__.py does not significantly increase package size unless you're re-exporting many large modules. Typically, it’s more about how you want users to discover and import functionality. If you feel comfortable directing developers to escrow_create, you can certainly leave it out of __init__.py. However, adding a single function there will have a negligible impact on package size; it mainly offers discoverability and an implicit statement that this function is part of the “official” public API.

from xrpl.transaction.reliable_submission import submit_and_wait
from xrpl.utils import datetime_to_ripple_time
from xrpl.wallet import generate_faucet_wallet
Expand Down Expand Up @@ -64,3 +66,41 @@
print("Balances of wallets after Escrow was sent:")
print(get_balance(wallet1.address, client))
print(get_balance(wallet2.address, client))

# Setup conditional escrows
cryptoCondition = generate_escrow_cryptoconditions(urandom(32))

create_tx = EscrowCreate(
account=wallet1.address,
destination=wallet2.address,
amount="1000000",
condition=cryptoCondition["condition"],
cancel_after=datetime_to_ripple_time(datetime.now()) + 100,
)

create_escrow_response = submit_and_wait(create_tx, client, wallet1)
print(create_escrow_response)

# Create an AccountObjects request and have the client call it to see if escrow exists
account_objects_request = AccountObjects(account=wallet1.address)
account_objects = (client.request(account_objects_request)).result["account_objects"]

print("Conditional Escrow object exists in wallet1's account:")
print(account_objects)

# Create an EscrowFinish transaction, then sign, autofill, and send it
finish_tx = EscrowFinish(
account=wallet1.address,
owner=wallet1.address,
offer_sequence=create_escrow_response.result["Sequence"],
fulfillment=cryptoCondition["fulfillment"],
condition=cryptoCondition["condition"],
)

submit_and_wait(finish_tx, client, wallet1)

# The fees for EscrowFinish transaction of a conditional escrows are higher.
# Additionally, the fees scale with the reference load on the server
print("Balances of wallets after Escrow was sent:")
print(get_balance(wallet1.address, client))
print(get_balance(wallet2.address, client))
48 changes: 47 additions & 1 deletion tests/unit/models/transactions/test_escrow_create.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from os import urandom
from unittest import TestCase

from xrpl.models.exceptions import XRPLModelException
from xrpl.models.transactions import EscrowCreate
from xrpl.models.transactions import EscrowCreate, EscrowFinish
from xrpl.models.transactions.escrow_create import generate_escrow_cryptoconditions

_OFFER_SEQUENCE = 1
_OWNER = "rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN"


class TestEscrowCreate(TestCase):
Expand All @@ -24,3 +29,44 @@ def test_final_after_less_than_cancel_after(self):
finish_after=finish_after,
sequence=sequence,
)

def test_escrow_condition_and_fulfillment(self):
account = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ"
amount = "100"
destination = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ"
fee = "0.00001"
sequence = 19048

# use os.urandom as the source of cryptographic randomness
condition, fulfillment = generate_escrow_cryptoconditions(urandom(32))

EscrowCreate(
account=account,
amount=amount,
destination=destination,
fee=fee,
sequence=sequence,
condition=condition,
)

# EscrowFinish without the fullfillment must throw an error
with self.assertRaises(XRPLModelException):
EscrowFinish(
account=account,
condition=condition,
fee=fee,
sequence=sequence,
offer_sequence=_OFFER_SEQUENCE,
owner=_OWNER,
)

# execute Escrow finish with the fulfillment
EscrowFinish(
account=account,
condition=condition,
fee=fee,
sequence=sequence,
fulfillment=fulfillment,
offer_sequence=_OFFER_SEQUENCE,
owner=_OWNER,
)
33 changes: 32 additions & 1 deletion xrpl/models/transactions/escrow_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
from __future__ import annotations # Requires Python 3.7+

from dataclasses import dataclass, field
from typing import Dict, Optional
from typing import Dict, Optional, TypedDict

# CK: TODO Find a py.typed or library stub for cryptoconditions
from cryptoconditions import PreimageSha256 # type: ignore

from xrpl.models.amounts import Amount
from xrpl.models.required import REQUIRED
Expand Down Expand Up @@ -81,3 +84,31 @@ def _get_errors(self: EscrowCreate) -> Dict[str, str]:
] = "The finish_after time must be before the cancel_after time."

return errors


class CryptoConditions(TypedDict):
"""
A typed-dictionary containing the condition and the fulfillment for
conditional Escrows
"""

condition: str
fulfillment: str


def generate_escrow_cryptoconditions(secret: bytes) -> CryptoConditions:
"""Generate a condition and fulfillment for escrows

Args:
secret: Cryptographic source of randomness used to generate the condition and
fulfillment

Returns:
A pair of condition and fulfillment is returned

"""
fufill = PreimageSha256(preimage=secret)
return {
"condition": str.upper(fufill.condition_binary.hex()),
"fulfillment": str.upper(fufill.serialize_binary().hex()),
}