golem_base_sdk

GolemBase Python SDK.

  1"""GolemBase Python SDK."""
  2
  3import asyncio
  4import base64
  5import logging
  6import logging.config
  7import typing
  8from collections.abc import (
  9    AsyncGenerator,
 10    Callable,
 11    Coroutine,
 12    Sequence,
 13)
 14from typing import (
 15    Any,
 16    cast,
 17)
 18
 19from eth_typing import ChecksumAddress, HexStr
 20from web3 import AsyncWeb3, WebSocketProvider
 21from web3.contract import AsyncContract
 22from web3.exceptions import ProviderConnectionError, Web3RPCError
 23from web3.method import Method, default_root_munger
 24from web3.middleware import SignAndSendRawMiddlewareBuilder
 25from web3.types import LogReceipt, RPCEndpoint, TxParams, TxReceipt, Wei
 26from web3.utils.subscriptions import (
 27    LogsSubscription,
 28    LogsSubscriptionContext,
 29)
 30
 31from .constants import (
 32    GOLEM_BASE_ABI,
 33    STORAGE_ADDRESS,
 34)
 35from .types import (
 36    Address,
 37    Annotation,
 38    CreateEntityReturnType,
 39    EntityKey,
 40    EntityMetadata,
 41    ExtendEntityReturnType,
 42    GenericBytes,
 43    GolemBaseCreate,
 44    GolemBaseDelete,
 45    GolemBaseExtend,
 46    GolemBaseTransaction,
 47    GolemBaseTransactionReceipt,
 48    GolemBaseUpdate,
 49    QueryEntitiesResult,
 50    UpdateEntityReturnType,
 51    WatchLogsHandle,
 52)
 53from .utils import rlp_encode_transaction
 54from .wallet import (
 55    WalletError,
 56    decrypt_wallet,
 57)
 58
 59__all__: Sequence[str] = [
 60    # Exports from .types
 61    "Address",
 62    "Annotation",
 63    "CreateEntityReturnType",
 64    "EntityKey",
 65    "EntityMetadata",
 66    "ExtendEntityReturnType",
 67    "GenericBytes",
 68    "GolemBaseCreate",
 69    "GolemBaseDelete",
 70    "GolemBaseExtend",
 71    "GolemBaseTransaction",
 72    "GolemBaseTransactionReceipt",
 73    "GolemBaseUpdate",
 74    "QueryEntitiesResult",
 75    "UpdateEntityReturnType",
 76    "WatchLogsHandle",
 77    # Exports from .constants
 78    "GOLEM_BASE_ABI",
 79    "STORAGE_ADDRESS",
 80    # Exports from .wallet
 81    "decrypt_wallet",
 82    "WalletError",
 83    # Exports from this file
 84    "GolemBaseClient",
 85    # Re-exports
 86    "Wei",
 87]
 88
 89
 90logger = logging.getLogger(__name__)
 91"""@private"""
 92
 93
 94class GolemBaseHttpClient(AsyncWeb3):
 95    """Subclass of AsyncWeb3 with added Golem Base methods."""
 96
 97    def __init__(self, rpc_url: str):
 98        super().__init__(
 99            AsyncWeb3.AsyncHTTPProvider(rpc_url, request_kwargs={"timeout": 60})
100        )
101
102        self.eth.attach_methods(
103            {
104                "get_storage_value": Method(
105                    json_rpc_method=RPCEndpoint("golembase_getStorageValue"),
106                    mungers=[default_root_munger],
107                ),
108                "get_entity_metadata": Method(
109                    json_rpc_method=RPCEndpoint("golembase_getEntityMetaData"),
110                    mungers=[default_root_munger],
111                ),
112                "get_entities_to_expire_at_block": Method(
113                    json_rpc_method=RPCEndpoint("golembase_getEntitiesToExpireAtBlock"),
114                    mungers=[default_root_munger],
115                ),
116                "get_entity_count": Method(
117                    json_rpc_method=RPCEndpoint("golembase_getEntityCount"),
118                    mungers=[default_root_munger],
119                ),
120                "get_all_entity_keys": Method(
121                    json_rpc_method=RPCEndpoint("golembase_getAllEntityKeys"),
122                    mungers=[default_root_munger],
123                ),
124                "get_entities_of_owner": Method(
125                    json_rpc_method=RPCEndpoint("golembase_getEntitiesOfOwner"),
126                    mungers=[default_root_munger],
127                ),
128                "query_entities": Method(
129                    json_rpc_method=RPCEndpoint("golembase_queryEntities"),
130                    mungers=[default_root_munger],
131                ),
132            }
133        )
134
135    async def get_storage_value(self, entity_key: EntityKey) -> bytes:
136        """Get the storage value stored in the given entity."""
137        return base64.b64decode(
138            await self.eth.get_storage_value(  # type: ignore[attr-defined]
139                entity_key.as_hex_string()
140            )
141        )
142
143    async def get_entity_metadata(self, entity_key: EntityKey) -> EntityMetadata:
144        """Get the metadata of the given entity."""
145        metadata = await self.eth.get_entity_metadata(  # type: ignore[attr-defined]
146            entity_key.as_hex_string()
147        )
148
149        return EntityMetadata(
150            entity_key=entity_key,
151            owner=Address(GenericBytes.from_hex_string(metadata.owner)),
152            expires_at_block=metadata.expiresAtBlock,
153            string_annotations=list(
154                map(
155                    lambda ann: Annotation(key=ann["key"], value=ann["value"]),
156                    metadata.stringAnnotations,
157                )
158            ),
159            numeric_annotations=list(
160                map(
161                    lambda ann: Annotation(key=ann["key"], value=ann["value"]),
162                    metadata.numericAnnotations,
163                )
164            ),
165        )
166
167    async def get_entities_to_expire_at_block(
168        self, block_number: int
169    ) -> Sequence[EntityKey]:
170        """Get all entities that will expire at the given block."""
171        return list(
172            map(
173                lambda e: EntityKey(GenericBytes.from_hex_string(e)),
174                await self.eth.get_entities_to_expire_at_block(  # type: ignore[attr-defined]
175                    block_number
176                ),
177            )
178        )
179
180    async def get_entity_count(self) -> int:
181        """Get the total entity count in Golem Base."""
182        return cast(int, await self.eth.get_entity_count())  # type: ignore[attr-defined]
183
184    async def get_all_entity_keys(self) -> Sequence[EntityKey]:
185        """Get all entity keys in Golem Base."""
186        return list(
187            map(
188                lambda e: EntityKey(GenericBytes.from_hex_string(e)),
189                await self.eth.get_all_entity_keys(),  # type: ignore[attr-defined]
190            )
191        )
192
193    async def get_entities_of_owner(
194        self, owner: ChecksumAddress
195    ) -> Sequence[EntityKey]:
196        """Get all the entities owned by the given address."""
197        return list(
198            map(
199                lambda e: EntityKey(GenericBytes.from_hex_string(e)),
200                await self.eth.get_entities_of_owner(owner),  # type: ignore[attr-defined]
201            )
202        )
203
204    async def query_entities(self, query: str) -> Sequence[QueryEntitiesResult]:
205        """Get all entities that satisfy the given Golem Base query."""
206        return list(
207            map(
208                lambda result: QueryEntitiesResult(
209                    entity_key=result.key, storage_value=base64.b64decode(result.value)
210                ),
211                await self.eth.query_entities(query),  # type: ignore[attr-defined]
212            )
213        )
214
215
216class GolemBaseROClient:
217    _http_client: GolemBaseHttpClient
218    _ws_client: AsyncWeb3
219    _golem_base_contract: AsyncContract
220    _background_tasks: set[asyncio.Task[None]]
221
222    @staticmethod
223    async def create_ro_client(rpc_url: str, ws_url: str) -> "GolemBaseROClient":
224        """
225        Create a `GolemBaseClient` instance.
226
227        This is the preferred method to create an instance.
228        """
229        return GolemBaseROClient(
230            rpc_url, await GolemBaseROClient._create_ws_client(ws_url)
231        )
232
233    @staticmethod
234    async def _create_ws_client(ws_url: str) -> "AsyncWeb3":
235        ws_client: AsyncWeb3 = await AsyncWeb3(WebSocketProvider(ws_url))
236        return ws_client
237
238    def __init__(self, rpc_url: str, ws_client: AsyncWeb3) -> None:
239        """Initialise the GolemBaseClient instance."""
240        self._http_client = GolemBaseHttpClient(rpc_url)
241        self._ws_client = ws_client
242
243        # Keep references to async tasks we created
244        self._background_tasks = set()
245
246        def is_connected(
247            client: AsyncWeb3,
248        ) -> Callable[[bool], Coroutine[Any, Any, bool]]:
249            async def inner(show_traceback: bool) -> bool:
250                try:
251                    logger.debug("Calling eth_blockNumber to test connectivity...")
252                    await client.eth.get_block_number()
253                    return True
254                except (OSError, ProviderConnectionError) as e:
255                    logger.debug(
256                        "Problem connecting to provider", exc_info=show_traceback
257                    )
258                    if show_traceback:
259                        raise ProviderConnectionError(
260                            "Problem connecting to provider"
261                        ) from e
262                    return False
263
264            return inner
265
266        # The default is_connected method calls web3_clientVersion, but the web3
267        # API is not enabled on all our nodes, so let's monkey patch this to call
268        # eth_getBlockNumber instead.
269        # The method on the provider is usually not called directly, instead you
270        # can call the eponymous method on the client, which will delegate to the
271        # provider.
272        object.__setattr__(
273            self.http_client().provider,
274            "is_connected",
275            is_connected(self.http_client()),
276        )
277
278        # Allow caching of certain methods to improve performance
279        self.http_client().provider.cache_allowed_requests = True
280
281        # https://github.com/pylint-dev/pylint/issues/3162
282        # pylint: disable=no-member
283        self.golem_base_contract = self.http_client().eth.contract(
284            address=STORAGE_ADDRESS.as_address(),
285            abi=GOLEM_BASE_ABI,
286        )
287        for event in self.golem_base_contract.all_events():
288            logger.debug(
289                "Registered event %s with hash %s", event.signature, event.topic
290            )
291
292    def http_client(self) -> GolemBaseHttpClient:
293        """Get the underlying web3 http client."""
294        return self._http_client
295
296    def ws_client(self) -> AsyncWeb3:
297        """Get the underlying web3 websocket client."""
298        return self._ws_client
299
300    async def is_connected(self) -> bool:
301        """Check whether the client's underlying http client is connected."""
302        return cast(bool, await self.http_client().is_connected())  # type: ignore[redundant-cast]
303
304    async def disconnect(self) -> None:
305        """
306        Disconnect this client.
307
308        this method disconnects both the underlying http and ws clients and
309        unsubscribes from all subscriptions.
310        """
311        await self.http_client().provider.disconnect()
312        await self.ws_client().subscription_manager.unsubscribe_all()
313        await self.ws_client().provider.disconnect()
314
315    async def get_storage_value(self, entity_key: EntityKey) -> bytes:
316        """Get the storage value stored in the given entity."""
317        return await self.http_client().get_storage_value(entity_key)
318
319    async def get_entity_metadata(self, entity_key: EntityKey) -> EntityMetadata:
320        """Get the metadata of the given entity."""
321        return await self.http_client().get_entity_metadata(entity_key)
322
323    async def get_entities_to_expire_at_block(
324        self, block_number: int
325    ) -> Sequence[EntityKey]:
326        """Get all entities that will expire at the given block."""
327        return await self.http_client().get_entities_to_expire_at_block(block_number)
328
329    async def get_entity_count(self) -> int:
330        """Get the total entity count in Golem Base."""
331        return await self.http_client().get_entity_count()
332
333    async def get_all_entity_keys(self) -> Sequence[EntityKey]:
334        """Get all entity keys in Golem Base."""
335        return await self.http_client().get_all_entity_keys()
336
337    async def get_entities_of_owner(
338        self, owner: ChecksumAddress
339    ) -> Sequence[EntityKey]:
340        """Get all the entities owned by the given address."""
341        return await self.http_client().get_entities_of_owner(owner)
342
343    async def query_entities(self, query: str) -> Sequence[QueryEntitiesResult]:
344        """Get all entities that satisfy the given Golem Base query."""
345        return await self.http_client().query_entities(query)
346
347    async def watch_logs(
348        self,
349        *,
350        label: str,
351        create_callback: Callable[[CreateEntityReturnType], None] | None = None,
352        update_callback: Callable[[UpdateEntityReturnType], None] | None = None,
353        delete_callback: Callable[[EntityKey], None] | None = None,
354        extend_callback: Callable[[ExtendEntityReturnType], None] | None = None,
355    ) -> WatchLogsHandle:
356        """
357        Subscribe to events on Golem Base.
358
359        You can pass in four different callbacks, and the right one will
360        be invoked for every create, update, delete, and extend operation.
361        """
362
363        async def log_handler(
364            handler_context: LogsSubscriptionContext,
365        ) -> None:
366            # We only use this handler for log receipts
367            # TypeDicts cannot be checked at runtime
368            log_receipt = typing.cast(LogReceipt, handler_context.result)
369            logger.debug("New log: %s", log_receipt)
370            res = await self._process_golem_base_log_receipt(log_receipt)
371
372            if create_callback:
373                for create in res.creates:
374                    create_callback(create)
375            if update_callback:
376                for update in res.updates:
377                    update_callback(update)
378            if delete_callback:
379                for key in res.deletes:
380                    delete_callback(key)
381            if extend_callback:
382                for extension in res.extensions:
383                    extend_callback(extension)
384
385        def create_subscription(topic: HexStr) -> LogsSubscription:
386            return LogsSubscription(
387                label=f"Golem Base subscription to topic {topic} with label {label}",
388                address=self.golem_base_contract.address,
389                topics=[topic],
390                handler=log_handler,
391                # optional `handler_context` args to help parse a response
392                handler_context={},
393            )
394
395        event_names = []
396        if create_callback:
397            event_names.append("GolemBaseStorageEntityCreated")
398        if update_callback:
399            event_names.append("GolemBaseStorageEntityUpdated")
400        if delete_callback:
401            event_names.append("GolemBaseStorageEntityDeleted")
402        if extend_callback:
403            event_names.append("GolemBaseStorageEntityBTLExtended")
404
405        events = list(
406            map(
407                lambda event_name: create_subscription(
408                    self.golem_base_contract.get_event_by_name(event_name).topic
409                ),
410                event_names,
411            )
412        )
413        subscription_ids = await self._ws_client.subscription_manager.subscribe(
414            events,
415        )
416        logger.info("Sub ID: %s", subscription_ids)
417
418        # Start a subscription loop in case there is none running
419        await self._start_subscription_loop()
420
421        async def unsubscribe() -> None:
422            await self._ws_client.subscription_manager.unsubscribe(subscription_ids)
423
424        return WatchLogsHandle(_unsubscribe=unsubscribe)
425
426    async def _start_subscription_loop(self) -> None:
427        """Create a long running task to handle subscriptions."""
428        # The loop will finish when there are no subscriptions left, so this method
429        # gets called every time a subscription is created, and we'll check
430        # whether we need to make a new task or whether one is already running.
431        if not self._background_tasks:
432            # Start the asyncio event loop
433            task = asyncio.create_task(
434                self.ws_client().subscription_manager.handle_subscriptions()
435            )
436            self._background_tasks.add(task)
437
438            def task_done(task: asyncio.Task[None]) -> None:
439                logger.info("Subscription background task done, removing...")
440                self._background_tasks.discard(task)
441
442            task.add_done_callback(task_done)
443
444    async def _process_golem_base_log_receipt(
445        self,
446        log_receipt: LogReceipt,
447    ) -> GolemBaseTransactionReceipt:
448        # Read the first entry of the topics array,
449        # which is the hash of the event signature, identifying the event
450        topic = AsyncWeb3.to_hex(log_receipt["topics"][0])
451        # Look up the corresponding event
452        # If there is no such event in the ABI, it probably needs to be added
453        event = self.golem_base_contract.get_event_by_topic(topic)
454        # Use the event to process the whole log
455        event_data = event.process_log(log_receipt)
456
457        creates: list[CreateEntityReturnType] = []
458        updates: list[UpdateEntityReturnType] = []
459        deletes: list[EntityKey] = []
460        extensions: list[ExtendEntityReturnType] = []
461
462        match event_data["event"]:
463            case "GolemBaseStorageEntityCreated":
464                creates.append(
465                    CreateEntityReturnType(
466                        expiration_block=event_data["args"]["expirationBlock"],
467                        entity_key=EntityKey(
468                            GenericBytes(
469                                event_data["args"]["entityKey"].to_bytes(32, "big")
470                            )
471                        ),
472                    )
473                )
474            case "GolemBaseStorageEntityUpdated":
475                updates.append(
476                    UpdateEntityReturnType(
477                        expiration_block=event_data["args"]["expirationBlock"],
478                        entity_key=EntityKey(
479                            GenericBytes(
480                                event_data["args"]["entityKey"].to_bytes(32, "big")
481                            )
482                        ),
483                    )
484                )
485            case "GolemBaseStorageEntityDeleted":
486                deletes.append(
487                    EntityKey(
488                        GenericBytes(
489                            event_data["args"]["entityKey"].to_bytes(32, "big")
490                        ),
491                    )
492                )
493            case "GolemBaseStorageEntityBTLExtended":
494                extensions.append(
495                    ExtendEntityReturnType(
496                        old_expiration_block=event_data["args"]["oldExpirationBlock"],
497                        new_expiration_block=event_data["args"]["newExpirationBlock"],
498                        entity_key=EntityKey(
499                            GenericBytes(
500                                event_data["args"]["entityKey"].to_bytes(32, "big")
501                            )
502                        ),
503                    )
504                )
505
506        return GolemBaseTransactionReceipt(
507            creates=creates,
508            updates=updates,
509            deletes=deletes,
510            extensions=extensions,
511        )
512
513    async def _process_golem_base_receipt(
514        self, receipt: TxReceipt
515    ) -> GolemBaseTransactionReceipt:
516        # There doesn't seem to be a method for this in the web3 lib.
517        # The only option in the lib is to iterate over the events in the ABI
518        # and call process_receipt on each of them to try and decode the logs.
519        # This is inefficient though compared to reading the actual topic signature
520        # and immediately selecting the right event from the ABI, which is what
521        # we do here.
522        async def process_receipt(
523            receipt: TxReceipt,
524        ) -> AsyncGenerator[GolemBaseTransactionReceipt, None]:
525            for log in receipt["logs"]:
526                yield await self._process_golem_base_log_receipt(log)
527
528        creates: list[CreateEntityReturnType] = []
529        updates: list[UpdateEntityReturnType] = []
530        deletes: list[EntityKey] = []
531        extensions: list[ExtendEntityReturnType] = []
532
533        async for res in process_receipt(receipt):
534            creates.extend(res.creates)
535            updates.extend(res.updates)
536            deletes.extend(res.deletes)
537            extensions.extend(res.extensions)
538
539        return GolemBaseTransactionReceipt(
540            creates=creates,
541            updates=updates,
542            deletes=deletes,
543            extensions=extensions,
544        )
545
546
547class GolemBaseClient(GolemBaseROClient):
548    """
549    The Golem Base client used to interact with Golem Base.
550
551    Many useful methods are implemented directly on this type, while more
552    generic ethereum methods can be accessed through the underlying
553    web3 client that you can access with the
554    `GolemBaseClient.http_client()`
555    method.
556    """
557
558    @staticmethod
559    async def create_rw_client(
560        rpc_url: str, ws_url: str, private_key: bytes
561    ) -> "GolemBaseClient":
562        """
563        Create a read-write Golem Base client.
564
565        This is the preferred method to create an instance.
566        """
567        return GolemBaseClient(
568            rpc_url, await GolemBaseROClient._create_ws_client(ws_url), private_key
569        )
570
571    @staticmethod
572    async def create(
573        rpc_url: str, ws_url: str, private_key: bytes
574    ) -> "GolemBaseClient":
575        """
576        Create a read-write Golem Base client.
577
578        This method is deprecated in favour of `GolemBaseClient.create_rw_client()`.
579        """
580        return await GolemBaseClient.create_rw_client(rpc_url, ws_url, private_key)
581
582    def __init__(self, rpc_url: str, ws_client: AsyncWeb3, private_key: bytes) -> None:
583        """Initialise the GolemBaseClient instance."""
584        super().__init__(rpc_url, ws_client)
585
586        # Set up the ethereum account
587        self.account = self.http_client().eth.account.from_key(private_key)
588        # Inject a middleware that will sign transactions with the account that
589        # we created
590        self.http_client().middleware_onion.inject(
591            # pylint doesn't detect nested @curry annotations properly...
592            # pylint: disable=no-value-for-parameter
593            SignAndSendRawMiddlewareBuilder.build(self.account),
594            layer=0,
595        )
596        # Set the account as the default, so we don't need to specify the from field
597        # every time
598        self.http_client().eth.default_account = self.account.address
599        logger.debug("Using account: %s", self.account.address)
600
601    def get_account_address(self) -> ChecksumAddress:
602        """Get the address associated with the private key of this client."""
603        return cast(ChecksumAddress, self.account.address)
604
605    async def create_entities(
606        self,
607        creates: Sequence[GolemBaseCreate],
608        *,
609        gas: int | None = None,
610        maxFeePerGas: Wei | None = None,
611        maxPriorityFeePerGas: Wei | None = None,
612    ) -> Sequence[CreateEntityReturnType]:
613        """Create entities in Golem Base."""
614        return (
615            await self.send_transaction(
616                creates=creates,
617                gas=gas,
618                maxFeePerGas=maxFeePerGas,
619                maxPriorityFeePerGas=maxPriorityFeePerGas,
620            )
621        ).creates
622
623    async def update_entities(
624        self,
625        updates: Sequence[GolemBaseUpdate],
626        *,
627        gas: int | None = None,
628        maxFeePerGas: Wei | None = None,
629        maxPriorityFeePerGas: Wei | None = None,
630    ) -> Sequence[UpdateEntityReturnType]:
631        """Update entities in Golem Base."""
632        return (
633            await self.send_transaction(
634                updates=updates,
635                gas=gas,
636                maxFeePerGas=maxFeePerGas,
637                maxPriorityFeePerGas=maxPriorityFeePerGas,
638            )
639        ).updates
640
641    async def delete_entities(
642        self,
643        deletes: Sequence[GolemBaseDelete],
644        *,
645        gas: int | None = None,
646        maxFeePerGas: Wei | None = None,
647        maxPriorityFeePerGas: Wei | None = None,
648    ) -> Sequence[EntityKey]:
649        """Delete entities from Golem Base."""
650        return (
651            await self.send_transaction(
652                deletes=deletes,
653                gas=gas,
654                maxFeePerGas=maxFeePerGas,
655                maxPriorityFeePerGas=maxPriorityFeePerGas,
656            )
657        ).deletes
658
659    async def extend_entities(
660        self,
661        extensions: Sequence[GolemBaseExtend],
662        *,
663        gas: int | None = None,
664        maxFeePerGas: Wei | None = None,
665        maxPriorityFeePerGas: Wei | None = None,
666    ) -> Sequence[ExtendEntityReturnType]:
667        """Extend the BTL of entities in Golem Base."""
668        return (
669            await self.send_transaction(
670                extensions=extensions,
671                gas=gas,
672                maxFeePerGas=maxFeePerGas,
673                maxPriorityFeePerGas=maxPriorityFeePerGas,
674            )
675        ).extensions
676
677    async def send_transaction(
678        self,
679        *,
680        creates: Sequence[GolemBaseCreate] | None = None,
681        updates: Sequence[GolemBaseUpdate] | None = None,
682        deletes: Sequence[GolemBaseDelete] | None = None,
683        extensions: Sequence[GolemBaseExtend] | None = None,
684        gas: int | None = None,
685        maxFeePerGas: Wei | None = None,
686        maxPriorityFeePerGas: Wei | None = None,
687    ) -> GolemBaseTransactionReceipt:
688        """
689        Send a generic transaction to Golem Base.
690
691        This transaction can contain multiple create, update, delete and
692        extend operations.
693        """
694        tx = GolemBaseTransaction(
695            creates=creates,
696            updates=updates,
697            deletes=deletes,
698            extensions=extensions,
699            gas=gas,
700            maxFeePerGas=maxFeePerGas,
701            maxPriorityFeePerGas=maxPriorityFeePerGas,
702        )
703        return await self._send_gb_transaction(tx)
704
705    async def _send_gb_transaction(
706        self, tx: GolemBaseTransaction
707    ) -> GolemBaseTransactionReceipt:
708        txData: TxParams = {
709            # https://github.com/pylint-dev/pylint/issues/3162
710            # pylint: disable=no-member
711            "to": STORAGE_ADDRESS.as_address(),
712            "value": AsyncWeb3.to_wei(0, "ether"),
713            "data": rlp_encode_transaction(tx),
714        }
715
716        if tx.gas:
717            txData |= {"gas": tx.gas}
718        if tx.maxFeePerGas:
719            txData |= {"maxFeePerGas": tx.maxFeePerGas}
720        if tx.maxPriorityFeePerGas:
721            txData |= {"maxPriorityFeePerGas": tx.maxPriorityFeePerGas}
722
723        txhash = await self.http_client().eth.send_transaction(txData)
724        receipt = await self.http_client().eth.wait_for_transaction_receipt(txhash)
725
726        # If we get a receipt and the transaction was failed, we run the same
727        # transaction with eth_call, which will simulate it and get us back the
728        # error that was reported by geth.
729        # Otherwise the error is not actually present in the receipt and so we
730        # don't have something useful to present to the user.
731        # This only happens when the gas price was explicitly provided, since
732        # otherwise there will be a call to eth_estimateGas, which will fail with
733        # the same error message that we would get here (and so we'll never actually
734        # get to submitting the transaction).
735        # The status in the receipt is either 0x0 for failed or 0x1 for success.
736        if not int(receipt["status"]):
737            # This call will lead to an exception, but that's OK, what we want
738            # is to raise a useful exception to the user with an error message.
739            try:
740                await self.http_client().eth.call(txData)
741            except Web3RPCError as e:
742                if e.rpc_response:
743                    error = e.rpc_response["error"]["message"]
744                    raise Exception(
745                        f"Error while processing transaction: {error}"
746                    ) from e
747                else:
748                    raise e
749
750        return await self._process_golem_base_receipt(receipt)
Address = Address
@dataclass(frozen=True)
class Annotation(typing.Generic[~V]):
54@dataclass(frozen=True)
55class Annotation(Generic[V]):
56    """Class to represent generic annotations."""
57
58    key: str
59    value: V
60
61    # @override
62    def __repr__(self) -> str:
63        """Encode annotation as a string."""
64        return f"{type(self).__name__}({self.key} -> {self.value})"

Class to represent generic annotations.

Annotation(key: str, value: ~V)
key: str
value: ~V
@dataclass(frozen=True)
class CreateEntityReturnType:
145@dataclass(frozen=True)
146class CreateEntityReturnType:
147    """The return type of a Golem Base create operation."""
148
149    expiration_block: int
150    entity_key: EntityKey

The return type of a Golem Base create operation.

CreateEntityReturnType(expiration_block: int, entity_key: EntityKey)
expiration_block: int
entity_key: EntityKey
EntityKey = EntityKey
@dataclass(frozen=True)
class EntityMetadata:
180@dataclass(frozen=True)
181class EntityMetadata:
182    """A class representing entity metadata."""
183
184    entity_key: EntityKey
185    owner: Address
186    expires_at_block: int
187    string_annotations: Sequence[Annotation[str]]
188    numeric_annotations: Sequence[Annotation[int]]

A class representing entity metadata.

EntityMetadata( entity_key: EntityKey, owner: Address, expires_at_block: int, string_annotations: Sequence[Annotation[str]], numeric_annotations: Sequence[Annotation[int]])
entity_key: EntityKey
owner: Address
expires_at_block: int
string_annotations: Sequence[Annotation[str]]
numeric_annotations: Sequence[Annotation[int]]
@dataclass(frozen=True)
class ExtendEntityReturnType:
161@dataclass(frozen=True)
162class ExtendEntityReturnType:
163    """The return type of a Golem Base extend operation."""
164
165    old_expiration_block: int
166    new_expiration_block: int
167    entity_key: EntityKey

The return type of a Golem Base extend operation.

ExtendEntityReturnType( old_expiration_block: int, new_expiration_block: int, entity_key: EntityKey)
old_expiration_block: int
new_expiration_block: int
entity_key: EntityKey
@dataclass(frozen=True)
class GenericBytes:
18@dataclass(frozen=True)
19class GenericBytes:
20    """Class to represent bytes that can be converted to more meaningful types."""
21
22    generic_bytes: bytes
23
24    def as_hex_string(self) -> HexStr:
25        """Convert this instance to a hexadecimal string."""
26        return HexStr("0x" + self.generic_bytes.hex())
27
28    def as_address(self) -> ChecksumAddress:
29        """Convert this instance to a `eth_typing.ChecksumAddress`."""
30        return AsyncWeb3.to_checksum_address(self.as_hex_string())
31
32    # @override
33    def __repr__(self) -> str:
34        """Encode bytes as a string."""
35        return f"{type(self).__name__}({self.as_hex_string()})"
36
37    @staticmethod
38    def from_hex_string(hexstr: str) -> "GenericBytes":
39        """Create a `GenericBytes` instance from a hexadecimal string."""
40        assert hexstr.startswith("0x")
41        assert len(hexstr) % 2 == 0
42
43        return GenericBytes(bytes.fromhex(hexstr[2:]))

Class to represent bytes that can be converted to more meaningful types.

GenericBytes(generic_bytes: bytes)
generic_bytes: bytes
def as_hex_string(self) -> eth_typing.encoding.HexStr:
24    def as_hex_string(self) -> HexStr:
25        """Convert this instance to a hexadecimal string."""
26        return HexStr("0x" + self.generic_bytes.hex())

Convert this instance to a hexadecimal string.

def as_address(self) -> eth_typing.evm.ChecksumAddress:
28    def as_address(self) -> ChecksumAddress:
29        """Convert this instance to a `eth_typing.ChecksumAddress`."""
30        return AsyncWeb3.to_checksum_address(self.as_hex_string())

Convert this instance to a eth_typing.ChecksumAddress.

@staticmethod
def from_hex_string(hexstr: str) -> GenericBytes:
37    @staticmethod
38    def from_hex_string(hexstr: str) -> "GenericBytes":
39        """Create a `GenericBytes` instance from a hexadecimal string."""
40        assert hexstr.startswith("0x")
41        assert len(hexstr) % 2 == 0
42
43        return GenericBytes(bytes.fromhex(hexstr[2:]))

Create a GenericBytes instance from a hexadecimal string.

@dataclass(frozen=True)
class GolemBaseCreate:
67@dataclass(frozen=True)
68class GolemBaseCreate:
69    """Class to represent a create operation in Golem Base."""
70
71    data: bytes
72    btl: int
73    string_annotations: Sequence[Annotation[str]]
74    numeric_annotations: Sequence[Annotation[int]]

Class to represent a create operation in Golem Base.

GolemBaseCreate( data: bytes, btl: int, string_annotations: Sequence[Annotation[str]], numeric_annotations: Sequence[Annotation[int]])
data: bytes
btl: int
string_annotations: Sequence[Annotation[str]]
numeric_annotations: Sequence[Annotation[int]]
@dataclass(frozen=True)
class GolemBaseDelete:
88@dataclass(frozen=True)
89class GolemBaseDelete:
90    """Class to represent a delete operation in Golem Base."""
91
92    entity_key: EntityKey

Class to represent a delete operation in Golem Base.

GolemBaseDelete(entity_key: EntityKey)
entity_key: EntityKey
@dataclass(frozen=True)
class GolemBaseExtend:
 95@dataclass(frozen=True)
 96class GolemBaseExtend:
 97    """Class to represent a BTL extend operation in Golem Base."""
 98
 99    entity_key: EntityKey
100    number_of_blocks: int

Class to represent a BTL extend operation in Golem Base.

GolemBaseExtend(entity_key: EntityKey, number_of_blocks: int)
entity_key: EntityKey
number_of_blocks: int
@dataclass(frozen=True)
class GolemBaseTransaction:
103@dataclass(frozen=True)
104class GolemBaseTransaction:
105    """
106    Class to represent a transaction in Golem Base.
107
108    A transaction consist of one or more
109    `GolemBaseCreate`,
110    `GolemBaseUpdate`,
111    `GolemBaseDelete` and
112    `GolemBaseExtend`
113    operations.
114    """
115
116    def __init__(
117        self,
118        *,
119        creates: Sequence[GolemBaseCreate] | None = None,
120        updates: Sequence[GolemBaseUpdate] | None = None,
121        deletes: Sequence[GolemBaseDelete] | None = None,
122        extensions: Sequence[GolemBaseExtend] | None = None,
123        gas: int | None = None,
124        maxFeePerGas: Wei | None = None,
125        maxPriorityFeePerGas: Wei | None = None,
126    ):
127        """Initialise the GolemBaseTransaction instance."""
128        object.__setattr__(self, "creates", creates or [])
129        object.__setattr__(self, "updates", updates or [])
130        object.__setattr__(self, "deletes", deletes or [])
131        object.__setattr__(self, "extensions", extensions or [])
132        object.__setattr__(self, "gas", gas)
133        object.__setattr__(self, "maxFeePerGas", maxFeePerGas)
134        object.__setattr__(self, "maxPriorityFeePerGas", maxPriorityFeePerGas)
135
136    creates: Sequence[GolemBaseCreate]
137    updates: Sequence[GolemBaseUpdate]
138    deletes: Sequence[GolemBaseDelete]
139    extensions: Sequence[GolemBaseExtend]
140    gas: int | None
141    maxFeePerGas: Wei | None
142    maxPriorityFeePerGas: Wei | None

Class to represent a transaction in Golem Base.

A transaction consist of one or more GolemBaseCreate, GolemBaseUpdate, GolemBaseDelete and GolemBaseExtend operations.

GolemBaseTransaction( *, creates: Sequence[GolemBaseCreate] | None = None, updates: Sequence[GolemBaseUpdate] | None = None, deletes: Sequence[GolemBaseDelete] | None = None, extensions: Sequence[GolemBaseExtend] | None = None, gas: int | None = None, maxFeePerGas: Optional[web3.types.Wei] = None, maxPriorityFeePerGas: Optional[web3.types.Wei] = None)
116    def __init__(
117        self,
118        *,
119        creates: Sequence[GolemBaseCreate] | None = None,
120        updates: Sequence[GolemBaseUpdate] | None = None,
121        deletes: Sequence[GolemBaseDelete] | None = None,
122        extensions: Sequence[GolemBaseExtend] | None = None,
123        gas: int | None = None,
124        maxFeePerGas: Wei | None = None,
125        maxPriorityFeePerGas: Wei | None = None,
126    ):
127        """Initialise the GolemBaseTransaction instance."""
128        object.__setattr__(self, "creates", creates or [])
129        object.__setattr__(self, "updates", updates or [])
130        object.__setattr__(self, "deletes", deletes or [])
131        object.__setattr__(self, "extensions", extensions or [])
132        object.__setattr__(self, "gas", gas)
133        object.__setattr__(self, "maxFeePerGas", maxFeePerGas)
134        object.__setattr__(self, "maxPriorityFeePerGas", maxPriorityFeePerGas)

Initialise the GolemBaseTransaction instance.

creates: Sequence[GolemBaseCreate]
updates: Sequence[GolemBaseUpdate]
deletes: Sequence[GolemBaseDelete]
extensions: Sequence[GolemBaseExtend]
gas: int | None
maxFeePerGas: Optional[web3.types.Wei]
maxPriorityFeePerGas: Optional[web3.types.Wei]
@dataclass(frozen=True)
class GolemBaseTransactionReceipt:
170@dataclass(frozen=True)
171class GolemBaseTransactionReceipt:
172    """The return type of a Golem Base transaction."""
173
174    creates: Sequence[CreateEntityReturnType]
175    updates: Sequence[UpdateEntityReturnType]
176    extensions: Sequence[ExtendEntityReturnType]
177    deletes: Sequence[EntityKey]

The return type of a Golem Base transaction.

GolemBaseTransactionReceipt( creates: Sequence[CreateEntityReturnType], updates: Sequence[UpdateEntityReturnType], extensions: Sequence[ExtendEntityReturnType], deletes: Sequence[EntityKey])
creates: Sequence[CreateEntityReturnType]
updates: Sequence[UpdateEntityReturnType]
extensions: Sequence[ExtendEntityReturnType]
deletes: Sequence[EntityKey]
@dataclass(frozen=True)
class GolemBaseUpdate:
77@dataclass(frozen=True)
78class GolemBaseUpdate:
79    """Class to represent an update operation in Golem Base."""
80
81    entity_key: EntityKey
82    data: bytes
83    btl: int
84    string_annotations: Sequence[Annotation[str]]
85    numeric_annotations: Sequence[Annotation[int]]

Class to represent an update operation in Golem Base.

GolemBaseUpdate( entity_key: EntityKey, data: bytes, btl: int, string_annotations: Sequence[Annotation[str]], numeric_annotations: Sequence[Annotation[int]])
entity_key: EntityKey
data: bytes
btl: int
string_annotations: Sequence[Annotation[str]]
numeric_annotations: Sequence[Annotation[int]]
@dataclass(frozen=True)
class QueryEntitiesResult:
191@dataclass(frozen=True)
192class QueryEntitiesResult:
193    """A class representing the return value of a Golem Base query."""
194
195    entity_key: EntityKey
196    storage_value: bytes

A class representing the return value of a Golem Base query.

QueryEntitiesResult(entity_key: EntityKey, storage_value: bytes)
entity_key: EntityKey
storage_value: bytes
@dataclass(frozen=True)
class UpdateEntityReturnType:
153@dataclass(frozen=True)
154class UpdateEntityReturnType:
155    """The return type of a Golem Base update operation."""
156
157    expiration_block: int
158    entity_key: EntityKey

The return type of a Golem Base update operation.

UpdateEntityReturnType(expiration_block: int, entity_key: EntityKey)
expiration_block: int
entity_key: EntityKey
@dataclass(frozen=True)
class WatchLogsHandle:
199@dataclass(frozen=True)
200class WatchLogsHandle:
201    """
202    Class returned by `GolemBaseClient.watch_logs`.
203
204    Allows you to unsubscribe from the associated subscription.
205    """
206
207    _unsubscribe: Callable[[], Coroutine[Any, Any, None]]
208
209    async def unsubscribe(self) -> None:
210        """Unsubscribe from this subscription."""
211        await self._unsubscribe()

Class returned by GolemBaseClient.watch_logs.

Allows you to unsubscribe from the associated subscription.

WatchLogsHandle(_unsubscribe: Callable[[], Coroutine[typing.Any, typing.Any, None]])
async def unsubscribe(self) -> None:
209    async def unsubscribe(self) -> None:
210        """Unsubscribe from this subscription."""
211        await self._unsubscribe()

Unsubscribe from this subscription.

GOLEM_BASE_ABI = [{'anonymous': False, 'inputs': [{'indexed': True, 'name': 'entityKey', 'type': 'uint256'}, {'indexed': False, 'name': 'expirationBlock', 'type': 'uint256'}], 'name': 'GolemBaseStorageEntityCreated', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': True, 'name': 'entityKey', 'type': 'uint256'}, {'indexed': False, 'name': 'expirationBlock', 'type': 'uint256'}], 'name': 'GolemBaseStorageEntityUpdated', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': True, 'name': 'entityKey', 'type': 'uint256'}], 'name': 'GolemBaseStorageEntityDeleted', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': True, 'name': 'entityKey', 'type': 'uint256'}, {'indexed': False, 'name': 'oldExpirationBlock', 'type': 'uint256'}, {'indexed': False, 'name': 'newExpirationBlock', 'type': 'uint256'}], 'name': 'GolemBaseStorageEntityBTLExtended', 'type': 'event'}]
STORAGE_ADDRESS = GenericBytes(0x0000000000000000000000000000000060138453)
async def decrypt_wallet() -> bytes:
21async def decrypt_wallet() -> bytes:
22    """Decrypts the wallet and returns the private key bytes."""
23    if not WALLET_PATH.exists():
24        raise WalletError(f"Expected wallet file to exist at '{WALLET_PATH}'")
25
26    async with await anyio.open_file(
27        WALLET_PATH,
28        "r",
29    ) as f:
30        keyfile_json = json.loads(await f.read())
31
32        if not sys.stdin.isatty():
33            password = sys.stdin.read().rstrip()
34        else:
35            password = getpass.getpass("Enter password to decrypt wallet: ")
36
37        try:
38            print(f"Attempting to decrypt wallet at '{WALLET_PATH}'")
39            private_key = Account.decrypt(keyfile_json, password)
40            print("Successfully decrypted wallet")
41        except ValueError as e:
42            raise WalletError("Incorrect password or corrupted wallet file.") from e
43
44        return cast(bytes, private_key)

Decrypts the wallet and returns the private key bytes.

class WalletError(builtins.Exception):
16class WalletError(Exception):
17    """Base class for wallet-related errors."""
18
19    pass

Base class for wallet-related errors.

class GolemBaseClient(GolemBaseROClient):
548class GolemBaseClient(GolemBaseROClient):
549    """
550    The Golem Base client used to interact with Golem Base.
551
552    Many useful methods are implemented directly on this type, while more
553    generic ethereum methods can be accessed through the underlying
554    web3 client that you can access with the
555    `GolemBaseClient.http_client()`
556    method.
557    """
558
559    @staticmethod
560    async def create_rw_client(
561        rpc_url: str, ws_url: str, private_key: bytes
562    ) -> "GolemBaseClient":
563        """
564        Create a read-write Golem Base client.
565
566        This is the preferred method to create an instance.
567        """
568        return GolemBaseClient(
569            rpc_url, await GolemBaseROClient._create_ws_client(ws_url), private_key
570        )
571
572    @staticmethod
573    async def create(
574        rpc_url: str, ws_url: str, private_key: bytes
575    ) -> "GolemBaseClient":
576        """
577        Create a read-write Golem Base client.
578
579        This method is deprecated in favour of `GolemBaseClient.create_rw_client()`.
580        """
581        return await GolemBaseClient.create_rw_client(rpc_url, ws_url, private_key)
582
583    def __init__(self, rpc_url: str, ws_client: AsyncWeb3, private_key: bytes) -> None:
584        """Initialise the GolemBaseClient instance."""
585        super().__init__(rpc_url, ws_client)
586
587        # Set up the ethereum account
588        self.account = self.http_client().eth.account.from_key(private_key)
589        # Inject a middleware that will sign transactions with the account that
590        # we created
591        self.http_client().middleware_onion.inject(
592            # pylint doesn't detect nested @curry annotations properly...
593            # pylint: disable=no-value-for-parameter
594            SignAndSendRawMiddlewareBuilder.build(self.account),
595            layer=0,
596        )
597        # Set the account as the default, so we don't need to specify the from field
598        # every time
599        self.http_client().eth.default_account = self.account.address
600        logger.debug("Using account: %s", self.account.address)
601
602    def get_account_address(self) -> ChecksumAddress:
603        """Get the address associated with the private key of this client."""
604        return cast(ChecksumAddress, self.account.address)
605
606    async def create_entities(
607        self,
608        creates: Sequence[GolemBaseCreate],
609        *,
610        gas: int | None = None,
611        maxFeePerGas: Wei | None = None,
612        maxPriorityFeePerGas: Wei | None = None,
613    ) -> Sequence[CreateEntityReturnType]:
614        """Create entities in Golem Base."""
615        return (
616            await self.send_transaction(
617                creates=creates,
618                gas=gas,
619                maxFeePerGas=maxFeePerGas,
620                maxPriorityFeePerGas=maxPriorityFeePerGas,
621            )
622        ).creates
623
624    async def update_entities(
625        self,
626        updates: Sequence[GolemBaseUpdate],
627        *,
628        gas: int | None = None,
629        maxFeePerGas: Wei | None = None,
630        maxPriorityFeePerGas: Wei | None = None,
631    ) -> Sequence[UpdateEntityReturnType]:
632        """Update entities in Golem Base."""
633        return (
634            await self.send_transaction(
635                updates=updates,
636                gas=gas,
637                maxFeePerGas=maxFeePerGas,
638                maxPriorityFeePerGas=maxPriorityFeePerGas,
639            )
640        ).updates
641
642    async def delete_entities(
643        self,
644        deletes: Sequence[GolemBaseDelete],
645        *,
646        gas: int | None = None,
647        maxFeePerGas: Wei | None = None,
648        maxPriorityFeePerGas: Wei | None = None,
649    ) -> Sequence[EntityKey]:
650        """Delete entities from Golem Base."""
651        return (
652            await self.send_transaction(
653                deletes=deletes,
654                gas=gas,
655                maxFeePerGas=maxFeePerGas,
656                maxPriorityFeePerGas=maxPriorityFeePerGas,
657            )
658        ).deletes
659
660    async def extend_entities(
661        self,
662        extensions: Sequence[GolemBaseExtend],
663        *,
664        gas: int | None = None,
665        maxFeePerGas: Wei | None = None,
666        maxPriorityFeePerGas: Wei | None = None,
667    ) -> Sequence[ExtendEntityReturnType]:
668        """Extend the BTL of entities in Golem Base."""
669        return (
670            await self.send_transaction(
671                extensions=extensions,
672                gas=gas,
673                maxFeePerGas=maxFeePerGas,
674                maxPriorityFeePerGas=maxPriorityFeePerGas,
675            )
676        ).extensions
677
678    async def send_transaction(
679        self,
680        *,
681        creates: Sequence[GolemBaseCreate] | None = None,
682        updates: Sequence[GolemBaseUpdate] | None = None,
683        deletes: Sequence[GolemBaseDelete] | None = None,
684        extensions: Sequence[GolemBaseExtend] | None = None,
685        gas: int | None = None,
686        maxFeePerGas: Wei | None = None,
687        maxPriorityFeePerGas: Wei | None = None,
688    ) -> GolemBaseTransactionReceipt:
689        """
690        Send a generic transaction to Golem Base.
691
692        This transaction can contain multiple create, update, delete and
693        extend operations.
694        """
695        tx = GolemBaseTransaction(
696            creates=creates,
697            updates=updates,
698            deletes=deletes,
699            extensions=extensions,
700            gas=gas,
701            maxFeePerGas=maxFeePerGas,
702            maxPriorityFeePerGas=maxPriorityFeePerGas,
703        )
704        return await self._send_gb_transaction(tx)
705
706    async def _send_gb_transaction(
707        self, tx: GolemBaseTransaction
708    ) -> GolemBaseTransactionReceipt:
709        txData: TxParams = {
710            # https://github.com/pylint-dev/pylint/issues/3162
711            # pylint: disable=no-member
712            "to": STORAGE_ADDRESS.as_address(),
713            "value": AsyncWeb3.to_wei(0, "ether"),
714            "data": rlp_encode_transaction(tx),
715        }
716
717        if tx.gas:
718            txData |= {"gas": tx.gas}
719        if tx.maxFeePerGas:
720            txData |= {"maxFeePerGas": tx.maxFeePerGas}
721        if tx.maxPriorityFeePerGas:
722            txData |= {"maxPriorityFeePerGas": tx.maxPriorityFeePerGas}
723
724        txhash = await self.http_client().eth.send_transaction(txData)
725        receipt = await self.http_client().eth.wait_for_transaction_receipt(txhash)
726
727        # If we get a receipt and the transaction was failed, we run the same
728        # transaction with eth_call, which will simulate it and get us back the
729        # error that was reported by geth.
730        # Otherwise the error is not actually present in the receipt and so we
731        # don't have something useful to present to the user.
732        # This only happens when the gas price was explicitly provided, since
733        # otherwise there will be a call to eth_estimateGas, which will fail with
734        # the same error message that we would get here (and so we'll never actually
735        # get to submitting the transaction).
736        # The status in the receipt is either 0x0 for failed or 0x1 for success.
737        if not int(receipt["status"]):
738            # This call will lead to an exception, but that's OK, what we want
739            # is to raise a useful exception to the user with an error message.
740            try:
741                await self.http_client().eth.call(txData)
742            except Web3RPCError as e:
743                if e.rpc_response:
744                    error = e.rpc_response["error"]["message"]
745                    raise Exception(
746                        f"Error while processing transaction: {error}"
747                    ) from e
748                else:
749                    raise e
750
751        return await self._process_golem_base_receipt(receipt)

The Golem Base client used to interact with Golem Base.

Many useful methods are implemented directly on this type, while more generic ethereum methods can be accessed through the underlying web3 client that you can access with the GolemBaseClient.http_client() method.

GolemBaseClient(rpc_url: str, ws_client: web3.main.AsyncWeb3, private_key: bytes)
583    def __init__(self, rpc_url: str, ws_client: AsyncWeb3, private_key: bytes) -> None:
584        """Initialise the GolemBaseClient instance."""
585        super().__init__(rpc_url, ws_client)
586
587        # Set up the ethereum account
588        self.account = self.http_client().eth.account.from_key(private_key)
589        # Inject a middleware that will sign transactions with the account that
590        # we created
591        self.http_client().middleware_onion.inject(
592            # pylint doesn't detect nested @curry annotations properly...
593            # pylint: disable=no-value-for-parameter
594            SignAndSendRawMiddlewareBuilder.build(self.account),
595            layer=0,
596        )
597        # Set the account as the default, so we don't need to specify the from field
598        # every time
599        self.http_client().eth.default_account = self.account.address
600        logger.debug("Using account: %s", self.account.address)

Initialise the GolemBaseClient instance.

@staticmethod
async def create_rw_client( rpc_url: str, ws_url: str, private_key: bytes) -> GolemBaseClient:
559    @staticmethod
560    async def create_rw_client(
561        rpc_url: str, ws_url: str, private_key: bytes
562    ) -> "GolemBaseClient":
563        """
564        Create a read-write Golem Base client.
565
566        This is the preferred method to create an instance.
567        """
568        return GolemBaseClient(
569            rpc_url, await GolemBaseROClient._create_ws_client(ws_url), private_key
570        )

Create a read-write Golem Base client.

This is the preferred method to create an instance.

@staticmethod
async def create( rpc_url: str, ws_url: str, private_key: bytes) -> GolemBaseClient:
572    @staticmethod
573    async def create(
574        rpc_url: str, ws_url: str, private_key: bytes
575    ) -> "GolemBaseClient":
576        """
577        Create a read-write Golem Base client.
578
579        This method is deprecated in favour of `GolemBaseClient.create_rw_client()`.
580        """
581        return await GolemBaseClient.create_rw_client(rpc_url, ws_url, private_key)

Create a read-write Golem Base client.

This method is deprecated in favour of GolemBaseClient.create_rw_client().

account
def get_account_address(self) -> eth_typing.evm.ChecksumAddress:
602    def get_account_address(self) -> ChecksumAddress:
603        """Get the address associated with the private key of this client."""
604        return cast(ChecksumAddress, self.account.address)

Get the address associated with the private key of this client.

async def create_entities( self, creates: Sequence[GolemBaseCreate], *, gas: int | None = None, maxFeePerGas: Optional[web3.types.Wei] = None, maxPriorityFeePerGas: Optional[web3.types.Wei] = None) -> Sequence[CreateEntityReturnType]:
606    async def create_entities(
607        self,
608        creates: Sequence[GolemBaseCreate],
609        *,
610        gas: int | None = None,
611        maxFeePerGas: Wei | None = None,
612        maxPriorityFeePerGas: Wei | None = None,
613    ) -> Sequence[CreateEntityReturnType]:
614        """Create entities in Golem Base."""
615        return (
616            await self.send_transaction(
617                creates=creates,
618                gas=gas,
619                maxFeePerGas=maxFeePerGas,
620                maxPriorityFeePerGas=maxPriorityFeePerGas,
621            )
622        ).creates

Create entities in Golem Base.

async def update_entities( self, updates: Sequence[GolemBaseUpdate], *, gas: int | None = None, maxFeePerGas: Optional[web3.types.Wei] = None, maxPriorityFeePerGas: Optional[web3.types.Wei] = None) -> Sequence[UpdateEntityReturnType]:
624    async def update_entities(
625        self,
626        updates: Sequence[GolemBaseUpdate],
627        *,
628        gas: int | None = None,
629        maxFeePerGas: Wei | None = None,
630        maxPriorityFeePerGas: Wei | None = None,
631    ) -> Sequence[UpdateEntityReturnType]:
632        """Update entities in Golem Base."""
633        return (
634            await self.send_transaction(
635                updates=updates,
636                gas=gas,
637                maxFeePerGas=maxFeePerGas,
638                maxPriorityFeePerGas=maxPriorityFeePerGas,
639            )
640        ).updates

Update entities in Golem Base.

async def delete_entities( self, deletes: Sequence[GolemBaseDelete], *, gas: int | None = None, maxFeePerGas: Optional[web3.types.Wei] = None, maxPriorityFeePerGas: Optional[web3.types.Wei] = None) -> Sequence[EntityKey]:
642    async def delete_entities(
643        self,
644        deletes: Sequence[GolemBaseDelete],
645        *,
646        gas: int | None = None,
647        maxFeePerGas: Wei | None = None,
648        maxPriorityFeePerGas: Wei | None = None,
649    ) -> Sequence[EntityKey]:
650        """Delete entities from Golem Base."""
651        return (
652            await self.send_transaction(
653                deletes=deletes,
654                gas=gas,
655                maxFeePerGas=maxFeePerGas,
656                maxPriorityFeePerGas=maxPriorityFeePerGas,
657            )
658        ).deletes

Delete entities from Golem Base.

async def extend_entities( self, extensions: Sequence[GolemBaseExtend], *, gas: int | None = None, maxFeePerGas: Optional[web3.types.Wei] = None, maxPriorityFeePerGas: Optional[web3.types.Wei] = None) -> Sequence[ExtendEntityReturnType]:
660    async def extend_entities(
661        self,
662        extensions: Sequence[GolemBaseExtend],
663        *,
664        gas: int | None = None,
665        maxFeePerGas: Wei | None = None,
666        maxPriorityFeePerGas: Wei | None = None,
667    ) -> Sequence[ExtendEntityReturnType]:
668        """Extend the BTL of entities in Golem Base."""
669        return (
670            await self.send_transaction(
671                extensions=extensions,
672                gas=gas,
673                maxFeePerGas=maxFeePerGas,
674                maxPriorityFeePerGas=maxPriorityFeePerGas,
675            )
676        ).extensions

Extend the BTL of entities in Golem Base.

async def send_transaction( self, *, creates: Sequence[GolemBaseCreate] | None = None, updates: Sequence[GolemBaseUpdate] | None = None, deletes: Sequence[GolemBaseDelete] | None = None, extensions: Sequence[GolemBaseExtend] | None = None, gas: int | None = None, maxFeePerGas: Optional[web3.types.Wei] = None, maxPriorityFeePerGas: Optional[web3.types.Wei] = None) -> GolemBaseTransactionReceipt:
678    async def send_transaction(
679        self,
680        *,
681        creates: Sequence[GolemBaseCreate] | None = None,
682        updates: Sequence[GolemBaseUpdate] | None = None,
683        deletes: Sequence[GolemBaseDelete] | None = None,
684        extensions: Sequence[GolemBaseExtend] | None = None,
685        gas: int | None = None,
686        maxFeePerGas: Wei | None = None,
687        maxPriorityFeePerGas: Wei | None = None,
688    ) -> GolemBaseTransactionReceipt:
689        """
690        Send a generic transaction to Golem Base.
691
692        This transaction can contain multiple create, update, delete and
693        extend operations.
694        """
695        tx = GolemBaseTransaction(
696            creates=creates,
697            updates=updates,
698            deletes=deletes,
699            extensions=extensions,
700            gas=gas,
701            maxFeePerGas=maxFeePerGas,
702            maxPriorityFeePerGas=maxPriorityFeePerGas,
703        )
704        return await self._send_gb_transaction(tx)

Send a generic transaction to Golem Base.

This transaction can contain multiple create, update, delete and extend operations.

Wei = web3.types.Wei