swig-developer-sdk 0.6.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.
- swig_developer_sdk/__init__.py +180 -0
- swig_developer_sdk/client.py +36 -0
- swig_developer_sdk/common.py +104 -0
- swig_developer_sdk/core.py +162 -0
- swig_developer_sdk/evm.py +64 -0
- swig_developer_sdk/one_business.py +163 -0
- swig_developer_sdk/passkeys.py +122 -0
- swig_developer_sdk/paymaster.py +106 -0
- swig_developer_sdk/proxy.py +724 -0
- swig_developer_sdk/ramp.py +724 -0
- swig_developer_sdk/signing.py +461 -0
- swig_developer_sdk/transactions.py +277 -0
- swig_developer_sdk/wallets.py +1393 -0
- swig_developer_sdk-0.6.0.dist-info/METADATA +89 -0
- swig_developer_sdk-0.6.0.dist-info/RECORD +16 -0
- swig_developer_sdk-0.6.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import inspect
|
|
5
|
+
import struct
|
|
6
|
+
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Protocol, TypeAlias, TypeVar, cast
|
|
9
|
+
|
|
10
|
+
from solders.instruction import CompiledInstruction
|
|
11
|
+
from solders.message import Message, MessageV0
|
|
12
|
+
from solders.pubkey import Pubkey
|
|
13
|
+
from solders.signature import Signature
|
|
14
|
+
from solders.transaction import VersionedTransaction
|
|
15
|
+
|
|
16
|
+
from .transactions import (
|
|
17
|
+
ClientSignatureRequest,
|
|
18
|
+
PreparedTransaction,
|
|
19
|
+
SignedPreparedTransaction,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
SECP256R1_PROGRAM_ID = Pubkey.from_string("Secp256r1SigVerify1111111111111111111111111")
|
|
23
|
+
SWIG_PROGRAM_ID = Pubkey.from_string("swigypWHEksbC64pWKwah1WTeh9JXwx8H1rJHLdbQMB")
|
|
24
|
+
SIGN_V2_DISCRIMINATOR = 11
|
|
25
|
+
SECP256R1_AUTHORITY_PAYLOAD_SIZE = 17
|
|
26
|
+
SECP256R1_PUBLIC_KEY_SIZE = 33
|
|
27
|
+
SECP256R1_SIGNATURE_SIZE = 64
|
|
28
|
+
SECP256R1_HEADER_SIZE = 16
|
|
29
|
+
SECP256K1_AUTHORITY_PAYLOAD_SIZE = 77
|
|
30
|
+
SECP256K1_SIGNATURE_SIZE = 65
|
|
31
|
+
SECP256K1_SIGNATURE_OFFSET = 12
|
|
32
|
+
U16_MAX = 65535
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True, slots=True)
|
|
36
|
+
class PasskeySigningResult:
|
|
37
|
+
signature: bytes
|
|
38
|
+
prefix: bytes | None = None
|
|
39
|
+
message: bytes | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True, slots=True)
|
|
43
|
+
class Secp256k1SigningResult:
|
|
44
|
+
signature: bytes
|
|
45
|
+
prefix: bytes | None = None
|
|
46
|
+
message: bytes | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
T = TypeVar("T")
|
|
50
|
+
MaybeAwaitable: TypeAlias = T | Awaitable[T]
|
|
51
|
+
PasskeySigningFn: TypeAlias = Callable[[bytes], MaybeAwaitable[PasskeySigningResult]]
|
|
52
|
+
Secp256k1SigningFn: TypeAlias = Callable[
|
|
53
|
+
[bytes], MaybeAwaitable[Secp256k1SigningResult]
|
|
54
|
+
]
|
|
55
|
+
PreparedTransactionSigningFn: TypeAlias = Callable[
|
|
56
|
+
[str, PreparedTransaction], MaybeAwaitable[str]
|
|
57
|
+
]
|
|
58
|
+
Secp256r1SigningFns: TypeAlias = PasskeySigningFn | Mapping[str, PasskeySigningFn]
|
|
59
|
+
Secp256k1SigningFns: TypeAlias = Secp256k1SigningFn | Mapping[str, Secp256k1SigningFn]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class PreparedTransactionSigner(Protocol):
|
|
63
|
+
def sign_prepared_transaction(
|
|
64
|
+
self, prepared: PreparedTransaction
|
|
65
|
+
) -> MaybeAwaitable[SignedPreparedTransaction]: ...
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def sign_prepared_transaction(
|
|
69
|
+
prepared: PreparedTransaction,
|
|
70
|
+
*,
|
|
71
|
+
sign_transaction: PreparedTransactionSigningFn,
|
|
72
|
+
) -> SignedPreparedTransaction:
|
|
73
|
+
transaction = await _resolve(sign_transaction(prepared.transaction, prepared))
|
|
74
|
+
return SignedPreparedTransaction(
|
|
75
|
+
transaction=transaction,
|
|
76
|
+
transaction_encoding=prepared.transaction_encoding,
|
|
77
|
+
network=prepared.network,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async def sign_prepared_transaction_with_signer(
|
|
82
|
+
prepared: PreparedTransaction,
|
|
83
|
+
signer: PreparedTransactionSigner,
|
|
84
|
+
) -> SignedPreparedTransaction:
|
|
85
|
+
return await _resolve(signer.sign_prepared_transaction(prepared))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def sign_prepared_swig_transaction(
|
|
89
|
+
prepared: PreparedTransaction,
|
|
90
|
+
*,
|
|
91
|
+
secp256r1: Secp256r1SigningFns | None = None,
|
|
92
|
+
secp256k1: Secp256k1SigningFns | None = None,
|
|
93
|
+
) -> SignedPreparedTransaction:
|
|
94
|
+
if prepared.transaction_encoding not in (None, "base64"):
|
|
95
|
+
raise ValueError("Only base64 prepared transactions can be signed")
|
|
96
|
+
|
|
97
|
+
transaction = _MutableTransaction.from_bytes(base64.b64decode(prepared.transaction))
|
|
98
|
+
for request in prepared.signature_requests:
|
|
99
|
+
if request.scheme == "secp256r1":
|
|
100
|
+
await _apply_secp256r1(transaction, request, secp256r1)
|
|
101
|
+
elif request.scheme == "secp256k1":
|
|
102
|
+
await _apply_secp256k1(transaction, request, secp256k1)
|
|
103
|
+
|
|
104
|
+
return SignedPreparedTransaction(
|
|
105
|
+
transaction=base64.b64encode(transaction.serialize()).decode("ascii"),
|
|
106
|
+
transaction_encoding=prepared.transaction_encoding or "base64",
|
|
107
|
+
network=prepared.network,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
async def sign_prepared_swig_transactions(
|
|
112
|
+
prepared_transactions: Sequence[PreparedTransaction],
|
|
113
|
+
*,
|
|
114
|
+
secp256r1: Secp256r1SigningFns | None = None,
|
|
115
|
+
secp256k1: Secp256k1SigningFns | None = None,
|
|
116
|
+
) -> tuple[SignedPreparedTransaction, ...]:
|
|
117
|
+
signed: list[SignedPreparedTransaction] = []
|
|
118
|
+
for prepared in prepared_transactions:
|
|
119
|
+
signed.append(
|
|
120
|
+
await sign_prepared_swig_transaction(
|
|
121
|
+
prepared,
|
|
122
|
+
secp256r1=secp256r1,
|
|
123
|
+
secp256k1=secp256k1,
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
return tuple(signed)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
async def _apply_secp256r1(
|
|
130
|
+
transaction: _MutableTransaction,
|
|
131
|
+
request: ClientSignatureRequest,
|
|
132
|
+
signing_fns: Secp256r1SigningFns | None,
|
|
133
|
+
) -> None:
|
|
134
|
+
if signing_fns is None:
|
|
135
|
+
raise ValueError("No secp256r1 signing function registered")
|
|
136
|
+
public_key = _hex_to_bytes(request.signer)
|
|
137
|
+
if len(public_key) != SECP256R1_PUBLIC_KEY_SIZE:
|
|
138
|
+
raise ValueError("Secp256r1 signature request signer must be 33 bytes")
|
|
139
|
+
message_hash = _hex_to_bytes(request.message_hash)
|
|
140
|
+
signing_fn = _resolve_passkey_signing_fn(request, signing_fns)
|
|
141
|
+
result = await _resolve(signing_fn(message_hash))
|
|
142
|
+
if len(result.signature) != SECP256R1_SIGNATURE_SIZE:
|
|
143
|
+
raise ValueError("Secp256r1 signature must be 64 bytes")
|
|
144
|
+
|
|
145
|
+
instruction_index = _find_secp256r1_instruction(
|
|
146
|
+
transaction, public_key, message_hash
|
|
147
|
+
)
|
|
148
|
+
transaction.set_instruction_data(
|
|
149
|
+
instruction_index,
|
|
150
|
+
_encode_secp256r1_instruction(
|
|
151
|
+
public_key,
|
|
152
|
+
result.signature,
|
|
153
|
+
result.message or message_hash,
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
if result.prefix is not None:
|
|
157
|
+
sign_v2_index = _find_following_sign_v2(transaction, instruction_index)
|
|
158
|
+
transaction.set_instruction_data(
|
|
159
|
+
sign_v2_index,
|
|
160
|
+
_patch_authority_prefix(
|
|
161
|
+
transaction.instruction_data(sign_v2_index),
|
|
162
|
+
result.prefix,
|
|
163
|
+
SECP256R1_AUTHORITY_PAYLOAD_SIZE,
|
|
164
|
+
"secp256r1",
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async def _apply_secp256k1(
|
|
170
|
+
transaction: _MutableTransaction,
|
|
171
|
+
request: ClientSignatureRequest,
|
|
172
|
+
signing_fns: Secp256k1SigningFns | None,
|
|
173
|
+
) -> None:
|
|
174
|
+
if signing_fns is None:
|
|
175
|
+
raise ValueError("No secp256k1 signing function registered")
|
|
176
|
+
signing_fn = _resolve_secp256k1_signing_fn(request, signing_fns)
|
|
177
|
+
message = _normalize_hex(request.message_hash).encode("ascii")
|
|
178
|
+
result = await _resolve(signing_fn(message))
|
|
179
|
+
if len(result.signature) != SECP256K1_SIGNATURE_SIZE:
|
|
180
|
+
raise ValueError("Secp256k1 signature must be 65 bytes")
|
|
181
|
+
index = _find_secp256k1_sign_v2(transaction, request)
|
|
182
|
+
transaction.set_instruction_data(
|
|
183
|
+
index,
|
|
184
|
+
_patch_secp256k1_payload(transaction.instruction_data(index), request, result),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _resolve_passkey_signing_fn(
|
|
189
|
+
request: ClientSignatureRequest,
|
|
190
|
+
signing_fns: Secp256r1SigningFns,
|
|
191
|
+
) -> PasskeySigningFn:
|
|
192
|
+
if callable(signing_fns):
|
|
193
|
+
return signing_fns
|
|
194
|
+
normalized = _normalize_hex(request.signer)
|
|
195
|
+
signing_fn = (
|
|
196
|
+
signing_fns.get(normalized)
|
|
197
|
+
or signing_fns.get(f"0x{normalized}")
|
|
198
|
+
or signing_fns.get(request.signer)
|
|
199
|
+
)
|
|
200
|
+
if signing_fn is None:
|
|
201
|
+
raise ValueError(
|
|
202
|
+
f"No secp256r1 signing function registered for signer {request.signer}"
|
|
203
|
+
)
|
|
204
|
+
return signing_fn
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _resolve_secp256k1_signing_fn(
|
|
208
|
+
request: ClientSignatureRequest,
|
|
209
|
+
signing_fns: Secp256k1SigningFns,
|
|
210
|
+
) -> Secp256k1SigningFn:
|
|
211
|
+
if callable(signing_fns):
|
|
212
|
+
return signing_fns
|
|
213
|
+
normalized = _normalize_hex(request.signer)
|
|
214
|
+
signing_fn = (
|
|
215
|
+
signing_fns.get(normalized)
|
|
216
|
+
or signing_fns.get(f"0x{normalized}")
|
|
217
|
+
or signing_fns.get(request.signer)
|
|
218
|
+
)
|
|
219
|
+
if signing_fn is None:
|
|
220
|
+
raise ValueError(
|
|
221
|
+
f"No secp256k1 signing function registered for signer {request.signer}"
|
|
222
|
+
)
|
|
223
|
+
return signing_fn
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class _MutableTransaction:
|
|
227
|
+
def __init__(
|
|
228
|
+
self,
|
|
229
|
+
message: Message | MessageV0,
|
|
230
|
+
signatures: Sequence[Signature],
|
|
231
|
+
) -> None:
|
|
232
|
+
self.message = message
|
|
233
|
+
self.signatures = list(signatures)
|
|
234
|
+
self.instructions = list(message.instructions)
|
|
235
|
+
|
|
236
|
+
@classmethod
|
|
237
|
+
def from_bytes(cls, value: bytes) -> _MutableTransaction:
|
|
238
|
+
transaction = VersionedTransaction.from_bytes(value)
|
|
239
|
+
return cls(transaction.message, transaction.signatures)
|
|
240
|
+
|
|
241
|
+
def program_id(self, index: int) -> Pubkey:
|
|
242
|
+
instruction = self.instructions[index]
|
|
243
|
+
account_keys = self.message.account_keys
|
|
244
|
+
if instruction.program_id_index >= len(account_keys):
|
|
245
|
+
raise ValueError(
|
|
246
|
+
"Versioned transaction program id is not statically available"
|
|
247
|
+
)
|
|
248
|
+
return account_keys[instruction.program_id_index]
|
|
249
|
+
|
|
250
|
+
def instruction_data(self, index: int) -> bytes:
|
|
251
|
+
return bytes(self.instructions[index].data)
|
|
252
|
+
|
|
253
|
+
def set_instruction_data(self, index: int, data: bytes) -> None:
|
|
254
|
+
instruction = self.instructions[index]
|
|
255
|
+
self.instructions[index] = CompiledInstruction(
|
|
256
|
+
instruction.program_id_index,
|
|
257
|
+
data,
|
|
258
|
+
bytes(instruction.accounts),
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def serialize(self) -> bytes:
|
|
262
|
+
if isinstance(self.message, MessageV0):
|
|
263
|
+
message: Message | MessageV0 = MessageV0(
|
|
264
|
+
self.message.header,
|
|
265
|
+
self.message.account_keys,
|
|
266
|
+
self.message.recent_blockhash,
|
|
267
|
+
self.instructions,
|
|
268
|
+
self.message.address_table_lookups,
|
|
269
|
+
)
|
|
270
|
+
else:
|
|
271
|
+
header = self.message.header
|
|
272
|
+
message = Message.new_with_compiled_instructions(
|
|
273
|
+
header.num_required_signatures,
|
|
274
|
+
header.num_readonly_signed_accounts,
|
|
275
|
+
header.num_readonly_unsigned_accounts,
|
|
276
|
+
self.message.account_keys,
|
|
277
|
+
self.message.recent_blockhash,
|
|
278
|
+
self.instructions,
|
|
279
|
+
)
|
|
280
|
+
return bytes(VersionedTransaction.populate(message, self.signatures))
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _find_secp256r1_instruction(
|
|
284
|
+
transaction: _MutableTransaction,
|
|
285
|
+
public_key: bytes,
|
|
286
|
+
message_hash: bytes,
|
|
287
|
+
) -> int:
|
|
288
|
+
for index in range(len(transaction.instructions)):
|
|
289
|
+
if transaction.program_id(index) != SECP256R1_PROGRAM_ID:
|
|
290
|
+
continue
|
|
291
|
+
parsed = _parse_secp256r1_instruction(transaction.instruction_data(index))
|
|
292
|
+
if parsed is not None and parsed == (public_key, message_hash):
|
|
293
|
+
return index
|
|
294
|
+
raise ValueError("Matching secp256r1 signature instruction not found")
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _find_following_sign_v2(
|
|
298
|
+
transaction: _MutableTransaction,
|
|
299
|
+
secp_instruction_index: int,
|
|
300
|
+
) -> int:
|
|
301
|
+
for index in range(secp_instruction_index + 1, len(transaction.instructions)):
|
|
302
|
+
if (
|
|
303
|
+
transaction.program_id(index) == SWIG_PROGRAM_ID
|
|
304
|
+
and _read_u16(transaction.instruction_data(index), 0)
|
|
305
|
+
== SIGN_V2_DISCRIMINATOR
|
|
306
|
+
):
|
|
307
|
+
return index
|
|
308
|
+
raise ValueError("Swig SignV2 instruction following secp256r1 precompile not found")
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _find_secp256k1_sign_v2(
|
|
312
|
+
transaction: _MutableTransaction,
|
|
313
|
+
request: ClientSignatureRequest,
|
|
314
|
+
) -> int:
|
|
315
|
+
fallback: list[int] = []
|
|
316
|
+
for index in range(len(transaction.instructions)):
|
|
317
|
+
if transaction.program_id(index) != SWIG_PROGRAM_ID:
|
|
318
|
+
continue
|
|
319
|
+
data = transaction.instruction_data(index)
|
|
320
|
+
if _read_u16(data, 0) != SIGN_V2_DISCRIMINATOR:
|
|
321
|
+
continue
|
|
322
|
+
offset = _authority_payload_offset(data)
|
|
323
|
+
if len(data) < offset + SECP256K1_AUTHORITY_PAYLOAD_SIZE:
|
|
324
|
+
continue
|
|
325
|
+
fallback.append(index)
|
|
326
|
+
if (
|
|
327
|
+
_read_u64(data, offset) == request.slot
|
|
328
|
+
and _read_u32(data, offset + 8) == request.counter
|
|
329
|
+
):
|
|
330
|
+
return index
|
|
331
|
+
if len(fallback) == 1:
|
|
332
|
+
return fallback[0]
|
|
333
|
+
raise ValueError("Matching secp256k1 Swig SignV2 instruction not found")
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _encode_secp256r1_instruction(
|
|
337
|
+
public_key: bytes,
|
|
338
|
+
signature: bytes,
|
|
339
|
+
message: bytes,
|
|
340
|
+
) -> bytes:
|
|
341
|
+
message_offset = (
|
|
342
|
+
SECP256R1_HEADER_SIZE + SECP256R1_PUBLIC_KEY_SIZE + SECP256R1_SIGNATURE_SIZE
|
|
343
|
+
)
|
|
344
|
+
data = bytearray(message_offset + len(message))
|
|
345
|
+
data[0] = 1
|
|
346
|
+
struct.pack_into(
|
|
347
|
+
"<7H",
|
|
348
|
+
data,
|
|
349
|
+
2,
|
|
350
|
+
SECP256R1_HEADER_SIZE + SECP256R1_PUBLIC_KEY_SIZE,
|
|
351
|
+
U16_MAX,
|
|
352
|
+
SECP256R1_HEADER_SIZE,
|
|
353
|
+
U16_MAX,
|
|
354
|
+
message_offset,
|
|
355
|
+
len(message),
|
|
356
|
+
U16_MAX,
|
|
357
|
+
)
|
|
358
|
+
data[SECP256R1_HEADER_SIZE : SECP256R1_HEADER_SIZE + 33] = public_key
|
|
359
|
+
signature_start = SECP256R1_HEADER_SIZE + SECP256R1_PUBLIC_KEY_SIZE
|
|
360
|
+
data[signature_start : signature_start + 64] = signature
|
|
361
|
+
data[message_offset:] = message
|
|
362
|
+
return bytes(data)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _parse_secp256r1_instruction(data: bytes) -> tuple[bytes, bytes] | None:
|
|
366
|
+
if len(data) < SECP256R1_HEADER_SIZE or data[0] != 1:
|
|
367
|
+
return None
|
|
368
|
+
public_key_offset = _read_u16(data, 6)
|
|
369
|
+
message_offset = _read_u16(data, 10)
|
|
370
|
+
message_size = _read_u16(data, 12)
|
|
371
|
+
if (
|
|
372
|
+
len(data) < public_key_offset + SECP256R1_PUBLIC_KEY_SIZE
|
|
373
|
+
or len(data) < message_offset + message_size
|
|
374
|
+
):
|
|
375
|
+
return None
|
|
376
|
+
return (
|
|
377
|
+
data[public_key_offset : public_key_offset + SECP256R1_PUBLIC_KEY_SIZE],
|
|
378
|
+
data[message_offset : message_offset + message_size],
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _patch_authority_prefix(
|
|
383
|
+
instruction_data: bytes,
|
|
384
|
+
prefix: bytes,
|
|
385
|
+
fixed_payload_size: int,
|
|
386
|
+
scheme: str,
|
|
387
|
+
) -> bytes:
|
|
388
|
+
offset = _authority_payload_offset(instruction_data)
|
|
389
|
+
if len(instruction_data) < offset + fixed_payload_size:
|
|
390
|
+
raise ValueError(
|
|
391
|
+
f"Swig SignV2 instruction is missing {scheme} authority payload"
|
|
392
|
+
)
|
|
393
|
+
return instruction_data[: offset + fixed_payload_size] + prefix
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _patch_secp256k1_payload(
|
|
397
|
+
instruction_data: bytes,
|
|
398
|
+
request: ClientSignatureRequest,
|
|
399
|
+
result: Secp256k1SigningResult,
|
|
400
|
+
) -> bytes:
|
|
401
|
+
offset = _authority_payload_offset(instruction_data)
|
|
402
|
+
if len(instruction_data) < offset + SECP256K1_AUTHORITY_PAYLOAD_SIZE:
|
|
403
|
+
raise ValueError(
|
|
404
|
+
"Swig SignV2 instruction is missing secp256k1 authority payload"
|
|
405
|
+
)
|
|
406
|
+
if request.slot < 0 or request.slot > 0xFFFFFFFFFFFFFFFF:
|
|
407
|
+
raise ValueError("Secp256k1 signature request slot must fit in u64")
|
|
408
|
+
if request.counter < 0 or request.counter > 0xFFFFFFFF:
|
|
409
|
+
raise ValueError("Secp256k1 signature request counter must fit in u32")
|
|
410
|
+
end = offset + SECP256K1_AUTHORITY_PAYLOAD_SIZE
|
|
411
|
+
patched = bytearray(instruction_data[:end])
|
|
412
|
+
struct.pack_into("<QI", patched, offset, request.slot, request.counter)
|
|
413
|
+
start = offset + SECP256K1_SIGNATURE_OFFSET
|
|
414
|
+
patched[start : start + SECP256K1_SIGNATURE_SIZE] = result.signature
|
|
415
|
+
if result.prefix is not None:
|
|
416
|
+
patched.extend(result.prefix)
|
|
417
|
+
return bytes(patched)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _authority_payload_offset(data: bytes) -> int:
|
|
421
|
+
return 8 + _read_u16(data, 2)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _normalize_hex(value: str) -> str:
|
|
425
|
+
normalized = value[2:] if value.startswith("0x") else value
|
|
426
|
+
if not normalized or any(
|
|
427
|
+
char not in "0123456789abcdefABCDEF" for char in normalized
|
|
428
|
+
):
|
|
429
|
+
raise ValueError("Invalid hex string")
|
|
430
|
+
return normalized.lower()
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _hex_to_bytes(value: str) -> bytes:
|
|
434
|
+
normalized = _normalize_hex(value)
|
|
435
|
+
if len(normalized) % 2:
|
|
436
|
+
raise ValueError("Hex strings must contain an even number of characters")
|
|
437
|
+
return bytes.fromhex(normalized)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _read_u16(data: bytes, offset: int) -> int:
|
|
441
|
+
if len(data) < offset + 2:
|
|
442
|
+
raise ValueError("Instruction data is too short")
|
|
443
|
+
return int(struct.unpack_from("<H", data, offset)[0])
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _read_u32(data: bytes, offset: int) -> int:
|
|
447
|
+
if len(data) < offset + 4:
|
|
448
|
+
raise ValueError("Instruction data is too short")
|
|
449
|
+
return int(struct.unpack_from("<I", data, offset)[0])
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def _read_u64(data: bytes, offset: int) -> int:
|
|
453
|
+
if len(data) < offset + 8:
|
|
454
|
+
raise ValueError("Instruction data is too short")
|
|
455
|
+
return int(struct.unpack_from("<Q", data, offset)[0])
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
async def _resolve(value: MaybeAwaitable[T]) -> T:
|
|
459
|
+
if inspect.isawaitable(value):
|
|
460
|
+
return await cast(Awaitable[T], value)
|
|
461
|
+
return value
|