Skip to content

Commit ad0d353

Browse files
committed
add script & multisig paths, fmt & constants
1 parent c5b182d commit ad0d353

File tree

3 files changed

+320
-85
lines changed

3 files changed

+320
-85
lines changed

aptos_sdk/async_client.py

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import asyncio
55
import logging
6+
import secrets
67
import time
78
from dataclasses import dataclass
89
from typing import Any, Dict, List, Optional
@@ -18,11 +19,12 @@
1819
from .transactions import (
1920
EntryFunction,
2021
MultiAgentRawTransaction,
22+
OrderlessPayload,
2123
RawTransaction,
24+
Script,
2225
SignedTransaction,
2326
TransactionArgument,
2427
TransactionPayload,
25-
OrderlessPayload,
2628
)
2729
from .type_tag import StructTag, TypeTag
2830

@@ -530,29 +532,40 @@ async def submit_and_wait_for_bcs_transaction(
530532
txn_hash = await self.submit_bcs_transaction(signed_transaction)
531533
await self.wait_for_transaction(txn_hash)
532534
return await self.transaction_by_hash(txn_hash)
533-
535+
534536
async def submit_orderless_transaction(
535-
self,
536-
sender: Account,
537-
payload: TransactionPayload,
538-
nonce: Optional[int] = None,
539-
wait: bool = False
540-
) -> str:
541-
537+
self,
538+
sender: Account,
539+
payload: TransactionPayload,
540+
nonce: Optional[int] = None,
541+
multisig_address: Optional[AccountAddress] = None,
542+
wait: bool = False,
543+
) -> str:
544+
542545
if nonce is None:
543-
raise ValueError("Nonce required for orderless")
544-
545-
if not isinstance(payload.value, EntryFunction):
546-
raise ValueError("Only EntryFunction supported for orderless")
547-
546+
nonce = secrets.randbits(64)
547+
548+
# Extract executable from payload (can be None for multisig voting)
549+
executable = payload.value if payload.value else None
550+
551+
if executable is not None and not isinstance(
552+
executable, (EntryFunction, Script)
553+
):
554+
raise ValueError(
555+
"Orderless transactions only support EntryFunction and Script payloads"
556+
)
557+
558+
# Create orderless payload
559+
orderless = OrderlessPayload(executable, nonce, multisig_address)
560+
548561
orderless = OrderlessPayload(payload.value, nonce)
549-
562+
550563
chain_id = await self.chain_id()
551564

552-
# Orderless transactions typically have shorter expiration windows (e.g., 30 seconds)
565+
# Orderless transactions typically have shorter expiration windows (60 seconds)
553566
# Use a much shorter TTL than regular transactions
554-
orderless_expiration_ttl = 30 # 30 seconds
555-
567+
orderless_expiration_ttl = 60
568+
556569
raw_txn = RawTransaction(
557570
sender=sender.address(),
558571
sequence_number=0xDEADBEEF,
@@ -562,15 +575,15 @@ async def submit_orderless_transaction(
562575
expiration_timestamps_secs=int(time.time()) + orderless_expiration_ttl,
563576
chain_id=chain_id,
564577
)
565-
578+
566579
authenticator = sender.sign_transaction(raw_txn)
567580
signed_txn = SignedTransaction(raw_txn, authenticator)
568-
581+
569582
tx_hash = await self.submit_bcs_transaction(signed_txn)
570-
583+
571584
if wait:
572585
await self.wait_for_transaction(tx_hash)
573-
586+
574587
return tx_hash
575588

576589
async def transaction_pending(self, txn_hash: str) -> bool:

aptos_sdk/transactions.py

Lines changed: 76 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ class TransactionPayload:
253253
SCRIPT: int = 0
254254
MODULE_BUNDLE: int = 1
255255
SCRIPT_FUNCTION: int = 2
256-
INNER_PAYLOAD: int = 4
256+
INNER_PAYLOAD: int = 4
257257

258258
variant: int
259259
value: Any
@@ -265,7 +265,7 @@ def __init__(self, payload: Any):
265265
self.variant = TransactionPayload.MODULE_BUNDLE
266266
elif isinstance(payload, EntryFunction):
267267
self.variant = TransactionPayload.SCRIPT_FUNCTION
268-
elif isinstance(payload, OrderlessPayload):
268+
elif isinstance(payload, OrderlessPayload):
269269
self.variant = TransactionPayload.INNER_PAYLOAD
270270
else:
271271
raise Exception("Invalid type")
@@ -298,6 +298,7 @@ def serialize(self, serializer: Serializer) -> None:
298298
serializer.uleb128(self.variant)
299299
self.value.serialize(serializer)
300300

301+
301302
class ModuleBundle:
302303
def __init__(self):
303304
raise NotImplementedError
@@ -479,45 +480,95 @@ def serialize(self, serializer: Serializer) -> None:
479480
serializer.sequence(self.ty_args, Serializer.struct)
480481
serializer.sequence(self.args, Serializer.to_bytes)
481482

483+
482484
class OrderlessPayload:
483485
"""Orderless transaction payload wrapper"""
484-
485-
def __init__(self, entry_function: EntryFunction, nonce: int):
486-
self.entry_function = entry_function
486+
487+
# OrderlessTransactionPayload variants
488+
ORDERLESS_V1: int = 0
489+
490+
# Executable variants
491+
EXECUTABLE_SCRIPT: int = 0
492+
EXECUTABLE_ENTRY_FUNCTION: int = 1
493+
EXECUTABLE_EMPTY: int = 2 # For multisig voting without execution
494+
495+
# ExtraConfig variants
496+
EXTRA_CONFIG_V1: int = 0
497+
498+
def __init__(
499+
self,
500+
executable: Optional[Union[Script, EntryFunction]],
501+
nonce: int,
502+
multisig_address: Optional[AccountAddress] = None,
503+
):
504+
"""
505+
Create an orderless transaction payload.
506+
507+
:param executable: Script or EntryFunction to execute. None for multisig voting.
508+
:param nonce: Unique nonce for replay protection
509+
:param multisig_address: Address of multisig account (required for multisig transactions)
510+
"""
511+
if executable is not None and not isinstance(
512+
executable, (Script, EntryFunction)
513+
):
514+
raise ValueError("Executable must be either Script or EntryFunction")
515+
516+
# If no executable, must be multisig voting
517+
if executable is None and multisig_address is None:
518+
raise ValueError("Either executable or multisig_address must be provided")
519+
520+
self.executable = executable
487521
self.nonce = nonce
488-
522+
self.multisig_address = multisig_address
523+
489524
def serialize(self, serializer: Serializer):
490525
"""Serialize orderless payload WITHOUT the outer variant (TransactionPayload handles that)"""
491-
526+
492527
# OrderlessTransactionPayload::V1 variant
493-
serializer.uleb128(0)
494-
495-
# Executable::EntryFunction variant
496-
serializer.uleb128(1)
497-
498-
# Serialize the entry function
499-
self.entry_function.serialize(serializer)
500-
528+
serializer.uleb128(OrderlessPayload.ORDERLESS_V1)
529+
530+
# Executable variant
531+
if self.executable is None:
532+
# Empty executable for multisig voting
533+
serializer.uleb128(OrderlessPayload.EXECUTABLE_EMPTY)
534+
elif isinstance(self.executable, Script):
535+
serializer.uleb128(OrderlessPayload.EXECUTABLE_SCRIPT)
536+
self.executable.serialize(serializer)
537+
elif isinstance(self.executable, EntryFunction):
538+
serializer.uleb128(OrderlessPayload.EXECUTABLE_ENTRY_FUNCTION)
539+
self.executable.serialize(serializer)
540+
else:
541+
raise ValueError("Invalid executable type")
542+
501543
# ExtraConfig::V1 variant
502-
serializer.uleb128(0)
503-
504-
# Option<MultisigAddress> - None
505-
serializer.bool(False)
506-
544+
serializer.uleb128(OrderlessPayload.EXTRA_CONFIG_V1)
545+
546+
# Option<MultisigAddress>
547+
if self.multisig_address is not None:
548+
serializer.bool(True) # Some
549+
self.multisig_address.serialize(serializer)
550+
else:
551+
serializer.bool(False) # None
552+
507553
# Option<u64> nonce - Some
508554
serializer.bool(True)
509555
serializer.u64(self.nonce)
510-
556+
511557
def __eq__(self, other: object) -> bool:
512558
if not isinstance(other, OrderlessPayload):
513559
return NotImplemented
514560
return (
515-
self.entry_function == other.entry_function
561+
self.executable == other.executable
516562
and self.nonce == other.nonce
563+
and self.multisig_address == other.multisig_address
517564
)
518-
565+
519566
def __str__(self):
520-
return f"OrderlessPayload(nonce={self.nonce}, {self.entry_function})"
567+
multisig_str = (
568+
f", multisig={self.multisig_address}" if self.multisig_address else ""
569+
)
570+
return f"OrderlessPayload(nonce={self.nonce}, {self.executable}{multisig_str})"
571+
521572

522573
class ModuleId:
523574
address: AccountAddress
@@ -939,4 +990,4 @@ def test_deserialize_raw_transaction_multi_agent(self):
939990
raw_txn_with_data.serialize(ser)
940991
self.assertEqual(ser.output().hex(), input_ma)
941992

942-
self.assertTrue(isinstance(raw_txn_with_data, MultiAgentRawTransaction))
993+
self.assertTrue(isinstance(raw_txn_with_data, MultiAgentRawTransaction))

0 commit comments

Comments
 (0)