Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changelog/brave-cats-split.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
pympp: minor
---

Added split payments support for Tempo charges, allowing a single charge to be split across multiple recipients. Port of [mpp-rs PR #180](https://github.com/tempoxyz/mpp-rs/pull/180).
5 changes: 5 additions & 0 deletions .changelog/shy-frogs-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
pympp: minor
---

Added split payments support for Tempo charges, allowing a single charge to be split across multiple recipients. Port of [mpp-rs PR #180](https://github.com/tempoxyz/mpp-rs/pull/180).
3 changes: 2 additions & 1 deletion src/mpp/methods/tempo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@
)
from mpp.methods.tempo.account import TempoAccount
from mpp.methods.tempo.client import TempoMethod, TransactionError, tempo
from mpp.methods.tempo.intents import ChargeIntent
from mpp.methods.tempo.intents import ChargeIntent, Transfer, get_transfers
from mpp.methods.tempo.schemas import Split
45 changes: 39 additions & 6 deletions src/mpp/methods/tempo/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ async def create_credential(self, challenge: Challenge) -> Credential:
if memo is None:
memo = encode_attribution(server_id=challenge.realm, client_id=self.client_id)

splits = method_details.get("splits") if isinstance(method_details, dict) else None

# Resolve RPC URL from challenge's chainId (like mppx), falling back
# to the method-level rpc_url.
rpc_url = self.rpc_url
Expand Down Expand Up @@ -152,6 +154,7 @@ async def create_credential(self, challenge: Challenge) -> Credential:
rpc_url=rpc_url,
expected_chain_id=expected_chain_id,
awaiting_fee_payer=use_fee_payer,
splits=splits,
)

# When signing with an access key, the credential source is the
Expand All @@ -174,6 +177,7 @@ async def _build_tempo_transfer(
rpc_url: str | None = None,
expected_chain_id: int | None = None,
awaiting_fee_payer: bool = False,
splits: list[dict] | None = None,
) -> tuple[str, int]:
"""Build a client-signed Tempo transaction.

Expand Down Expand Up @@ -208,10 +212,29 @@ async def _build_tempo_transfer(

resolved_rpc = rpc_url or self.rpc_url

if memo:
transfer_data = self._encode_transfer_with_memo(recipient, int(amount), memo)
gas_estimate_data: str | None = None

if splits:
from mpp.methods.tempo.intents import get_transfers
from mpp.methods.tempo.schemas import Split as SplitModel

parsed_splits = [SplitModel(**s) for s in splits]
transfer_list = get_transfers(int(amount), recipient, memo, parsed_splits)
call_list = []
for t in transfer_list:
if t.memo is not None:
td = self._encode_transfer_with_memo(t.recipient, t.amount, "0x" + t.memo.hex())
else:
td = self._encode_transfer(t.recipient, t.amount)
call_list.append(Call.create(to=currency, value=0, data=td))
calls_tuple = tuple(call_list)
else:
transfer_data = self._encode_transfer(recipient, int(amount))
if memo:
transfer_data = self._encode_transfer_with_memo(recipient, int(amount), memo)
else:
transfer_data = self._encode_transfer(recipient, int(amount))
calls_tuple = (Call.create(to=currency, value=0, data=transfer_data),)
gas_estimate_data = transfer_data

# When using an access key, fetch nonce from the root account
# (smart wallet), not the access key address.
Expand All @@ -236,8 +259,18 @@ async def _build_tempo_transfer(

gas_limit = DEFAULT_GAS_LIMIT
try:
estimated = await estimate_gas(resolved_rpc, nonce_address, currency, transfer_data)
gas_limit = max(gas_limit, estimated + 5_000)
if splits:
total_estimated = 0
for c in calls_tuple:
total_estimated += await estimate_gas(
resolved_rpc, nonce_address, currency, c.data.hex()
)
gas_limit = max(gas_limit, total_estimated + 5_000 * len(calls_tuple))
elif gas_estimate_data is not None:
estimated = await estimate_gas(
resolved_rpc, nonce_address, currency, gas_estimate_data
)
gas_limit = max(gas_limit, estimated + 5_000)
except Exception:
pass

Expand All @@ -251,7 +284,7 @@ async def _build_tempo_transfer(
fee_token=None if awaiting_fee_payer else currency,
awaiting_fee_payer=awaiting_fee_payer,
valid_before=valid_before,
calls=(Call.create(to=currency, value=0, data=transfer_data),),
calls=calls_tuple,
)

if self.root_account:
Expand Down
Loading
Loading