mppi-protocol 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,185 @@
1
+ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2
+ """Client and server classes corresponding to protobuf-defined services."""
3
+ import grpc
4
+ import warnings
5
+
6
+ from mppi.v1 import mppi_pb2 as mppi_dot_v1_dot_mppi__pb2
7
+
8
+ GRPC_GENERATED_VERSION = '1.84.0'
9
+ GRPC_VERSION = grpc.__version__
10
+ _version_not_supported = False
11
+
12
+ try:
13
+ from grpc._utilities import first_version_is_lower
14
+ _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
15
+ except ImportError:
16
+ _version_not_supported = True
17
+
18
+ if _version_not_supported:
19
+ raise RuntimeError(
20
+ f'The grpc package installed is at version {GRPC_VERSION},'
21
+ + ' but the generated code in mppi/v1/mppi_pb2_grpc.py depends on'
22
+ + f' grpcio>={GRPC_GENERATED_VERSION}.'
23
+ + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
24
+ + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
25
+ )
26
+
27
+
28
+ class ChatStub:
29
+ """Binary protobuf is the v1 transport. Semantic requirements, including scalar
30
+ presence, widths, directions and accounting rules, are in docs/protocol.md.
31
+ A successfully parsed message is not proof of payment authority.
32
+
33
+ """
34
+
35
+ def __init__(self, channel):
36
+ """Constructor.
37
+
38
+ Args:
39
+ channel: A grpc.Channel.
40
+ """
41
+ self.Completions = channel.unary_stream(
42
+ '/mppi.v1.Chat/Completions',
43
+ request_serializer=mppi_dot_v1_dot_mppi__pb2.CompleteRequest.SerializeToString,
44
+ response_deserializer=mppi_dot_v1_dot_mppi__pb2.ChatChunk.FromString,
45
+ _registered_method=True)
46
+
47
+
48
+ class ChatServicer:
49
+ """Binary protobuf is the v1 transport. Semantic requirements, including scalar
50
+ presence, widths, directions and accounting rules, are in docs/protocol.md.
51
+ A successfully parsed message is not proof of payment authority.
52
+
53
+ """
54
+
55
+ def Completions(self, request, context):
56
+ """One request, provider JSON payloads, then one definitive final record when
57
+ available. Cancellation and transport failure may prevent final delivery.
58
+ """
59
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
60
+ context.set_details('Method not implemented!')
61
+ raise NotImplementedError('Method not implemented!')
62
+
63
+
64
+ def add_ChatServicer_to_server(servicer, server):
65
+ rpc_method_handlers = {
66
+ 'Completions': grpc.unary_stream_rpc_method_handler(
67
+ servicer.Completions,
68
+ request_deserializer=mppi_dot_v1_dot_mppi__pb2.CompleteRequest.FromString,
69
+ response_serializer=mppi_dot_v1_dot_mppi__pb2.ChatChunk.SerializeToString,
70
+ ),
71
+ }
72
+ generic_handler = grpc.method_handlers_generic_handler(
73
+ 'mppi.v1.Chat', rpc_method_handlers)
74
+ server.add_generic_rpc_handlers((generic_handler,))
75
+ server.add_registered_method_handlers('mppi.v1.Chat', rpc_method_handlers)
76
+
77
+
78
+ # This class is part of an EXPERIMENTAL API.
79
+ class Chat:
80
+ """Binary protobuf is the v1 transport. Semantic requirements, including scalar
81
+ presence, widths, directions and accounting rules, are in docs/protocol.md.
82
+ A successfully parsed message is not proof of payment authority.
83
+
84
+ """
85
+
86
+ @staticmethod
87
+ def Completions(request,
88
+ target,
89
+ options=(),
90
+ channel_credentials=None,
91
+ call_credentials=None,
92
+ insecure=False,
93
+ compression=None,
94
+ wait_for_ready=None,
95
+ timeout=None,
96
+ metadata=None):
97
+ return grpc.experimental.unary_stream(
98
+ request,
99
+ target,
100
+ '/mppi.v1.Chat/Completions',
101
+ mppi_dot_v1_dot_mppi__pb2.CompleteRequest.SerializeToString,
102
+ mppi_dot_v1_dot_mppi__pb2.ChatChunk.FromString,
103
+ options,
104
+ channel_credentials,
105
+ insecure,
106
+ call_credentials,
107
+ compression,
108
+ wait_for_ready,
109
+ timeout,
110
+ metadata,
111
+ _registered_method=True)
112
+
113
+
114
+ class ControlStub:
115
+ """Missing associated documentation comment in .proto file."""
116
+
117
+ def __init__(self, channel):
118
+ """Constructor.
119
+
120
+ Args:
121
+ channel: A grpc.Channel.
122
+ """
123
+ self.Session = channel.stream_stream(
124
+ '/mppi.v1.Control/Session',
125
+ request_serializer=mppi_dot_v1_dot_mppi__pb2.ControlMessage.SerializeToString,
126
+ response_deserializer=mppi_dot_v1_dot_mppi__pb2.ControlMessage.FromString,
127
+ _registered_method=True)
128
+
129
+
130
+ class ControlServicer:
131
+ """Missing associated documentation comment in .proto file."""
132
+
133
+ def Session(self, request_iterator, context):
134
+ """The actual authenticated stream owns one session attachment. No prompts or
135
+ inference output travel on this stream. Vouchers have no per-message ACK.
136
+ """
137
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
138
+ context.set_details('Method not implemented!')
139
+ raise NotImplementedError('Method not implemented!')
140
+
141
+
142
+ def add_ControlServicer_to_server(servicer, server):
143
+ rpc_method_handlers = {
144
+ 'Session': grpc.stream_stream_rpc_method_handler(
145
+ servicer.Session,
146
+ request_deserializer=mppi_dot_v1_dot_mppi__pb2.ControlMessage.FromString,
147
+ response_serializer=mppi_dot_v1_dot_mppi__pb2.ControlMessage.SerializeToString,
148
+ ),
149
+ }
150
+ generic_handler = grpc.method_handlers_generic_handler(
151
+ 'mppi.v1.Control', rpc_method_handlers)
152
+ server.add_generic_rpc_handlers((generic_handler,))
153
+ server.add_registered_method_handlers('mppi.v1.Control', rpc_method_handlers)
154
+
155
+
156
+ # This class is part of an EXPERIMENTAL API.
157
+ class Control:
158
+ """Missing associated documentation comment in .proto file."""
159
+
160
+ @staticmethod
161
+ def Session(request_iterator,
162
+ target,
163
+ options=(),
164
+ channel_credentials=None,
165
+ call_credentials=None,
166
+ insecure=False,
167
+ compression=None,
168
+ wait_for_ready=None,
169
+ timeout=None,
170
+ metadata=None):
171
+ return grpc.experimental.stream_stream(
172
+ request_iterator,
173
+ target,
174
+ '/mppi.v1.Control/Session',
175
+ mppi_dot_v1_dot_mppi__pb2.ControlMessage.SerializeToString,
176
+ mppi_dot_v1_dot_mppi__pb2.ControlMessage.FromString,
177
+ options,
178
+ channel_credentials,
179
+ insecure,
180
+ call_credentials,
181
+ compression,
182
+ wait_for_ready,
183
+ timeout,
184
+ metadata,
185
+ _registered_method=True)
@@ -0,0 +1,45 @@
1
+ """Shared MPPI primitives; runtime boundaries live in dedicated typed modules."""
2
+
3
+ from .accounting import AccountingTransition, RunAccounting, RunStatus, report_transition
4
+ from .amounts import (
5
+ MAX_UINT64,
6
+ RATE_SCALE,
7
+ Rates,
8
+ TokenCounts,
9
+ cumulative_usage,
10
+ decimal_uint64,
11
+ funding_amount,
12
+ priced_numerator,
13
+ remaining_funding_tokens,
14
+ required_coverage,
15
+ top_up_total,
16
+ uint64,
17
+ )
18
+ from .catalog import Catalog, CatalogRow, parse_catalog, publish_catalog, validate_model
19
+ from .errors import ValidationCode, ValidationError
20
+
21
+ __all__ = [
22
+ "MAX_UINT64",
23
+ "RATE_SCALE",
24
+ "AccountingTransition",
25
+ "Catalog",
26
+ "CatalogRow",
27
+ "Rates",
28
+ "RunAccounting",
29
+ "RunStatus",
30
+ "TokenCounts",
31
+ "ValidationCode",
32
+ "ValidationError",
33
+ "cumulative_usage",
34
+ "decimal_uint64",
35
+ "funding_amount",
36
+ "parse_catalog",
37
+ "priced_numerator",
38
+ "publish_catalog",
39
+ "remaining_funding_tokens",
40
+ "report_transition",
41
+ "required_coverage",
42
+ "top_up_total",
43
+ "uint64",
44
+ "validate_model",
45
+ ]
@@ -0,0 +1,121 @@
1
+ """Bounded, best-effort lifecycle observers; never a payment control path."""
2
+
3
+ from collections import deque
4
+ from collections.abc import Callable
5
+ from copy import deepcopy
6
+ from dataclasses import dataclass, fields, is_dataclass
7
+ from inspect import iscoroutinefunction
8
+ from threading import Condition, Thread
9
+ from typing import Any
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class ObserverStats:
14
+ delivered: int
15
+ dropped: int
16
+ failed: int
17
+ queued: int
18
+
19
+
20
+ @dataclass(frozen=True, slots=True)
21
+ class HookError:
22
+ """Sanitized SDK boundary error; no prompt, credentials or upstream body."""
23
+
24
+ operation: str
25
+ code: str
26
+ session_id: bytes | None = None
27
+ run_id: bytes | None = None
28
+
29
+
30
+ class Observers:
31
+ """One worker, at most 64 queued records/1 MiB, plus one running callback.
32
+
33
+ Records are copied before enqueue. Full queues drop the newest record.
34
+ close discards pending records and never waits for caller code. Python
35
+ cannot terminate a blocked callback: it may retain this one daemon thread.
36
+ """
37
+
38
+ def __init__(self, hooks: Any) -> None:
39
+ self._callbacks: dict[str, Callable[[Any], None]] = {}
40
+ if hooks is not None:
41
+ for name in hooks.__dataclass_fields__:
42
+ value = getattr(hooks, name)
43
+ if value is not None:
44
+ if (
45
+ not callable(value)
46
+ or iscoroutinefunction(value)
47
+ or iscoroutinefunction(type(value).__call__)
48
+ ):
49
+ raise ValueError(f"{name} must be a synchronous callable")
50
+ self._callbacks[name] = value
51
+ self._condition = Condition()
52
+ self._queue: deque[tuple[str, Any, int]] = deque()
53
+ self._bytes = self._delivered = self._dropped = self._failed = 0
54
+ self._started = self._closed = False
55
+
56
+ def emit(self, name: str, value: Any) -> None:
57
+ if name not in self._callbacks:
58
+ return
59
+ # Protobuf records obey the wire limit. Other records are bounded SDK
60
+ # value objects. Do not serialize arbitrary application objects here.
61
+ size = _size(value)
62
+ with self._condition:
63
+ if self._closed or len(self._queue) >= 64 or self._bytes + size > 1048576:
64
+ self._dropped += 1
65
+ return
66
+ if not self._started:
67
+ try:
68
+ Thread(target=self._run, name="mppi-observers", daemon=True).start()
69
+ except RuntimeError:
70
+ self._failed += 1
71
+ return
72
+ self._started = True
73
+ self._queue.append((name, deepcopy(value), size))
74
+ self._bytes += size
75
+ self._condition.notify()
76
+
77
+ def _run(self) -> None:
78
+ while True:
79
+ with self._condition:
80
+ while not self._queue and not self._closed:
81
+ self._condition.wait()
82
+ if self._closed:
83
+ return
84
+ name, value, size = self._queue.popleft()
85
+ self._bytes -= size
86
+ try:
87
+ self._callbacks[name](value)
88
+ except BaseException:
89
+ # Caller callback failures, including SystemExit, cannot affect
90
+ # the SDK event loop, payment state or other queued callbacks.
91
+ with self._condition:
92
+ self._failed += 1
93
+ else:
94
+ with self._condition:
95
+ self._delivered += 1
96
+
97
+ def stats(self) -> ObserverStats:
98
+ with self._condition:
99
+ return ObserverStats(self._delivered, self._dropped, self._failed, len(self._queue))
100
+
101
+ def close(self) -> None:
102
+ with self._condition:
103
+ self._closed = True
104
+ self._dropped += len(self._queue)
105
+ self._queue.clear()
106
+ self._bytes = 0
107
+ self._condition.notify_all()
108
+
109
+
110
+ def _size(value: Any) -> int:
111
+ if hasattr(value, "ByteSize"):
112
+ return int(value.ByteSize())
113
+ if isinstance(value, bytes):
114
+ return len(value)
115
+ if isinstance(value, str):
116
+ return len(value) * 4
117
+ if isinstance(value, tuple):
118
+ return sum(_size(item) for item in value)
119
+ if is_dataclass(value) and not isinstance(value, type):
120
+ return sum(_size(getattr(value, field.name)) for field in fields(value))
121
+ return 32
@@ -0,0 +1,95 @@
1
+ """Pure provider-report validation; callers supply and own their payment state."""
2
+
3
+ from dataclasses import dataclass
4
+ from enum import StrEnum
5
+
6
+ from .amounts import Rates, TokenCounts, numerator, priced_numerator
7
+ from .errors import ValidationCode, ValidationError
8
+
9
+
10
+ class RunStatus(StrEnum):
11
+ COMPLETED = "COMPLETED"
12
+ FAILED = "FAILED"
13
+ CANCELLED = "CANCELLED"
14
+ NOT_ADMITTED = "NOT_ADMITTED"
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class RunAccounting:
19
+ counts: TokenCounts
20
+ remaining_numerator: int
21
+ final_status: RunStatus | None = None
22
+
23
+ def __post_init__(self) -> None:
24
+ if not isinstance(self.counts, TokenCounts):
25
+ raise ValidationError(ValidationCode.INVALID_VALUE, "counts must be TokenCounts")
26
+ numerator(self.remaining_numerator, field="remaining reservation")
27
+ if self.final_status is not None:
28
+ if (
29
+ not isinstance(self.final_status, RunStatus)
30
+ or self.final_status is RunStatus.NOT_ADMITTED
31
+ ):
32
+ raise ValidationError(
33
+ ValidationCode.INVALID_VALUE, "admitted run requires a definitive final status"
34
+ )
35
+ if self.remaining_numerator != 0:
36
+ raise ValidationError(
37
+ ValidationCode.INVALID_VALUE, "final accounting has no remaining reservation"
38
+ )
39
+
40
+
41
+ @dataclass(frozen=True, slots=True)
42
+ class AccountingTransition:
43
+ state: RunAccounting
44
+ delta_numerator: int
45
+ released_numerator: int
46
+ duplicate: bool
47
+
48
+
49
+ def report_transition(
50
+ previous: RunAccounting,
51
+ counts: TokenCounts,
52
+ rates: Rates,
53
+ *,
54
+ final_status: RunStatus | None = None,
55
+ ) -> AccountingTransition:
56
+ """Validate cumulative usage, conserve coverage and release only final slack.
57
+
58
+ This returns a calculation, not an admission grant, a commit operation,
59
+ proof of termination, or a new meter sequence. Runtime integration applies
60
+ it to a consistent authoritative session view supplied by the provider.
61
+ """
62
+ if final_status is not None and (
63
+ not isinstance(final_status, RunStatus) or final_status is RunStatus.NOT_ADMITTED
64
+ ):
65
+ raise ValidationError(ValidationCode.INVALID_VALUE, "invalid admitted-run final status")
66
+ old_numerator = priced_numerator(previous.counts, rates)
67
+ new_numerator = priced_numerator(counts, rates)
68
+ if previous.final_status is not None:
69
+ if counts == previous.counts and final_status == previous.final_status:
70
+ return AccountingTransition(previous, 0, 0, True)
71
+ raise ValidationError(
72
+ ValidationCode.INCONSISTENT_USAGE, "report conflicts with definitive final accounting"
73
+ )
74
+ old_counts = previous.counts
75
+ if (
76
+ counts.input_tokens < old_counts.input_tokens
77
+ or counts.cache_tokens < old_counts.cache_tokens
78
+ or counts.output_tokens < old_counts.output_tokens
79
+ or new_numerator < old_numerator
80
+ ):
81
+ raise ValidationError(
82
+ ValidationCode.INCONSISTENT_USAGE,
83
+ "cumulative counts or exact recognized cost regressed",
84
+ )
85
+ delta = new_numerator - old_numerator
86
+ if delta > previous.remaining_numerator:
87
+ raise ValidationError(
88
+ ValidationCode.UNCOVERED_USAGE, "reported usage exceeds remaining funded reservation"
89
+ )
90
+ remaining = previous.remaining_numerator - delta
91
+ released = remaining if final_status is not None else 0
92
+ result = RunAccounting(counts, 0 if final_status is not None else remaining, final_status)
93
+ return AccountingTransition(
94
+ result, delta, released, counts == old_counts and final_status is None
95
+ )
@@ -0,0 +1,124 @@
1
+ """Exact MPPI amounts. All monetary outputs are six-decimal USDC base units."""
2
+
3
+ import re
4
+ from collections.abc import Iterable
5
+ from dataclasses import dataclass
6
+
7
+ from .errors import ValidationCode, ValidationError
8
+
9
+ MAX_UINT64 = (1 << 64) - 1
10
+ RATE_SCALE = 1_000_000
11
+ _DECIMAL = re.compile(r"(?:0|[1-9][0-9]*)\Z", re.ASCII)
12
+
13
+
14
+ def uint64(value: int, *, field: str = "value", positive: bool = False) -> int:
15
+ """Reject booleans, coercions and out-of-range integers before encoding."""
16
+ minimum = 1 if positive else 0
17
+ if type(value) is not int or not minimum <= value <= MAX_UINT64:
18
+ raise ValidationError(
19
+ ValidationCode.INVALID_VALUE, f"{field} must be a uint64 integer >= {minimum}"
20
+ )
21
+ return value
22
+
23
+
24
+ def decimal_uint64(value: str, *, field: str = "value") -> int:
25
+ """Parse the exact catalog/TOML decimal-string grammar without float use."""
26
+ if type(value) is not str or len(value) > 20 or _DECIMAL.fullmatch(value) is None:
27
+ raise ValidationError(
28
+ ValidationCode.INVALID_VALUE, f"{field} must be a canonical decimal string"
29
+ )
30
+ return uint64(int(value), field=field)
31
+
32
+
33
+ def numerator(value: int, *, field: str = "numerator") -> int:
34
+ """Validate a wide nonnegative integer; no uint64 intermediate truncation."""
35
+ if type(value) is not int or value < 0:
36
+ raise ValidationError(
37
+ ValidationCode.INVALID_VALUE, f"{field} must be a nonnegative integer"
38
+ )
39
+ return value
40
+
41
+
42
+ @dataclass(frozen=True, slots=True)
43
+ class Rates:
44
+ input: int
45
+ output: int
46
+ cache: int | None = None
47
+
48
+ def __post_init__(self) -> None:
49
+ uint64(self.input, field="input rate")
50
+ uint64(self.output, field="output rate")
51
+ if self.cache is not None:
52
+ uint64(self.cache, field="cache rate")
53
+
54
+ @property
55
+ def greatest(self) -> int:
56
+ return max(self.input, self.output, *(() if self.cache is None else (self.cache,)))
57
+
58
+
59
+ @dataclass(frozen=True, slots=True)
60
+ class TokenCounts:
61
+ """Provider-supplied cumulative counts; cached input is a subset of input."""
62
+
63
+ input_tokens: int
64
+ cache_tokens: int
65
+ output_tokens: int
66
+
67
+ def __post_init__(self) -> None:
68
+ uint64(self.input_tokens, field="input_tokens")
69
+ uint64(self.cache_tokens, field="cache_tokens")
70
+ uint64(self.output_tokens, field="output_tokens")
71
+ if self.cache_tokens > self.input_tokens:
72
+ raise ValidationError(ValidationCode.INVALID_VALUE, "cache_tokens exceeds input_tokens")
73
+
74
+
75
+ def priced_numerator(counts: TokenCounts, rates: Rates) -> int:
76
+ """Price supplied counts without rounding or adding a missing cache rate."""
77
+ if counts.cache_tokens and rates.cache is None:
78
+ raise ValidationError(
79
+ ValidationCode.INCONSISTENT_USAGE, "positive cache count has no accepted cache price"
80
+ )
81
+ return (
82
+ (counts.input_tokens - counts.cache_tokens) * rates.input
83
+ + (counts.cache_tokens * rates.cache if rates.cache is not None else 0)
84
+ + counts.output_tokens * rates.output
85
+ )
86
+
87
+
88
+ def cumulative_usage(numerators: Iterable[int]) -> int:
89
+ """One floor after summing every accepted run/model/version numerator."""
90
+ return uint64(sum(numerator(n) for n in numerators) // RATE_SCALE, field="cumulative usage")
91
+
92
+
93
+ def required_coverage(numerators: Iterable[int]) -> int:
94
+ """One ceiling over usage and all remaining/new bounds, never per row."""
95
+ total = sum(numerator(n) for n in numerators)
96
+ whole, remainder = divmod(total, RATE_SCALE)
97
+ return uint64(whole + bool(remainder), field="required coverage")
98
+
99
+
100
+ def funding_amount(tokens: int, rates: Rates) -> int:
101
+ """Caller-chosen positive funding tokens at the greatest accepted rate."""
102
+ uint64(tokens, field="funding tokens", positive=True)
103
+ amount = required_coverage((tokens * rates.greatest,))
104
+ return uint64(amount, field="funding amount", positive=True)
105
+
106
+
107
+ def top_up_total(deposit: int, amount: int) -> int:
108
+ """Validate positive raw-unit funding and cumulative deposit capacity."""
109
+ uint64(deposit, field="deposit", positive=True)
110
+ uint64(amount, field="top-up amount", positive=True)
111
+ return uint64(deposit + amount, field="cumulative deposit", positive=True)
112
+
113
+
114
+ def remaining_funding_tokens(
115
+ deposit: int, usage_numerator: int, reservation_numerators: Iterable[int], rates: Rates
116
+ ) -> int | None:
117
+ """Advisory deposit headroom; all-zero pricing has no token estimate."""
118
+ uint64(deposit, field="deposit")
119
+ numerator(usage_numerator)
120
+ reserved = sum(numerator(n) for n in reservation_numerators)
121
+ if rates.greatest == 0:
122
+ return None
123
+ available = max(0, deposit * RATE_SCALE - usage_numerator - reserved)
124
+ return min(MAX_UINT64, available // rates.greatest)
@@ -0,0 +1,79 @@
1
+ """Wallet capability contracts. No wallet adapter is supplied by this module."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Protocol
5
+
6
+ from .amounts import uint64
7
+ from .errors import ValidationCode, ValidationError
8
+ from .types import Domain, OperationResult, RunContext, fixed_bytes, uint256
9
+
10
+
11
+ @dataclass(frozen=True, slots=True)
12
+ class WalletCall:
13
+ """A nonpayable MPPI/registry call made by the configured wallet."""
14
+
15
+ chain_id: int
16
+ to: bytes
17
+ data: bytes
18
+
19
+ def __post_init__(self) -> None:
20
+ uint64(self.chain_id, field="chain_id", positive=True)
21
+ fixed_bytes(self.to, 20, field="to", nonzero=True)
22
+ if type(self.data) is not bytes:
23
+ raise ValidationError(ValidationCode.INVALID_VALUE, "calldata must be bytes")
24
+
25
+
26
+ @dataclass(frozen=True, slots=True)
27
+ class FundingAuthorization:
28
+ """USDC receiveWithAuthorization input, in the token's separate domain."""
29
+
30
+ chain_id: int
31
+ token: bytes
32
+ domain_name: str
33
+ domain_version: str
34
+ owner: bytes
35
+ recipient: bytes
36
+ value: int
37
+ valid_after: int
38
+ valid_before: int
39
+ nonce: bytes
40
+
41
+ def __post_init__(self) -> None:
42
+ uint64(self.chain_id, field="chain_id", positive=True)
43
+ for name in ("token", "owner", "recipient"):
44
+ fixed_bytes(getattr(self, name), 20, field=name, nonzero=True)
45
+ for name in ("value", "valid_after", "valid_before"):
46
+ uint256(getattr(self, name), field=name)
47
+ fixed_bytes(self.nonce, 32, field="nonce")
48
+ if not self.domain_name or not self.domain_version or self.valid_after >= self.valid_before:
49
+ raise ValidationError(
50
+ ValidationCode.INVALID_VALUE, "invalid token domain or authorization interval"
51
+ )
52
+
53
+
54
+ class TransactionWallet(Protocol):
55
+ @property
56
+ def address(self) -> bytes: ...
57
+
58
+ def execute(self, call: WalletCall, *, operation_ref: str) -> OperationResult: ...
59
+
60
+ def reconcile(self, *, operation_ref: str) -> OperationResult: ...
61
+
62
+
63
+ class FundingWallet(TransactionWallet, Protocol):
64
+ def sign_funding_authorization(self, authorization: FundingAuthorization) -> bytes: ...
65
+
66
+
67
+ class SessionSigner(Protocol):
68
+ """The fixed ECDSA capability, separate from wallet identity and authority."""
69
+
70
+ @property
71
+ def address(self) -> bytes: ...
72
+
73
+ def sign_voucher(self, domain: Domain, session_id: bytes, cumulative_amount: int) -> bytes: ...
74
+
75
+ def sign_run(self, domain: Domain, context: RunContext) -> bytes: ...
76
+
77
+ def sign_close(self, domain: Domain, session_id: bytes) -> bytes: ...
78
+
79
+ def sign_reconnect(self, domain: Domain, session_id: bytes, nonce: bytes) -> bytes: ...