diff --git a/README.md b/README.md index 8609644..74ffa20 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ pip install cdp-sdk ``` if you prefer to manage dependencies with Poetry: + ```bash poetry add cdp-sdk ``` @@ -155,6 +156,12 @@ print(f"Faucet transaction successfully completed: {usdc_faucet_tx}") transfer = wallet1.transfer(0.00001, "usdc", wallet3, gasless=True).wait() ``` +By default, gasless transfers are batched with other transfers, and might take longer to submit. If you want to opt out of batching, you can set the `skip_batching` option to `True`, which will submit the transaction immediately. + +```python +transfer = wallet1.transfer(0.00001, "usdc", wallet3, gasless=True, skip_batching=True).wait() +``` + ### Listing Transfers ```python @@ -239,7 +246,9 @@ fetched_wallet.load_seed(file_path) ``` ### Creating a Webhook + A webhook is a way to provide other applications with real-time information from the blockchain. When an event occurs on a blockchain address, it can send a POST request to a URL you specify. You can create a webhook to receive notifications about events that occur in your wallet or crypto address, such as when a user makes a transfer. + ```python from cdp.client.models.webhook import WebhookEventType from cdp.client.models.webhook import WebhookEventFilter @@ -252,10 +261,13 @@ wh1 = Webhook.create( ) print(wh1) ``` + In the above example, parameter `network_id` is optional, if not provided, the default network is `base-sepolia`. Today we support Base mainnet and Base Sepolia networks. ### Creating a Webhook On A Wallet + A webhook can be attached to an existing wallet to monitor events that occur on the wallet, i.e. all addresses associated with this wallet. A list of supported blockchain events can be found [here](https://docs.cdp.coinbase.com/get-started/docs/webhooks/event-types). + ```python import cdp @@ -265,9 +277,9 @@ print(wh1) ``` ## Examples + Examples, demo apps, and further code samples can be found in the [CDP SDK Python Documentation](https://docs.cdp.coinbase.com/cdp-apis/docs/welcome). ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for more information. - diff --git a/cdp/client/__init__.py b/cdp/client/__init__.py index 68ff0a9..bf587b4 100644 --- a/cdp/client/__init__.py +++ b/cdp/client/__init__.py @@ -60,6 +60,7 @@ from cdp.client.models.asset import Asset from cdp.client.models.balance import Balance from cdp.client.models.broadcast_contract_invocation_request import BroadcastContractInvocationRequest +from cdp.client.models.broadcast_external_transfer_request import BroadcastExternalTransferRequest from cdp.client.models.broadcast_staking_operation_request import BroadcastStakingOperationRequest from cdp.client.models.broadcast_trade_request import BroadcastTradeRequest from cdp.client.models.broadcast_transfer_request import BroadcastTransferRequest @@ -70,6 +71,7 @@ from cdp.client.models.contract_invocation_list import ContractInvocationList from cdp.client.models.create_address_request import CreateAddressRequest from cdp.client.models.create_contract_invocation_request import CreateContractInvocationRequest +from cdp.client.models.create_external_transfer_request import CreateExternalTransferRequest from cdp.client.models.create_fund_operation_request import CreateFundOperationRequest from cdp.client.models.create_fund_quote_request import CreateFundQuoteRequest from cdp.client.models.create_payload_signature_request import CreatePayloadSignatureRequest diff --git a/cdp/client/api/external_addresses_api.py b/cdp/client/api/external_addresses_api.py index 857820f..18becfb 100644 --- a/cdp/client/api/external_addresses_api.py +++ b/cdp/client/api/external_addresses_api.py @@ -21,7 +21,10 @@ from typing_extensions import Annotated from cdp.client.models.address_balance_list import AddressBalanceList from cdp.client.models.balance import Balance +from cdp.client.models.broadcast_external_transfer_request import BroadcastExternalTransferRequest +from cdp.client.models.create_external_transfer_request import CreateExternalTransferRequest from cdp.client.models.faucet_transaction import FaucetTransaction +from cdp.client.models.transfer import Transfer from cdp.client.api_client import ApiClient, RequestSerialized from cdp.client.api_response import ApiResponse @@ -41,6 +44,629 @@ def __init__(self, api_client=None) -> None: self.api_client = api_client + @validate_call + def broadcast_external_transfer( + self, + network_id: Annotated[StrictStr, Field(description="The ID of the network the address belongs to")], + address_id: Annotated[StrictStr, Field(description="The ID of the address the transfer belongs to")], + transfer_id: Annotated[StrictStr, Field(description="The ID of the transfer to broadcast")], + broadcast_external_transfer_request: BroadcastExternalTransferRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Transfer: + """Broadcast an external address' transfer + + Broadcast an external address's transfer with a signed payload + + :param network_id: The ID of the network the address belongs to (required) + :type network_id: str + :param address_id: The ID of the address the transfer belongs to (required) + :type address_id: str + :param transfer_id: The ID of the transfer to broadcast (required) + :type transfer_id: str + :param broadcast_external_transfer_request: (required) + :type broadcast_external_transfer_request: BroadcastExternalTransferRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._broadcast_external_transfer_serialize( + network_id=network_id, + address_id=address_id, + transfer_id=transfer_id, + broadcast_external_transfer_request=broadcast_external_transfer_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Transfer", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def broadcast_external_transfer_with_http_info( + self, + network_id: Annotated[StrictStr, Field(description="The ID of the network the address belongs to")], + address_id: Annotated[StrictStr, Field(description="The ID of the address the transfer belongs to")], + transfer_id: Annotated[StrictStr, Field(description="The ID of the transfer to broadcast")], + broadcast_external_transfer_request: BroadcastExternalTransferRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Transfer]: + """Broadcast an external address' transfer + + Broadcast an external address's transfer with a signed payload + + :param network_id: The ID of the network the address belongs to (required) + :type network_id: str + :param address_id: The ID of the address the transfer belongs to (required) + :type address_id: str + :param transfer_id: The ID of the transfer to broadcast (required) + :type transfer_id: str + :param broadcast_external_transfer_request: (required) + :type broadcast_external_transfer_request: BroadcastExternalTransferRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._broadcast_external_transfer_serialize( + network_id=network_id, + address_id=address_id, + transfer_id=transfer_id, + broadcast_external_transfer_request=broadcast_external_transfer_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Transfer", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def broadcast_external_transfer_without_preload_content( + self, + network_id: Annotated[StrictStr, Field(description="The ID of the network the address belongs to")], + address_id: Annotated[StrictStr, Field(description="The ID of the address the transfer belongs to")], + transfer_id: Annotated[StrictStr, Field(description="The ID of the transfer to broadcast")], + broadcast_external_transfer_request: BroadcastExternalTransferRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Broadcast an external address' transfer + + Broadcast an external address's transfer with a signed payload + + :param network_id: The ID of the network the address belongs to (required) + :type network_id: str + :param address_id: The ID of the address the transfer belongs to (required) + :type address_id: str + :param transfer_id: The ID of the transfer to broadcast (required) + :type transfer_id: str + :param broadcast_external_transfer_request: (required) + :type broadcast_external_transfer_request: BroadcastExternalTransferRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._broadcast_external_transfer_serialize( + network_id=network_id, + address_id=address_id, + transfer_id=transfer_id, + broadcast_external_transfer_request=broadcast_external_transfer_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Transfer", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _broadcast_external_transfer_serialize( + self, + network_id, + address_id, + transfer_id, + broadcast_external_transfer_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + if network_id is not None: + _path_params['network_id'] = network_id + if address_id is not None: + _path_params['address_id'] = address_id + if transfer_id is not None: + _path_params['transfer_id'] = transfer_id + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if broadcast_external_transfer_request is not None: + _body_params = broadcast_external_transfer_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'apiKey' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/v1/networks/{network_id}/addresses/{address_id}/transfers/{transfer_id}/broadcast', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + def create_external_transfer( + self, + network_id: Annotated[StrictStr, Field(description="The ID of the network the address is on")], + address_id: Annotated[StrictStr, Field(description="The ID of the address to transfer from")], + create_external_transfer_request: CreateExternalTransferRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Transfer: + """Create a new transfer + + Create a new transfer between addresses. + + :param network_id: The ID of the network the address is on (required) + :type network_id: str + :param address_id: The ID of the address to transfer from (required) + :type address_id: str + :param create_external_transfer_request: (required) + :type create_external_transfer_request: CreateExternalTransferRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._create_external_transfer_serialize( + network_id=network_id, + address_id=address_id, + create_external_transfer_request=create_external_transfer_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Transfer", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def create_external_transfer_with_http_info( + self, + network_id: Annotated[StrictStr, Field(description="The ID of the network the address is on")], + address_id: Annotated[StrictStr, Field(description="The ID of the address to transfer from")], + create_external_transfer_request: CreateExternalTransferRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Transfer]: + """Create a new transfer + + Create a new transfer between addresses. + + :param network_id: The ID of the network the address is on (required) + :type network_id: str + :param address_id: The ID of the address to transfer from (required) + :type address_id: str + :param create_external_transfer_request: (required) + :type create_external_transfer_request: CreateExternalTransferRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._create_external_transfer_serialize( + network_id=network_id, + address_id=address_id, + create_external_transfer_request=create_external_transfer_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Transfer", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def create_external_transfer_without_preload_content( + self, + network_id: Annotated[StrictStr, Field(description="The ID of the network the address is on")], + address_id: Annotated[StrictStr, Field(description="The ID of the address to transfer from")], + create_external_transfer_request: CreateExternalTransferRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Create a new transfer + + Create a new transfer between addresses. + + :param network_id: The ID of the network the address is on (required) + :type network_id: str + :param address_id: The ID of the address to transfer from (required) + :type address_id: str + :param create_external_transfer_request: (required) + :type create_external_transfer_request: CreateExternalTransferRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._create_external_transfer_serialize( + network_id=network_id, + address_id=address_id, + create_external_transfer_request=create_external_transfer_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Transfer", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _create_external_transfer_serialize( + self, + network_id, + address_id, + create_external_transfer_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + if network_id is not None: + _path_params['network_id'] = network_id + if address_id is not None: + _path_params['address_id'] = address_id + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if create_external_transfer_request is not None: + _body_params = create_external_transfer_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'apiKey' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/v1/networks/{network_id}/addresses/{address_id}/transfers', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + @validate_call def get_external_address_balance( self, @@ -333,6 +959,297 @@ def _get_external_address_balance_serialize( + @validate_call + def get_external_transfer( + self, + network_id: Annotated[StrictStr, Field(description="The ID of the network the address is on")], + address_id: Annotated[StrictStr, Field(description="The ID of the address the transfer belongs to")], + transfer_id: Annotated[StrictStr, Field(description="The ID of the transfer to fetch")], + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Transfer: + """Get a external address' transfer + + Get an external address' transfer by ID + + :param network_id: The ID of the network the address is on (required) + :type network_id: str + :param address_id: The ID of the address the transfer belongs to (required) + :type address_id: str + :param transfer_id: The ID of the transfer to fetch (required) + :type transfer_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_external_transfer_serialize( + network_id=network_id, + address_id=address_id, + transfer_id=transfer_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Transfer", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def get_external_transfer_with_http_info( + self, + network_id: Annotated[StrictStr, Field(description="The ID of the network the address is on")], + address_id: Annotated[StrictStr, Field(description="The ID of the address the transfer belongs to")], + transfer_id: Annotated[StrictStr, Field(description="The ID of the transfer to fetch")], + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Transfer]: + """Get a external address' transfer + + Get an external address' transfer by ID + + :param network_id: The ID of the network the address is on (required) + :type network_id: str + :param address_id: The ID of the address the transfer belongs to (required) + :type address_id: str + :param transfer_id: The ID of the transfer to fetch (required) + :type transfer_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_external_transfer_serialize( + network_id=network_id, + address_id=address_id, + transfer_id=transfer_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Transfer", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def get_external_transfer_without_preload_content( + self, + network_id: Annotated[StrictStr, Field(description="The ID of the network the address is on")], + address_id: Annotated[StrictStr, Field(description="The ID of the address the transfer belongs to")], + transfer_id: Annotated[StrictStr, Field(description="The ID of the transfer to fetch")], + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Get a external address' transfer + + Get an external address' transfer by ID + + :param network_id: The ID of the network the address is on (required) + :type network_id: str + :param address_id: The ID of the address the transfer belongs to (required) + :type address_id: str + :param transfer_id: The ID of the transfer to fetch (required) + :type transfer_id: str + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._get_external_transfer_serialize( + network_id=network_id, + address_id=address_id, + transfer_id=transfer_id, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Transfer", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _get_external_transfer_serialize( + self, + network_id, + address_id, + transfer_id, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + if network_id is not None: + _path_params['network_id'] = network_id + if address_id is not None: + _path_params['address_id'] = address_id + if transfer_id is not None: + _path_params['transfer_id'] = transfer_id + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + 'apiKey' + ] + + return self.api_client.param_serialize( + method='GET', + resource_path='/v1/networks/{network_id}/addresses/{address_id}/transfers/{transfer_id}', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + @validate_call def get_faucet_transaction( self, diff --git a/cdp/client/models/__init__.py b/cdp/client/models/__init__.py index 9191c6a..64cf743 100644 --- a/cdp/client/models/__init__.py +++ b/cdp/client/models/__init__.py @@ -24,6 +24,7 @@ from cdp.client.models.asset import Asset from cdp.client.models.balance import Balance from cdp.client.models.broadcast_contract_invocation_request import BroadcastContractInvocationRequest +from cdp.client.models.broadcast_external_transfer_request import BroadcastExternalTransferRequest from cdp.client.models.broadcast_staking_operation_request import BroadcastStakingOperationRequest from cdp.client.models.broadcast_trade_request import BroadcastTradeRequest from cdp.client.models.broadcast_transfer_request import BroadcastTransferRequest @@ -34,6 +35,7 @@ from cdp.client.models.contract_invocation_list import ContractInvocationList from cdp.client.models.create_address_request import CreateAddressRequest from cdp.client.models.create_contract_invocation_request import CreateContractInvocationRequest +from cdp.client.models.create_external_transfer_request import CreateExternalTransferRequest from cdp.client.models.create_fund_operation_request import CreateFundOperationRequest from cdp.client.models.create_fund_quote_request import CreateFundQuoteRequest from cdp.client.models.create_payload_signature_request import CreatePayloadSignatureRequest diff --git a/cdp/client/models/broadcast_external_transfer_request.py b/cdp/client/models/broadcast_external_transfer_request.py new file mode 100644 index 0000000..5592184 --- /dev/null +++ b/cdp/client/models/broadcast_external_transfer_request.py @@ -0,0 +1,87 @@ +# coding: utf-8 + +""" + Coinbase Platform API + + This is the OpenAPI 3.0 specification for the Coinbase Platform APIs, used in conjunction with the Coinbase Platform SDKs. + + The version of the OpenAPI document: 0.0.1-alpha + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr +from typing import Any, ClassVar, Dict, List +from typing import Optional, Set +from typing_extensions import Self + +class BroadcastExternalTransferRequest(BaseModel): + """ + BroadcastExternalTransferRequest + """ # noqa: E501 + signed_payload: StrictStr = Field(description="The hex-encoded signed payload of the external transfer") + __properties: ClassVar[List[str]] = ["signed_payload"] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of BroadcastExternalTransferRequest from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of BroadcastExternalTransferRequest from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "signed_payload": obj.get("signed_payload") + }) + return _obj + + diff --git a/cdp/client/models/create_external_transfer_request.py b/cdp/client/models/create_external_transfer_request.py new file mode 100644 index 0000000..7b17aa3 --- /dev/null +++ b/cdp/client/models/create_external_transfer_request.py @@ -0,0 +1,95 @@ +# coding: utf-8 + +""" + Coinbase Platform API + + This is the OpenAPI 3.0 specification for the Coinbase Platform APIs, used in conjunction with the Coinbase Platform SDKs. + + The version of the OpenAPI document: 0.0.1-alpha + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from typing import Optional, Set +from typing_extensions import Self + +class CreateExternalTransferRequest(BaseModel): + """ + CreateExternalTransferRequest + """ # noqa: E501 + amount: StrictStr = Field(description="The amount to transfer") + asset_id: StrictStr = Field(description="The ID of the asset to transfer. Can be an asset symbol or a token contract address.") + destination: StrictStr = Field(description="The destination address, which can be a 0x address, Basename, or ENS name") + gasless: StrictBool = Field(description="Whether the transfer uses sponsored gas") + skip_batching: Optional[StrictBool] = Field(default=None, description="When true, the transfer will be submitted immediately. Otherwise, the transfer will be batched. Defaults to false. Note: Requires the gasless option to be set to true. ") + __properties: ClassVar[List[str]] = ["amount", "asset_id", "destination", "gasless", "skip_batching"] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of CreateExternalTransferRequest from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of CreateExternalTransferRequest from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "amount": obj.get("amount"), + "asset_id": obj.get("asset_id"), + "destination": obj.get("destination"), + "gasless": obj.get("gasless"), + "skip_batching": obj.get("skip_batching") + }) + return _obj + + diff --git a/cdp/client/models/create_fund_operation_request.py b/cdp/client/models/create_fund_operation_request.py index adebe8c..9c84e9c 100644 --- a/cdp/client/models/create_fund_operation_request.py +++ b/cdp/client/models/create_fund_operation_request.py @@ -27,7 +27,7 @@ class CreateFundOperationRequest(BaseModel): CreateFundOperationRequest """ # noqa: E501 amount: StrictStr = Field(description="The amount of the asset to fund the address with in atomic units.") - asset_id: StrictStr = Field(description="The ID of the asset to fund the address with.") + asset_id: StrictStr = Field(description="The ID of the asset to fund the address with. Can be an asset symbol or a token contract address.") fund_quote_id: Optional[StrictStr] = Field(default=None, description="The Optional ID of the fund quote to fund the address with. If omitted we will generate a quote and immediately execute it.") __properties: ClassVar[List[str]] = ["amount", "asset_id", "fund_quote_id"] diff --git a/cdp/client/models/create_fund_quote_request.py b/cdp/client/models/create_fund_quote_request.py index e7f7bda..671c325 100644 --- a/cdp/client/models/create_fund_quote_request.py +++ b/cdp/client/models/create_fund_quote_request.py @@ -27,7 +27,7 @@ class CreateFundQuoteRequest(BaseModel): CreateFundQuoteRequest """ # noqa: E501 amount: StrictStr = Field(description="The amount of the asset to fund the address with in atomic units.") - asset_id: StrictStr = Field(description="The ID of the asset to fund the address with.") + asset_id: StrictStr = Field(description="The ID of the asset to fund the address with. Can be an asset symbol alias or a token contract address.") __properties: ClassVar[List[str]] = ["amount", "asset_id"] model_config = ConfigDict( diff --git a/cdp/client/models/create_transfer_request.py b/cdp/client/models/create_transfer_request.py index a180aa5..2c794f5 100644 --- a/cdp/client/models/create_transfer_request.py +++ b/cdp/client/models/create_transfer_request.py @@ -28,10 +28,11 @@ class CreateTransferRequest(BaseModel): """ # noqa: E501 amount: StrictStr = Field(description="The amount to transfer") network_id: StrictStr = Field(description="The ID of the blockchain network") - asset_id: StrictStr = Field(description="The ID of the asset to transfer") + asset_id: StrictStr = Field(description="The ID of the asset to transfer. Can be an asset symbol or a token contract address.") destination: StrictStr = Field(description="The destination address, which can be a 0x address, Basename, or ENS name") gasless: Optional[StrictBool] = Field(default=None, description="Whether the transfer uses sponsored gas") - __properties: ClassVar[List[str]] = ["amount", "network_id", "asset_id", "destination", "gasless"] + skip_batching: Optional[StrictBool] = Field(default=None, description="When true, the transfer will be submitted immediately. Otherwise, the transfer will be batched. Defaults to false") + __properties: ClassVar[List[str]] = ["amount", "network_id", "asset_id", "destination", "gasless", "skip_batching"] model_config = ConfigDict( populate_by_name=True, @@ -88,7 +89,8 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "network_id": obj.get("network_id"), "asset_id": obj.get("asset_id"), "destination": obj.get("destination"), - "gasless": obj.get("gasless") + "gasless": obj.get("gasless"), + "skip_batching": obj.get("skip_batching") }) return _obj diff --git a/cdp/transfer.py b/cdp/transfer.py index b77d568..545297a 100644 --- a/cdp/transfer.py +++ b/cdp/transfer.py @@ -151,6 +151,7 @@ def create( network_id: str, wallet_id: str, gasless: bool = False, + skip_batching: bool = False, ) -> "Transfer": """Create a transfer. @@ -162,11 +163,17 @@ def create( network_id (str): The network ID. wallet_id (str): The wallet ID. gasless (bool): Whether to use gasless. + skip_batching (bool): When True, the Transfer will be submitted immediately. Otherwise, the Transfer will be batched. Defaults to False. Note: requires gasless option to be set to True. Returns: Transfer: The transfer. """ + print('skip_batching', skip_batching) + print('gasless', gasless) + if skip_batching and not gasless: + raise ValueError("skip_batching requires gasless to be True") + asset = Asset.fetch(network_id, asset_id) if hasattr(destination, "address_id"): @@ -184,6 +191,7 @@ def create( destination=destination, network_id=network_id, gasless=gasless, + skip_batching=skip_batching, ) model = Cdp.api_clients.transfers.create_transfer( diff --git a/cdp/wallet.py b/cdp/wallet.py index baf5f66..542f7e3 100644 --- a/cdp/wallet.py +++ b/cdp/wallet.py @@ -35,6 +35,7 @@ from cdp.payload_signature import PayloadSignature from cdp.smart_contract import SmartContract from cdp.trade import Trade +from cdp.transfer import Transfer from cdp.wallet_address import WalletAddress from cdp.wallet_data import WalletData from cdp.webhook import Webhook @@ -436,7 +437,8 @@ def transfer( asset_id: str, destination: Union[Address, "Wallet", str], gasless: bool = False, - ) -> Any: + skip_batching: bool = False, + ) -> Transfer: """Transfer funds from the wallet. Args: @@ -444,6 +446,7 @@ def transfer( asset_id (str): The ID of the asset to transfer. destination (Union[Address, 'Wallet', str]): The destination for the transfer. gasless (bool): Whether the transfer should be gasless. Defaults to False. + skip_batching (bool): When True, the Transfer will be submitted immediately. Otherwise, the Transfer will be batched. Defaults to False. Note: requires gasless option to be set to True. Returns: Any: The result of the transfer operation. @@ -459,7 +462,7 @@ def transfer( if isinstance(amount, float | int | str): amount = Decimal(amount) - return self.default_address.transfer(amount, asset_id, destination, gasless) + return self.default_address.transfer(amount, asset_id, destination, gasless, skip_batching) def trade(self, amount: Number | Decimal | str, from_asset_id: str, to_asset_id: str) -> Trade: """Trade funds from the wallet address. diff --git a/cdp/wallet_address.py b/cdp/wallet_address.py index 14865e4..d43d1b5 100644 --- a/cdp/wallet_address.py +++ b/cdp/wallet_address.py @@ -100,6 +100,7 @@ def transfer( asset_id: str, destination: Union[Address, "Wallet", str], gasless: bool = False, + skip_batching: bool = False, ) -> Transfer: """Transfer funds from the wallet address. @@ -108,6 +109,7 @@ def transfer( asset_id (str): The asset ID. destination (Union[Address, 'Wallet', str]): The transfer destination. gasless (bool): Whether to use gasless transfer. + skip_batching (bool): When True, the Transfer will be submitted immediately. Otherwise, the Transfer will be batched. Defaults to False. Note: requires gasless option to be set to True. Returns: Transfer: The created transfer object. @@ -125,6 +127,7 @@ def transfer( network_id=self.network_id, wallet_id=self.wallet_id, gasless=gasless, + skip_batching=skip_batching, ) if Cdp.use_server_signer: diff --git a/tests/test_transfer.py b/tests/test_transfer.py index 9153905..e95bf71 100644 --- a/tests/test_transfer.py +++ b/tests/test_transfer.py @@ -85,6 +85,61 @@ def test_create_transfer(mock_asset, mock_api_clients, transfer_factory, asset_f assert create_transfer_request.network_id == "base-sepolia" assert create_transfer_request.gasless == gasless +@patch("cdp.Cdp.api_clients") +@patch("cdp.transfer.Asset") +def test_create_transfer_with_skip_batching(mock_asset, mock_api_clients, transfer_factory, asset_factory): + """Test the creation of a Transfer object.""" + mock_fetch = Mock() + mock_fetch.return_value = asset_factory() + mock_asset.fetch = mock_fetch + + mock_primary_denomination = Mock() + mock_primary_denomination.return_value = "usdc" + mock_asset.primary_denomination = mock_primary_denomination + + mock_create_transfer = Mock() + mock_create_transfer.return_value = transfer_factory(gasless=True)._model + mock_api_clients.transfers.create_transfer = mock_create_transfer + + transfer = Transfer.create( + address_id="0xaddressid", + amount=Decimal("1"), + asset_id="usdc", + destination="0xdestination", + network_id="base-sepolia", + wallet_id="test-wallet-id", + gasless=True, + skip_batching=True, + ) + + assert isinstance(transfer, Transfer) + mock_fetch.assert_called_once_with("base-sepolia", "usdc") + mock_primary_denomination.assert_called_once_with("usdc") + mock_create_transfer.assert_called_once_with( + wallet_id="test-wallet-id", address_id="0xaddressid", create_transfer_request=ANY + ) + + create_transfer_request = mock_create_transfer.call_args[1]["create_transfer_request"] + assert create_transfer_request.amount == "1000000" # 1 USDC in atomic units + assert create_transfer_request.asset_id == "usdc" + assert create_transfer_request.destination == "0xdestination" + assert create_transfer_request.network_id == "base-sepolia" + assert create_transfer_request.gasless == True + assert create_transfer_request.skip_batching == True + +def test_create_transfer_invalid_skip_batching(): + """Test the creation of a Transfer object with skip_batching and no gasless.""" + with pytest.raises(ValueError, match="skip_batching requires gasless to be True"): + Transfer.create( + address_id="0xaddressid", + amount=Decimal("1"), + asset_id="usdc", + destination="0xdestination", + network_id="base-sepolia", + wallet_id="test-wallet-id", + gasless=False, + skip_batching=True, + ) @patch("cdp.Cdp.api_clients") def test_list_transfers(mock_api_clients, transfer_factory): diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 0e06d65..342edd5 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -262,7 +262,7 @@ def test_wallet_transfer_with_server_signer(wallet_factory): assert isinstance(transfer, Transfer) mock_default_address.transfer.assert_called_once_with( - Decimal("1.0"), "eth", "0xdestination", False + Decimal("1.0"), "eth", "0xdestination", False, False ) diff --git a/tests/test_wallet_address.py b/tests/test_wallet_address.py index 9b3e75c..0dcaec0 100644 --- a/tests/test_wallet_address.py +++ b/tests/test_wallet_address.py @@ -135,6 +135,7 @@ def test_transfer_with_server_signer( network_id=wallet_address.network_id, wallet_id=wallet_address.wallet_id, gasless=False, + skip_batching=False, ) mock_transfer_instance.sign.assert_not_called() mock_transfer_instance.broadcast.assert_not_called() @@ -175,6 +176,7 @@ def test_transfer(mock_api_clients, mock_transfer, wallet_address_factory, balan network_id=wallet_address_with_key.network_id, wallet_id=wallet_address_with_key.wallet_id, gasless=False, + skip_batching=False, ) mock_transfer_instance.sign.assert_called_once_with(wallet_address_with_key.key) mock_transfer_instance.broadcast.assert_called_once() @@ -214,6 +216,7 @@ def test_transfer_create_api_error( network_id=wallet_address_with_key.network_id, wallet_id=wallet_address_with_key.wallet_id, gasless=False, + skip_batching=False, ) @@ -253,6 +256,7 @@ def test_transfer_broadcast_api_error( network_id=wallet_address_with_key.network_id, wallet_id=wallet_address_with_key.wallet_id, gasless=False, + skip_batching=False, ) mock_transfer_instance.sign.assert_called_once_with(wallet_address_with_key.key) mock_transfer_instance.broadcast.assert_called_once()