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
2 changes: 1 addition & 1 deletion tplus/client/clearingengine/settlement.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async def init_settlement(self, request: dict | TxSettlementRequest):
# Validate.
request = TxSettlementRequest.model_validate(request)

data = request.model_dump(mode="json")
data = request.model_dump(mode="json", exclude_none=True)
await self._post("settlement/init", json_data=data)

async def get_signatures(self, user: str) -> list[dict]:
Expand Down
5 changes: 4 additions & 1 deletion tplus/evm/contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,12 +520,15 @@ def deposit(
def execute_atomic_settlement(
self,
settlement: dict,
settler: HexBytes,
data: HexBytes,
signature: HexBytes,
**tx_kwargs,
) -> "ReceiptAPI":
try:
return self.contract.executeAtomicSettlement(settlement, data, signature, **tx_kwargs)
return self.contract.executeAtomicSettlement(
settlement, settler, data, signature, **tx_kwargs
)
except Exception as err:
err_id = getattr(err, "message", "")
if erc20_err_name := _decode_erc20_error(getattr(err, "message", f"{err}")):
Expand Down
37 changes: 24 additions & 13 deletions tplus/evm/managers/settle.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from tplus.evm.managers.evm import ChainConnectedManager
from tplus.logger import get_logger
from tplus.model.approval import SettlementApproval
from tplus.model.settlement import SettlementMode, TxSettlementRequest
from tplus.model.settlement import MakerOrderAttachment, SettlementMode, TxSettlementRequest
from tplus.model.types import ChainID, UserPublicKey
from tplus.utils.amount import Amount
from tplus.utils.user.decrypt import decrypt_settlement_approval
Expand Down Expand Up @@ -42,6 +42,7 @@ class SettlementInfo:
amount_out: Amount
nonce: int
chain_id: "ChainID"
settler: "UserPublicKey | None" = None


class SettlementManager(ChainConnectedManager):
Expand Down Expand Up @@ -163,6 +164,8 @@ async def init_settlement(
asset_out: "Address32",
amount_out: Amount,
user: "User | None" = None,
settler: "UserPublicKey | None" = None,
maker_order: MakerOrderAttachment | None = None,
account_index: int | None = None,
mode: SettlementMode = SettlementMode.MARGIN,
then_execute: bool = False,
Expand All @@ -181,6 +184,8 @@ async def init_settlement(
asset_out: The address of the asset leaving the protocol.
amount_out: Both the normalized and atomic amounts for the amount leaving the protocol.
user: Specify the tplus user. Defaults to the default tplus user.
settler: The settler account executing the settlement. Defaults to the user's public key.
maker_order: Optional maker order attachment for delegated settlements.
account_index: Specify the index of the tplus account for this settlement approval. Defaults to the
selected user's account index.
then_execute: Set to ``True`` to wait for the approval and then execute the settlement on-chain.
Expand Down Expand Up @@ -208,18 +213,21 @@ async def init_settlement(
if account_index is None:
account_index = user.sub_account

request = TxSettlementRequest.create_signed(
{
"chain_id": self.chain_id,
"mode": mode,
"asset_in": asset_in,
"amount_in": amount_in_normalized,
"asset_out": asset_out,
"amount_out": amount_out_normalized,
"sub_account_index": account_index,
},
user,
)
request_data = {
"chain_id": self.chain_id,
"mode": mode,
"asset_in": asset_in,
"amount_in": amount_in_normalized,
"asset_out": asset_out,
"amount_out": amount_out_normalized,
"sub_account_index": account_index,
}
if settler is not None:
request_data["settler"] = settler

request = TxSettlementRequest.create_signed(request_data, user)
if maker_order is not None:
request.maker_order = maker_order

settlement_info = SettlementInfo(
asset_in=asset_in,
Expand All @@ -228,6 +236,7 @@ async def init_settlement(
amount_out=amount_out,
nonce=expected_nonce,
chain_id=self.chain_id,
settler=settler or user.public_key,
)

approval_task: asyncio.Task | None = None
Expand Down Expand Up @@ -313,6 +322,7 @@ async def execute_settlement(
nonce = approval.inner.nonce
expiry = approval.expiry
user = user or self.default_user
settler = settlement_info.settler or user.public_key
token_in_address = kwargs.pop("token_in", None)
token_out_address = kwargs.pop("token_out", None)

Expand Down Expand Up @@ -343,6 +353,7 @@ async def execute_settlement(
"nonce": nonce,
"validUntil": expiry,
},
HexBytes(settler),
"",
HexBytes(approval.inner.signature),
**kwargs,
Expand Down
2 changes: 1 addition & 1 deletion tplus/evm/manifests/tplus-contracts.json

Large diffs are not rendered by default.

46 changes: 39 additions & 7 deletions tplus/model/settlement.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class InnerSettlementRequest(BaseSettlement):

tplus_user: UserPublicKey
sub_account_index: int
settler: UserPublicKey
settler: UserPublicKey | None = None
chain_id: ChainID

@classmethod
Expand Down Expand Up @@ -101,17 +101,19 @@ def signing_payload(self) -> str:
base_data = self.model_dump(mode="json", exclude_none=True)

user = base_data.pop("tplus_user")
settler = base_data.pop("settler")
settler = base_data.pop("settler", None)
chain_id = base_data.pop("chain_id", None)

# NOTE: The order here matters!
payload = {
"tplus_user": user,
"sub_account_index": base_data.pop("sub_account_index"),
"settler": settler,
**base_data,
"chain_id": chain_id,
}
if settler is not None:
payload["settler"] = settler

payload.update(base_data)
payload["chain_id"] = chain_id

return (
json.dumps(payload, separators=(",", ":"))
Expand All @@ -121,6 +123,33 @@ def signing_payload(self) -> str:
)


class InnerMakerOrderAttachment(BaseModel):
"""
The signed inner part of a maker order attachment for delegated settlement.
"""

mm_pubkey: UserPublicKey
"""The market maker's public key."""

settler: UserPublicKey
"""The settler/executor designated by the MM."""

expires_at: int
"""Expiry timestamp in nanoseconds."""


class MakerOrderAttachment(BaseModel):
"""
A maker order attached to a delegated settlement request.
"""

inner: InnerMakerOrderAttachment
"""The signed inner part."""

signature: list[int]
"""MM's signature over ``inner``."""


class TxSettlementRequest(BaseModel):
"""
Atomic settlement request.
Expand All @@ -136,6 +165,11 @@ class TxSettlementRequest(BaseModel):
The settler's signature from signing the necessary data (mostly from ``.inner``).
"""

maker_order: MakerOrderAttachment | None = None
"""
Optional maker order for delegated settlement.
"""

@classmethod
def create_signed(
cls,
Expand All @@ -155,8 +189,6 @@ def create_signed(
if isinstance(inner, dict):
if "tplus_user" not in inner:
inner["tplus_user"] = signer.public_key
if "settler" not in inner:
inner["settler"] = signer.public_key

inner = InnerSettlementRequest.model_validate(inner)

Expand Down
Loading