Skip to content

Commit 5ad49e0

Browse files
John-peterson-coinbasexinyu-li-cbhoward-at-cb
authoredOct 25, 2024··
v0.0.7 Release (#34)
* sync CDP transaction struct change (#33) * sync CDP yml file change * add changelog * list works * Update webhook.py * Update webhook.py * Update webhook.py * Update webhook.py * wallet webhook * Update wallet.py * wallet test * Create test_webhook.py * lint * lint * Update cdp/webhook.py Co-authored-by: John Peterson <98187317+John-peterson-coinbase@users.noreply.github.com> * Update cdp/webhook.py Co-authored-by: John Peterson <98187317+John-peterson-coinbase@users.noreply.github.com> * address comments * Update README.md Co-authored-by: John Peterson <98187317+John-peterson-coinbase@users.noreply.github.com> * Update README.md Co-authored-by: John Peterson <98187317+John-peterson-coinbase@users.noreply.github.com> * Update README.md Co-authored-by: John Peterson <98187317+John-peterson-coinbase@users.noreply.github.com> * address comments * Update cdp/webhook.py Co-authored-by: John Peterson <98187317+John-peterson-coinbase@users.noreply.github.com> * Prepare 0.0.7 Release --------- Co-authored-by: xinyu-li-cb <142266583+xinyu-li-cb@users.noreply.github.com> Co-authored-by: Howard Xie <howard.xie@coinbase.com> Co-authored-by: cb-howardatcb <86798563+howard-at-cb@users.noreply.github.com>
1 parent dbd42d4 commit 5ad49e0

24 files changed

+1328
-4
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ env/
2929
.\#*
3030
.projectile
3131

32+
.idea/

‎CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
## Unreleased
44

5+
## [0.0.7] - 2024-10-25
6+
7+
### Added
8+
9+
- Include ERC20 and ERC721 token transfer information into transaction content.
10+
- Support for wallet and address webhooks to trigger based on onchain activities.
11+
512
## [0.0.6] - 2024-10-17
613

714
### Added

‎README.md

+25
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,31 @@ print(f"Trade successfully completed: {trade}")
169169
list(address.trades())
170170
```
171171

172+
## Creating a Webhook
173+
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.
174+
```python
175+
from cdp.client.models.webhook import WebhookEventType
176+
from cdp.client.models.webhook import WebhookEventFilter
177+
178+
wh1 = Webhook.create(
179+
notification_uri="https://your-app.com/callback",
180+
event_type=WebhookEventType.ERC20_TRANSFER,
181+
event_filters=[WebhookEventFilter(from_address="0x71d4d7d5e9ce0f41e6a68bd3a9b43aa597dc0eb0")]
182+
)
183+
print(wh1)
184+
```
185+
186+
## Creating a Webhook On A Wallet
187+
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).
188+
```python
189+
import cdp
190+
191+
wallet1 = Wallet.create()
192+
wh1 = wallet1.create_webhook("https://your-app.com/callback")
193+
print(wh1)
194+
```
195+
172196
## Contributing
173197

174198
See [CONTRIBUTING.md](CONTRIBUTING.md) for more information.
199+

‎cdp/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from cdp.wallet import Wallet
1717
from cdp.wallet_address import WalletAddress
1818
from cdp.wallet_data import WalletData
19+
from cdp.webhook import Webhook
1920

2021
__all__ = [
2122
"__version__",
@@ -24,6 +25,7 @@
2425
"Wallet",
2526
"WalletAddress",
2627
"WalletData",
28+
"Webhook",
2729
"Asset",
2830
"Transfer",
2931
"Address",

‎cdp/__version__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.0.6"
1+
__version__ = "0.0.7"

‎cdp/api_clients.py

+18
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from cdp.client.api.transaction_history_api import TransactionHistoryApi
1111
from cdp.client.api.transfers_api import TransfersApi
1212
from cdp.client.api.wallets_api import WalletsApi
13+
from cdp.client.api.webhooks_api import WebhooksApi
1314

1415

1516
class ApiClients:
@@ -21,6 +22,7 @@ class ApiClients:
2122
Attributes:
2223
_cdp_client (CdpApiClient): The CDP API client used to initialize individual API clients.
2324
_wallets (Optional[WalletsApi]): The WalletsApi client instance.
25+
_webhooks (Optional[WebhooksApi]): The WebhooksApi client instance.
2426
_addresses (Optional[AddressesApi]): The AddressesApi client instance.
2527
_external_addresses (Optional[ExternalAddressesApi]): The ExternalAddressesApi client instance.
2628
_transfers (Optional[TransfersApi]): The TransfersApi client instance.
@@ -40,6 +42,7 @@ def __init__(self, cdp_client: CdpApiClient) -> None:
4042
"""
4143
self._cdp_client: CdpApiClient = cdp_client
4244
self._wallets: WalletsApi | None = None
45+
self._webhooks: WebhooksApi | None = None
4346
self._addresses: AddressesApi | None = None
4447
self._external_addresses: ExternalAddressesApi | None = None
4548
self._transfers: TransfersApi | None = None
@@ -66,6 +69,21 @@ def wallets(self) -> WalletsApi:
6669
self._wallets = WalletsApi(api_client=self._cdp_client)
6770
return self._wallets
6871

72+
@property
73+
def webhooks(self) -> WebhooksApi:
74+
"""Get the WebhooksApi client instance.
75+
76+
Returns:
77+
WebhooksApi: The WebhooksApi client instance.
78+
79+
Note:
80+
This property lazily initializes the WebhooksApi client on first access.
81+
82+
"""
83+
if self._webhooks is None:
84+
self._webhooks = WebhooksApi(api_client=self._cdp_client)
85+
return self._webhooks
86+
6987
@property
7088
def addresses(self) -> AddressesApi:
7189
"""Get the AddressesApi client instance.

‎cdp/client/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from cdp.client.api.contract_invocations_api import ContractInvocationsApi
2525
from cdp.client.api.external_addresses_api import ExternalAddressesApi
2626
from cdp.client.api.networks_api import NetworksApi
27+
from cdp.client.api.onchain_identity_api import OnchainIdentityApi
2728
from cdp.client.api.server_signers_api import ServerSignersApi
2829
from cdp.client.api.smart_contracts_api import SmartContractsApi
2930
from cdp.client.api.stake_api import StakeApi
@@ -96,6 +97,9 @@
9697
from cdp.client.models.nft_contract_options import NFTContractOptions
9798
from cdp.client.models.network import Network
9899
from cdp.client.models.network_identifier import NetworkIdentifier
100+
from cdp.client.models.onchain_name import OnchainName
101+
from cdp.client.models.onchain_name_list import OnchainNameList
102+
from cdp.client.models.onchain_name_text_records_inner import OnchainNameTextRecordsInner
99103
from cdp.client.models.payload_signature import PayloadSignature
100104
from cdp.client.models.payload_signature_list import PayloadSignatureList
101105
from cdp.client.models.read_contract_request import ReadContractRequest

‎cdp/client/api/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from cdp.client.api.contract_invocations_api import ContractInvocationsApi
99
from cdp.client.api.external_addresses_api import ExternalAddressesApi
1010
from cdp.client.api.networks_api import NetworksApi
11+
from cdp.client.api.onchain_identity_api import OnchainIdentityApi
1112
from cdp.client.api.server_signers_api import ServerSignersApi
1213
from cdp.client.api.smart_contracts_api import SmartContractsApi
1314
from cdp.client.api.stake_api import StakeApi
+346
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
# coding: utf-8
2+
3+
"""
4+
Coinbase Platform API
5+
6+
This is the OpenAPI 3.0 specification for the Coinbase Platform APIs, used in conjunction with the Coinbase Platform SDKs.
7+
8+
The version of the OpenAPI document: 0.0.1-alpha
9+
Generated by OpenAPI Generator (https://openapi-generator.tech)
10+
11+
Do not edit the class manually.
12+
""" # noqa: E501
13+
14+
import warnings
15+
from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt
16+
from typing import Any, Dict, List, Optional, Tuple, Union
17+
from typing_extensions import Annotated
18+
19+
from pydantic import Field, StrictInt, StrictStr
20+
from typing import Optional
21+
from typing_extensions import Annotated
22+
from cdp.client.models.onchain_name_list import OnchainNameList
23+
24+
from cdp.client.api_client import ApiClient, RequestSerialized
25+
from cdp.client.api_response import ApiResponse
26+
from cdp.client.rest import RESTResponseType
27+
28+
29+
class OnchainIdentityApi:
30+
"""NOTE: This class is auto generated by OpenAPI Generator
31+
Ref: https://openapi-generator.tech
32+
33+
Do not edit the class manually.
34+
"""
35+
36+
def __init__(self, api_client=None) -> None:
37+
if api_client is None:
38+
api_client = ApiClient.get_default()
39+
self.api_client = api_client
40+
41+
42+
@validate_call
43+
def resolve_identity_by_address(
44+
self,
45+
network_id: Annotated[StrictStr, Field(description="The ID of the blockchain network")],
46+
address_id: Annotated[StrictStr, Field(description="The ID of the address to fetch the identity for")],
47+
limit: Annotated[Optional[StrictInt], Field(description="A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.")] = None,
48+
page: Annotated[Optional[Annotated[str, Field(strict=True, max_length=5000)]], Field(description="A cursor for pagination across multiple pages of results. Don't include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.")] = None,
49+
_request_timeout: Union[
50+
None,
51+
Annotated[StrictFloat, Field(gt=0)],
52+
Tuple[
53+
Annotated[StrictFloat, Field(gt=0)],
54+
Annotated[StrictFloat, Field(gt=0)]
55+
]
56+
] = None,
57+
_request_auth: Optional[Dict[StrictStr, Any]] = None,
58+
_content_type: Optional[StrictStr] = None,
59+
_headers: Optional[Dict[StrictStr, Any]] = None,
60+
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
61+
) -> OnchainNameList:
62+
"""Obtains onchain identity for an address on a specific network
63+
64+
Obtains onchain identity for an address on a specific network
65+
66+
:param network_id: The ID of the blockchain network (required)
67+
:type network_id: str
68+
:param address_id: The ID of the address to fetch the identity for (required)
69+
:type address_id: str
70+
:param limit: A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.
71+
:type limit: int
72+
:param page: A cursor for pagination across multiple pages of results. Don't include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
73+
:type page: str
74+
:param _request_timeout: timeout setting for this request. If one
75+
number provided, it will be total request
76+
timeout. It can also be a pair (tuple) of
77+
(connection, read) timeouts.
78+
:type _request_timeout: int, tuple(int, int), optional
79+
:param _request_auth: set to override the auth_settings for an a single
80+
request; this effectively ignores the
81+
authentication in the spec for a single request.
82+
:type _request_auth: dict, optional
83+
:param _content_type: force content-type for the request.
84+
:type _content_type: str, Optional
85+
:param _headers: set to override the headers for a single
86+
request; this effectively ignores the headers
87+
in the spec for a single request.
88+
:type _headers: dict, optional
89+
:param _host_index: set to override the host_index for a single
90+
request; this effectively ignores the host_index
91+
in the spec for a single request.
92+
:type _host_index: int, optional
93+
:return: Returns the result object.
94+
""" # noqa: E501
95+
96+
_param = self._resolve_identity_by_address_serialize(
97+
network_id=network_id,
98+
address_id=address_id,
99+
limit=limit,
100+
page=page,
101+
_request_auth=_request_auth,
102+
_content_type=_content_type,
103+
_headers=_headers,
104+
_host_index=_host_index
105+
)
106+
107+
_response_types_map: Dict[str, Optional[str]] = {
108+
'200': "OnchainNameList",
109+
}
110+
response_data = self.api_client.call_api(
111+
*_param,
112+
_request_timeout=_request_timeout
113+
)
114+
response_data.read()
115+
return self.api_client.response_deserialize(
116+
response_data=response_data,
117+
response_types_map=_response_types_map,
118+
).data
119+
120+
121+
@validate_call
122+
def resolve_identity_by_address_with_http_info(
123+
self,
124+
network_id: Annotated[StrictStr, Field(description="The ID of the blockchain network")],
125+
address_id: Annotated[StrictStr, Field(description="The ID of the address to fetch the identity for")],
126+
limit: Annotated[Optional[StrictInt], Field(description="A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.")] = None,
127+
page: Annotated[Optional[Annotated[str, Field(strict=True, max_length=5000)]], Field(description="A cursor for pagination across multiple pages of results. Don't include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.")] = None,
128+
_request_timeout: Union[
129+
None,
130+
Annotated[StrictFloat, Field(gt=0)],
131+
Tuple[
132+
Annotated[StrictFloat, Field(gt=0)],
133+
Annotated[StrictFloat, Field(gt=0)]
134+
]
135+
] = None,
136+
_request_auth: Optional[Dict[StrictStr, Any]] = None,
137+
_content_type: Optional[StrictStr] = None,
138+
_headers: Optional[Dict[StrictStr, Any]] = None,
139+
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
140+
) -> ApiResponse[OnchainNameList]:
141+
"""Obtains onchain identity for an address on a specific network
142+
143+
Obtains onchain identity for an address on a specific network
144+
145+
:param network_id: The ID of the blockchain network (required)
146+
:type network_id: str
147+
:param address_id: The ID of the address to fetch the identity for (required)
148+
:type address_id: str
149+
:param limit: A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.
150+
:type limit: int
151+
:param page: A cursor for pagination across multiple pages of results. Don't include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
152+
:type page: str
153+
:param _request_timeout: timeout setting for this request. If one
154+
number provided, it will be total request
155+
timeout. It can also be a pair (tuple) of
156+
(connection, read) timeouts.
157+
:type _request_timeout: int, tuple(int, int), optional
158+
:param _request_auth: set to override the auth_settings for an a single
159+
request; this effectively ignores the
160+
authentication in the spec for a single request.
161+
:type _request_auth: dict, optional
162+
:param _content_type: force content-type for the request.
163+
:type _content_type: str, Optional
164+
:param _headers: set to override the headers for a single
165+
request; this effectively ignores the headers
166+
in the spec for a single request.
167+
:type _headers: dict, optional
168+
:param _host_index: set to override the host_index for a single
169+
request; this effectively ignores the host_index
170+
in the spec for a single request.
171+
:type _host_index: int, optional
172+
:return: Returns the result object.
173+
""" # noqa: E501
174+
175+
_param = self._resolve_identity_by_address_serialize(
176+
network_id=network_id,
177+
address_id=address_id,
178+
limit=limit,
179+
page=page,
180+
_request_auth=_request_auth,
181+
_content_type=_content_type,
182+
_headers=_headers,
183+
_host_index=_host_index
184+
)
185+
186+
_response_types_map: Dict[str, Optional[str]] = {
187+
'200': "OnchainNameList",
188+
}
189+
response_data = self.api_client.call_api(
190+
*_param,
191+
_request_timeout=_request_timeout
192+
)
193+
response_data.read()
194+
return self.api_client.response_deserialize(
195+
response_data=response_data,
196+
response_types_map=_response_types_map,
197+
)
198+
199+
200+
@validate_call
201+
def resolve_identity_by_address_without_preload_content(
202+
self,
203+
network_id: Annotated[StrictStr, Field(description="The ID of the blockchain network")],
204+
address_id: Annotated[StrictStr, Field(description="The ID of the address to fetch the identity for")],
205+
limit: Annotated[Optional[StrictInt], Field(description="A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.")] = None,
206+
page: Annotated[Optional[Annotated[str, Field(strict=True, max_length=5000)]], Field(description="A cursor for pagination across multiple pages of results. Don't include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.")] = None,
207+
_request_timeout: Union[
208+
None,
209+
Annotated[StrictFloat, Field(gt=0)],
210+
Tuple[
211+
Annotated[StrictFloat, Field(gt=0)],
212+
Annotated[StrictFloat, Field(gt=0)]
213+
]
214+
] = None,
215+
_request_auth: Optional[Dict[StrictStr, Any]] = None,
216+
_content_type: Optional[StrictStr] = None,
217+
_headers: Optional[Dict[StrictStr, Any]] = None,
218+
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
219+
) -> RESTResponseType:
220+
"""Obtains onchain identity for an address on a specific network
221+
222+
Obtains onchain identity for an address on a specific network
223+
224+
:param network_id: The ID of the blockchain network (required)
225+
:type network_id: str
226+
:param address_id: The ID of the address to fetch the identity for (required)
227+
:type address_id: str
228+
:param limit: A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.
229+
:type limit: int
230+
:param page: A cursor for pagination across multiple pages of results. Don't include this parameter on the first call. Use the next_page value returned in a previous response to request subsequent results.
231+
:type page: str
232+
:param _request_timeout: timeout setting for this request. If one
233+
number provided, it will be total request
234+
timeout. It can also be a pair (tuple) of
235+
(connection, read) timeouts.
236+
:type _request_timeout: int, tuple(int, int), optional
237+
:param _request_auth: set to override the auth_settings for an a single
238+
request; this effectively ignores the
239+
authentication in the spec for a single request.
240+
:type _request_auth: dict, optional
241+
:param _content_type: force content-type for the request.
242+
:type _content_type: str, Optional
243+
:param _headers: set to override the headers for a single
244+
request; this effectively ignores the headers
245+
in the spec for a single request.
246+
:type _headers: dict, optional
247+
:param _host_index: set to override the host_index for a single
248+
request; this effectively ignores the host_index
249+
in the spec for a single request.
250+
:type _host_index: int, optional
251+
:return: Returns the result object.
252+
""" # noqa: E501
253+
254+
_param = self._resolve_identity_by_address_serialize(
255+
network_id=network_id,
256+
address_id=address_id,
257+
limit=limit,
258+
page=page,
259+
_request_auth=_request_auth,
260+
_content_type=_content_type,
261+
_headers=_headers,
262+
_host_index=_host_index
263+
)
264+
265+
_response_types_map: Dict[str, Optional[str]] = {
266+
'200': "OnchainNameList",
267+
}
268+
response_data = self.api_client.call_api(
269+
*_param,
270+
_request_timeout=_request_timeout
271+
)
272+
return response_data.response
273+
274+
275+
def _resolve_identity_by_address_serialize(
276+
self,
277+
network_id,
278+
address_id,
279+
limit,
280+
page,
281+
_request_auth,
282+
_content_type,
283+
_headers,
284+
_host_index,
285+
) -> RequestSerialized:
286+
287+
_host = None
288+
289+
_collection_formats: Dict[str, str] = {
290+
}
291+
292+
_path_params: Dict[str, str] = {}
293+
_query_params: List[Tuple[str, str]] = []
294+
_header_params: Dict[str, Optional[str]] = _headers or {}
295+
_form_params: List[Tuple[str, str]] = []
296+
_files: Dict[str, Union[str, bytes]] = {}
297+
_body_params: Optional[bytes] = None
298+
299+
# process the path parameters
300+
if network_id is not None:
301+
_path_params['network_id'] = network_id
302+
if address_id is not None:
303+
_path_params['address_id'] = address_id
304+
# process the query parameters
305+
if limit is not None:
306+
307+
_query_params.append(('limit', limit))
308+
309+
if page is not None:
310+
311+
_query_params.append(('page', page))
312+
313+
# process the header parameters
314+
# process the form parameters
315+
# process the body parameter
316+
317+
318+
# set the HTTP header `Accept`
319+
if 'Accept' not in _header_params:
320+
_header_params['Accept'] = self.api_client.select_header_accept(
321+
[
322+
'application/json'
323+
]
324+
)
325+
326+
327+
# authentication setting
328+
_auth_settings: List[str] = [
329+
]
330+
331+
return self.api_client.param_serialize(
332+
method='GET',
333+
resource_path='/v1/networks/{network_id}/addresses/{address_id}/identity',
334+
path_params=_path_params,
335+
query_params=_query_params,
336+
header_params=_header_params,
337+
body=_body_params,
338+
post_params=_form_params,
339+
files=_files,
340+
auth_settings=_auth_settings,
341+
collection_formats=_collection_formats,
342+
_host=_host,
343+
_request_auth=_request_auth
344+
)
345+
346+

‎cdp/client/models/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@
6262
from cdp.client.models.nft_contract_options import NFTContractOptions
6363
from cdp.client.models.network import Network
6464
from cdp.client.models.network_identifier import NetworkIdentifier
65+
from cdp.client.models.onchain_name import OnchainName
66+
from cdp.client.models.onchain_name_list import OnchainNameList
67+
from cdp.client.models.onchain_name_text_records_inner import OnchainNameTextRecordsInner
6568
from cdp.client.models.payload_signature import PayloadSignature
6669
from cdp.client.models.payload_signature_list import PayloadSignatureList
6770
from cdp.client.models.read_contract_request import ReadContractRequest
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# coding: utf-8
2+
3+
"""
4+
Coinbase Platform API
5+
6+
This is the OpenAPI 3.0 specification for the Coinbase Platform APIs, used in conjunction with the Coinbase Platform SDKs.
7+
8+
The version of the OpenAPI document: 0.0.1-alpha
9+
Generated by OpenAPI Generator (https://openapi-generator.tech)
10+
11+
Do not edit the class manually.
12+
""" # noqa: E501
13+
14+
15+
from __future__ import annotations
16+
import pprint
17+
import re # noqa: F401
18+
import json
19+
20+
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
21+
from typing import Any, ClassVar, Dict, List, Optional
22+
from cdp.client.models.token_transfer_type import TokenTransferType
23+
from typing import Optional, Set
24+
from typing_extensions import Self
25+
26+
class EthereumTokenTransfer(BaseModel):
27+
"""
28+
EthereumTokenTransfer
29+
""" # noqa: E501
30+
contract_address: StrictStr
31+
from_address: StrictStr
32+
to_address: StrictStr
33+
value: Optional[StrictStr] = Field(default=None, description="The value of the transaction in atomic units of the token being transfer for ERC20 or ERC1155 contracts.")
34+
token_id: Optional[StrictStr] = Field(default=None, description="The ID of ERC721 or ERC1155 token being transferred.")
35+
log_index: StrictInt
36+
token_transfer_type: TokenTransferType
37+
__properties: ClassVar[List[str]] = ["contract_address", "from_address", "to_address", "value", "token_id", "log_index", "token_transfer_type"]
38+
39+
model_config = ConfigDict(
40+
populate_by_name=True,
41+
validate_assignment=True,
42+
protected_namespaces=(),
43+
)
44+
45+
46+
def to_str(self) -> str:
47+
"""Returns the string representation of the model using alias"""
48+
return pprint.pformat(self.model_dump(by_alias=True))
49+
50+
def to_json(self) -> str:
51+
"""Returns the JSON representation of the model using alias"""
52+
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
53+
return json.dumps(self.to_dict())
54+
55+
@classmethod
56+
def from_json(cls, json_str: str) -> Optional[Self]:
57+
"""Create an instance of EthereumTokenTransfer from a JSON string"""
58+
return cls.from_dict(json.loads(json_str))
59+
60+
def to_dict(self) -> Dict[str, Any]:
61+
"""Return the dictionary representation of the model using alias.
62+
63+
This has the following differences from calling pydantic's
64+
`self.model_dump(by_alias=True)`:
65+
66+
* `None` is only added to the output dict for nullable fields that
67+
were set at model initialization. Other fields with value `None`
68+
are ignored.
69+
"""
70+
excluded_fields: Set[str] = set([
71+
])
72+
73+
_dict = self.model_dump(
74+
by_alias=True,
75+
exclude=excluded_fields,
76+
exclude_none=True,
77+
)
78+
return _dict
79+
80+
@classmethod
81+
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
82+
"""Create an instance of EthereumTokenTransfer from a dict"""
83+
if obj is None:
84+
return None
85+
86+
if not isinstance(obj, dict):
87+
return cls.model_validate(obj)
88+
89+
_obj = cls.model_validate({
90+
"contract_address": obj.get("contract_address"),
91+
"from_address": obj.get("from_address"),
92+
"to_address": obj.get("to_address"),
93+
"value": obj.get("value"),
94+
"token_id": obj.get("token_id"),
95+
"log_index": obj.get("log_index"),
96+
"token_transfer_type": obj.get("token_transfer_type")
97+
})
98+
return _obj
99+
100+

‎cdp/client/models/ethereum_transaction.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from datetime import datetime
2121
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
2222
from typing import Any, ClassVar, Dict, List, Optional
23+
from cdp.client.models.ethereum_token_transfer import EthereumTokenTransfer
2324
from cdp.client.models.ethereum_transaction_access_list import EthereumTransactionAccessList
2425
from cdp.client.models.ethereum_transaction_flattened_trace import EthereumTransactionFlattenedTrace
2526
from typing import Optional, Set
@@ -43,10 +44,11 @@ class EthereumTransaction(BaseModel):
4344
max_priority_fee_per_gas: Optional[StrictInt] = Field(default=None, description="The max priority fee per gas as defined in EIP-1559. https://eips.ethereum.org/EIPS/eip-1559 for more details.")
4445
priority_fee_per_gas: Optional[StrictInt] = Field(default=None, description="The confirmed priority fee per gas as defined in EIP-1559. https://eips.ethereum.org/EIPS/eip-1559 for more details.")
4546
transaction_access_list: Optional[EthereumTransactionAccessList] = None
47+
token_transfers: Optional[List[EthereumTokenTransfer]] = None
4648
flattened_traces: Optional[List[EthereumTransactionFlattenedTrace]] = None
4749
block_timestamp: Optional[datetime] = Field(default=None, description="The timestamp of the block in which the event was emitted")
4850
mint: Optional[StrictStr] = Field(default=None, description="This is for handling optimism rollup specific EIP-2718 transaction type field.")
49-
__properties: ClassVar[List[str]] = ["from", "gas", "gas_price", "hash", "input", "nonce", "to", "index", "value", "type", "max_fee_per_gas", "max_priority_fee_per_gas", "priority_fee_per_gas", "transaction_access_list", "flattened_traces", "block_timestamp", "mint"]
51+
__properties: ClassVar[List[str]] = ["from", "gas", "gas_price", "hash", "input", "nonce", "to", "index", "value", "type", "max_fee_per_gas", "max_priority_fee_per_gas", "priority_fee_per_gas", "transaction_access_list", "token_transfers", "flattened_traces", "block_timestamp", "mint"]
5052

5153
model_config = ConfigDict(
5254
populate_by_name=True,
@@ -90,6 +92,13 @@ def to_dict(self) -> Dict[str, Any]:
9092
# override the default output from pydantic by calling `to_dict()` of transaction_access_list
9193
if self.transaction_access_list:
9294
_dict['transaction_access_list'] = self.transaction_access_list.to_dict()
95+
# override the default output from pydantic by calling `to_dict()` of each item in token_transfers (list)
96+
_items = []
97+
if self.token_transfers:
98+
for _item_token_transfers in self.token_transfers:
99+
if _item_token_transfers:
100+
_items.append(_item_token_transfers.to_dict())
101+
_dict['token_transfers'] = _items
93102
# override the default output from pydantic by calling `to_dict()` of each item in flattened_traces (list)
94103
_items = []
95104
if self.flattened_traces:
@@ -123,6 +132,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
123132
"max_priority_fee_per_gas": obj.get("max_priority_fee_per_gas"),
124133
"priority_fee_per_gas": obj.get("priority_fee_per_gas"),
125134
"transaction_access_list": EthereumTransactionAccessList.from_dict(obj["transaction_access_list"]) if obj.get("transaction_access_list") is not None else None,
135+
"token_transfers": [EthereumTokenTransfer.from_dict(_item) for _item in obj["token_transfers"]] if obj.get("token_transfers") is not None else None,
126136
"flattened_traces": [EthereumTransactionFlattenedTrace.from_dict(_item) for _item in obj["flattened_traces"]] if obj.get("flattened_traces") is not None else None,
127137
"block_timestamp": obj.get("block_timestamp"),
128138
"mint": obj.get("mint")

‎cdp/client/models/onchain_name.py

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# coding: utf-8
2+
3+
"""
4+
Coinbase Platform API
5+
6+
This is the OpenAPI 3.0 specification for the Coinbase Platform APIs, used in conjunction with the Coinbase Platform SDKs.
7+
8+
The version of the OpenAPI document: 0.0.1-alpha
9+
Generated by OpenAPI Generator (https://openapi-generator.tech)
10+
11+
Do not edit the class manually.
12+
""" # noqa: E501
13+
14+
15+
from __future__ import annotations
16+
import pprint
17+
import re # noqa: F401
18+
import json
19+
20+
from pydantic import BaseModel, ConfigDict, Field, StrictStr
21+
from typing import Any, ClassVar, Dict, List, Optional
22+
from cdp.client.models.onchain_name_text_records_inner import OnchainNameTextRecordsInner
23+
from typing import Optional, Set
24+
from typing_extensions import Self
25+
26+
class OnchainName(BaseModel):
27+
"""
28+
A representation of an onchain stored name from name systems i.e. ENS or Basenames
29+
""" # noqa: E501
30+
token_id: StrictStr = Field(description="The ID for the NFT related to this name")
31+
owner_address: StrictStr = Field(description="The onchain address of the owner of the name")
32+
manager_address: StrictStr = Field(description="The onchain address of the manager of the name")
33+
primary_address: Optional[StrictStr] = Field(default=None, description="The primary onchain address of the name")
34+
domain: StrictStr = Field(description="The readable format for the name in complete form")
35+
avatar: Optional[StrictStr] = Field(default=None, description="The visual representation attached to this name")
36+
network_id: StrictStr = Field(description="The ID of the blockchain network")
37+
text_records: Optional[List[OnchainNameTextRecordsInner]] = Field(default=None, description="The metadata attached to this name")
38+
__properties: ClassVar[List[str]] = ["token_id", "owner_address", "manager_address", "primary_address", "domain", "avatar", "network_id", "text_records"]
39+
40+
model_config = ConfigDict(
41+
populate_by_name=True,
42+
validate_assignment=True,
43+
protected_namespaces=(),
44+
)
45+
46+
47+
def to_str(self) -> str:
48+
"""Returns the string representation of the model using alias"""
49+
return pprint.pformat(self.model_dump(by_alias=True))
50+
51+
def to_json(self) -> str:
52+
"""Returns the JSON representation of the model using alias"""
53+
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
54+
return json.dumps(self.to_dict())
55+
56+
@classmethod
57+
def from_json(cls, json_str: str) -> Optional[Self]:
58+
"""Create an instance of OnchainName from a JSON string"""
59+
return cls.from_dict(json.loads(json_str))
60+
61+
def to_dict(self) -> Dict[str, Any]:
62+
"""Return the dictionary representation of the model using alias.
63+
64+
This has the following differences from calling pydantic's
65+
`self.model_dump(by_alias=True)`:
66+
67+
* `None` is only added to the output dict for nullable fields that
68+
were set at model initialization. Other fields with value `None`
69+
are ignored.
70+
"""
71+
excluded_fields: Set[str] = set([
72+
])
73+
74+
_dict = self.model_dump(
75+
by_alias=True,
76+
exclude=excluded_fields,
77+
exclude_none=True,
78+
)
79+
# override the default output from pydantic by calling `to_dict()` of each item in text_records (list)
80+
_items = []
81+
if self.text_records:
82+
for _item_text_records in self.text_records:
83+
if _item_text_records:
84+
_items.append(_item_text_records.to_dict())
85+
_dict['text_records'] = _items
86+
return _dict
87+
88+
@classmethod
89+
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
90+
"""Create an instance of OnchainName from a dict"""
91+
if obj is None:
92+
return None
93+
94+
if not isinstance(obj, dict):
95+
return cls.model_validate(obj)
96+
97+
_obj = cls.model_validate({
98+
"token_id": obj.get("token_id"),
99+
"owner_address": obj.get("owner_address"),
100+
"manager_address": obj.get("manager_address"),
101+
"primary_address": obj.get("primary_address"),
102+
"domain": obj.get("domain"),
103+
"avatar": obj.get("avatar"),
104+
"network_id": obj.get("network_id"),
105+
"text_records": [OnchainNameTextRecordsInner.from_dict(_item) for _item in obj["text_records"]] if obj.get("text_records") is not None else None
106+
})
107+
return _obj
108+
109+
+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# coding: utf-8
2+
3+
"""
4+
Coinbase Platform API
5+
6+
This is the OpenAPI 3.0 specification for the Coinbase Platform APIs, used in conjunction with the Coinbase Platform SDKs.
7+
8+
The version of the OpenAPI document: 0.0.1-alpha
9+
Generated by OpenAPI Generator (https://openapi-generator.tech)
10+
11+
Do not edit the class manually.
12+
""" # noqa: E501
13+
14+
15+
from __future__ import annotations
16+
import pprint
17+
import re # noqa: F401
18+
import json
19+
20+
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
21+
from typing import Any, ClassVar, Dict, List, Optional
22+
from cdp.client.models.onchain_name import OnchainName
23+
from typing import Optional, Set
24+
from typing_extensions import Self
25+
26+
class OnchainNameList(BaseModel):
27+
"""
28+
A list of onchain events with pagination information
29+
""" # noqa: E501
30+
data: List[OnchainName] = Field(description="A list of onchain name objects")
31+
has_more: Optional[StrictBool] = Field(default=None, description="True if this list has another page of items after this one that can be fetched.")
32+
next_page: StrictStr = Field(description="The page token to be used to fetch the next page.")
33+
total_count: Optional[StrictInt] = Field(default=None, description="The total number of payload signatures for the address.")
34+
__properties: ClassVar[List[str]] = ["data", "has_more", "next_page", "total_count"]
35+
36+
model_config = ConfigDict(
37+
populate_by_name=True,
38+
validate_assignment=True,
39+
protected_namespaces=(),
40+
)
41+
42+
43+
def to_str(self) -> str:
44+
"""Returns the string representation of the model using alias"""
45+
return pprint.pformat(self.model_dump(by_alias=True))
46+
47+
def to_json(self) -> str:
48+
"""Returns the JSON representation of the model using alias"""
49+
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
50+
return json.dumps(self.to_dict())
51+
52+
@classmethod
53+
def from_json(cls, json_str: str) -> Optional[Self]:
54+
"""Create an instance of OnchainNameList from a JSON string"""
55+
return cls.from_dict(json.loads(json_str))
56+
57+
def to_dict(self) -> Dict[str, Any]:
58+
"""Return the dictionary representation of the model using alias.
59+
60+
This has the following differences from calling pydantic's
61+
`self.model_dump(by_alias=True)`:
62+
63+
* `None` is only added to the output dict for nullable fields that
64+
were set at model initialization. Other fields with value `None`
65+
are ignored.
66+
"""
67+
excluded_fields: Set[str] = set([
68+
])
69+
70+
_dict = self.model_dump(
71+
by_alias=True,
72+
exclude=excluded_fields,
73+
exclude_none=True,
74+
)
75+
# override the default output from pydantic by calling `to_dict()` of each item in data (list)
76+
_items = []
77+
if self.data:
78+
for _item_data in self.data:
79+
if _item_data:
80+
_items.append(_item_data.to_dict())
81+
_dict['data'] = _items
82+
return _dict
83+
84+
@classmethod
85+
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
86+
"""Create an instance of OnchainNameList from a dict"""
87+
if obj is None:
88+
return None
89+
90+
if not isinstance(obj, dict):
91+
return cls.model_validate(obj)
92+
93+
_obj = cls.model_validate({
94+
"data": [OnchainName.from_dict(_item) for _item in obj["data"]] if obj.get("data") is not None else None,
95+
"has_more": obj.get("has_more"),
96+
"next_page": obj.get("next_page"),
97+
"total_count": obj.get("total_count")
98+
})
99+
return _obj
100+
101+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# coding: utf-8
2+
3+
"""
4+
Coinbase Platform API
5+
6+
This is the OpenAPI 3.0 specification for the Coinbase Platform APIs, used in conjunction with the Coinbase Platform SDKs.
7+
8+
The version of the OpenAPI document: 0.0.1-alpha
9+
Generated by OpenAPI Generator (https://openapi-generator.tech)
10+
11+
Do not edit the class manually.
12+
""" # noqa: E501
13+
14+
15+
from __future__ import annotations
16+
import pprint
17+
import re # noqa: F401
18+
import json
19+
20+
from pydantic import BaseModel, ConfigDict, Field, StrictStr
21+
from typing import Any, ClassVar, Dict, List, Optional
22+
from typing import Optional, Set
23+
from typing_extensions import Self
24+
25+
class OnchainNameTextRecordsInner(BaseModel):
26+
"""
27+
OnchainNameTextRecordsInner
28+
""" # noqa: E501
29+
key: Optional[StrictStr] = Field(default=None, description="The key for the text record")
30+
value: Optional[StrictStr] = Field(default=None, description="The value for the text record")
31+
__properties: ClassVar[List[str]] = ["key", "value"]
32+
33+
model_config = ConfigDict(
34+
populate_by_name=True,
35+
validate_assignment=True,
36+
protected_namespaces=(),
37+
)
38+
39+
40+
def to_str(self) -> str:
41+
"""Returns the string representation of the model using alias"""
42+
return pprint.pformat(self.model_dump(by_alias=True))
43+
44+
def to_json(self) -> str:
45+
"""Returns the JSON representation of the model using alias"""
46+
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
47+
return json.dumps(self.to_dict())
48+
49+
@classmethod
50+
def from_json(cls, json_str: str) -> Optional[Self]:
51+
"""Create an instance of OnchainNameTextRecordsInner from a JSON string"""
52+
return cls.from_dict(json.loads(json_str))
53+
54+
def to_dict(self) -> Dict[str, Any]:
55+
"""Return the dictionary representation of the model using alias.
56+
57+
This has the following differences from calling pydantic's
58+
`self.model_dump(by_alias=True)`:
59+
60+
* `None` is only added to the output dict for nullable fields that
61+
were set at model initialization. Other fields with value `None`
62+
are ignored.
63+
"""
64+
excluded_fields: Set[str] = set([
65+
])
66+
67+
_dict = self.model_dump(
68+
by_alias=True,
69+
exclude=excluded_fields,
70+
exclude_none=True,
71+
)
72+
return _dict
73+
74+
@classmethod
75+
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
76+
"""Create an instance of OnchainNameTextRecordsInner from a dict"""
77+
if obj is None:
78+
return None
79+
80+
if not isinstance(obj, dict):
81+
return cls.model_validate(obj)
82+
83+
_obj = cls.model_validate({
84+
"key": obj.get("key"),
85+
"value": obj.get("value")
86+
})
87+
return _obj
88+
89+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# coding: utf-8
2+
3+
"""
4+
Coinbase Platform API
5+
6+
This is the OpenAPI 3.0 specification for the Coinbase Platform APIs, used in conjunction with the Coinbase Platform SDKs.
7+
8+
The version of the OpenAPI document: 0.0.1-alpha
9+
Generated by OpenAPI Generator (https://openapi-generator.tech)
10+
11+
Do not edit the class manually.
12+
""" # noqa: E501
13+
14+
15+
from __future__ import annotations
16+
import pprint
17+
import re # noqa: F401
18+
import json
19+
20+
from pydantic import BaseModel, ConfigDict, Field, StrictStr
21+
from typing import Any, ClassVar, Dict, List, Optional
22+
from typing import Optional, Set
23+
from typing_extensions import Self
24+
25+
class ReadSmartContractRequest(BaseModel):
26+
"""
27+
ReadSmartContractRequest
28+
""" # noqa: E501
29+
method: StrictStr = Field(description="The name of the contract method to call")
30+
args: List[StrictStr] = Field(description="The arguments to pass to the contract method")
31+
abi: Optional[StrictStr] = Field(default=None, description="The JSON-encoded ABI of the contract method (optional, will use cached ABI if not provided)")
32+
__properties: ClassVar[List[str]] = ["method", "args", "abi"]
33+
34+
model_config = ConfigDict(
35+
populate_by_name=True,
36+
validate_assignment=True,
37+
protected_namespaces=(),
38+
)
39+
40+
41+
def to_str(self) -> str:
42+
"""Returns the string representation of the model using alias"""
43+
return pprint.pformat(self.model_dump(by_alias=True))
44+
45+
def to_json(self) -> str:
46+
"""Returns the JSON representation of the model using alias"""
47+
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
48+
return json.dumps(self.to_dict())
49+
50+
@classmethod
51+
def from_json(cls, json_str: str) -> Optional[Self]:
52+
"""Create an instance of ReadSmartContractRequest from a JSON string"""
53+
return cls.from_dict(json.loads(json_str))
54+
55+
def to_dict(self) -> Dict[str, Any]:
56+
"""Return the dictionary representation of the model using alias.
57+
58+
This has the following differences from calling pydantic's
59+
`self.model_dump(by_alias=True)`:
60+
61+
* `None` is only added to the output dict for nullable fields that
62+
were set at model initialization. Other fields with value `None`
63+
are ignored.
64+
"""
65+
excluded_fields: Set[str] = set([
66+
])
67+
68+
_dict = self.model_dump(
69+
by_alias=True,
70+
exclude=excluded_fields,
71+
exclude_none=True,
72+
)
73+
return _dict
74+
75+
@classmethod
76+
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
77+
"""Create an instance of ReadSmartContractRequest from a dict"""
78+
if obj is None:
79+
return None
80+
81+
if not isinstance(obj, dict):
82+
return cls.model_validate(obj)
83+
84+
_obj = cls.model_validate({
85+
"method": obj.get("method"),
86+
"args": obj.get("args"),
87+
"abi": obj.get("abi")
88+
})
89+
return _obj
90+
91+
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# coding: utf-8
2+
3+
"""
4+
Coinbase Platform API
5+
6+
This is the OpenAPI 3.0 specification for the Coinbase Platform APIs, used in conjunction with the Coinbase Platform SDKs.
7+
8+
The version of the OpenAPI document: 0.0.1-alpha
9+
Generated by OpenAPI Generator (https://openapi-generator.tech)
10+
11+
Do not edit the class manually.
12+
""" # noqa: E501
13+
14+
15+
from __future__ import annotations
16+
import json
17+
from enum import Enum
18+
from typing_extensions import Self
19+
20+
21+
class TokenTransferType(str, Enum):
22+
"""
23+
The type of the token transfer.
24+
"""
25+
26+
"""
27+
allowed enum values
28+
"""
29+
ERC20 = 'erc20'
30+
ERC721 = 'erc721'
31+
ERC1155 = 'erc1155'
32+
UNKNOWN = 'unknown'
33+
34+
@classmethod
35+
def from_json(cls, json_str: str) -> Self:
36+
"""Create an instance of TokenTransferType from a JSON string"""
37+
return cls(json.loads(json_str))
38+
39+

‎cdp/wallet.py

+22
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
CreateWalletRequest,
2525
CreateWalletRequestWallet,
2626
)
27+
from cdp.client.models.create_wallet_webhook_request import CreateWalletWebhookRequest
2728
from cdp.client.models.wallet import Wallet as WalletModel
2829
from cdp.client.models.wallet_list import WalletList
2930
from cdp.contract_invocation import ContractInvocation
@@ -33,6 +34,7 @@
3334
from cdp.trade import Trade
3435
from cdp.wallet_address import WalletAddress
3536
from cdp.wallet_data import WalletData
37+
from cdp.webhook import Webhook
3638

3739

3840
class Wallet:
@@ -286,6 +288,26 @@ def create_address(self) -> "WalletAddress":
286288

287289
return wallet_address
288290

291+
def create_webhook(self, notification_uri: str) -> "Webhook":
292+
"""Create a new webhook for the wallet.
293+
294+
Args:
295+
notification_uri (str): The notification URI of the webhook.
296+
297+
Returns:
298+
Webhook: The created webhook object. It can be used to monitor activities happening in the wallet. When they occur, webhook will make a request to the specified URI.
299+
300+
Raises:
301+
Exception: If there's an error creating the webhook.
302+
303+
"""
304+
create_wallet_webhook_request = CreateWalletWebhookRequest(notification_uri = notification_uri)
305+
model = Cdp.api_clients.webhooks.create_wallet_webhook(
306+
wallet_id=self.id, create_wallet_webhook_request=create_wallet_webhook_request
307+
)
308+
309+
return Webhook(model)
310+
289311
def faucet(self, asset_id: str | None = None) -> FaucetTransaction:
290312
"""Request faucet funds.
291313

‎cdp/webhook.py

+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
from collections.abc import Iterator
2+
3+
from cdp.cdp import Cdp
4+
from cdp.client.models.create_webhook_request import CreateWebhookRequest
5+
from cdp.client.models.update_webhook_request import UpdateWebhookRequest
6+
from cdp.client.models.webhook import Webhook as WebhookModel
7+
from cdp.client.models.webhook import WebhookEventFilter, WebhookEventType, WebhookEventTypeFilter
8+
from cdp.client.models.webhook_list import WebhookList
9+
10+
11+
class Webhook:
12+
"""A class representing a webhook."""
13+
14+
def __init__(self, model: WebhookModel) -> None:
15+
"""Initialize the Webhook class.
16+
17+
Args:
18+
model (WebhookModel): The WebhookModel object representing the Webhook.
19+
20+
"""
21+
self._model = model
22+
23+
@property
24+
def id(self) -> str:
25+
"""Get the ID of the webhook.
26+
27+
Returns:
28+
str: The ID of the webhook.
29+
30+
"""
31+
return self._model.id
32+
33+
@property
34+
def network_id(self) -> str:
35+
"""Get the network ID of the webhook.
36+
37+
Returns:
38+
str: The network ID of the webhook.
39+
40+
"""
41+
return self._model.network_id
42+
43+
@property
44+
def notification_uri(self) -> str:
45+
"""Get the notification URI of the webhook.
46+
47+
Returns:
48+
str: The notification URI of the webhook.
49+
50+
"""
51+
return self._model.notification_uri
52+
53+
@property
54+
def event_type(self) -> WebhookEventType:
55+
"""Get the event type of the webhook.
56+
57+
Returns:
58+
str: The event type of the webhook.
59+
60+
"""
61+
return self._model.event_type
62+
63+
@property
64+
def event_type_filter(self) -> WebhookEventTypeFilter:
65+
"""Get the event type filter of the webhook.
66+
67+
Returns:
68+
str: The event type filter of the webhook.
69+
70+
"""
71+
return self._model.event_type_filter
72+
73+
@property
74+
def event_filters(self) -> list[WebhookEventFilter]:
75+
"""Get the event filters of the webhook.
76+
77+
Returns:
78+
str: The event filters of the webhook.
79+
80+
"""
81+
return self._model.event_filters
82+
83+
@classmethod
84+
def create(
85+
cls,
86+
notification_uri: str,
87+
event_type: WebhookEventType,
88+
event_type_filter: WebhookEventTypeFilter | None = None,
89+
event_filters: list[WebhookEventFilter] | None = None,
90+
network_id: str = "base-sepolia",
91+
) -> "Webhook":
92+
"""Create a new webhook.
93+
94+
Args:
95+
notification_uri (str): The URI where notifications should be sent.
96+
event_type (WebhookEventType): The type of event that the webhook listens to.
97+
event_type_filter (WebhookEventTypeFilter): Filter specifically for wallet activity event type.
98+
event_filters (List[WebhookEventTypeFilter]): Filters applied to the events that determine which specific address(es) trigger.
99+
network_id (str): The network ID of the wallet. Defaults to "base-sepolia".
100+
101+
Returns:
102+
Webhook: The created webhook object.
103+
104+
"""
105+
create_webhook_request = CreateWebhookRequest(
106+
network_id=network_id,
107+
event_type=event_type,
108+
event_type_filter=event_type_filter,
109+
event_filters=event_filters,
110+
notification_uri=notification_uri,
111+
)
112+
113+
model = Cdp.api_clients.webhooks.create_webhook(create_webhook_request)
114+
webhook = cls(model)
115+
116+
return webhook
117+
118+
@classmethod
119+
def list(cls) -> Iterator["Webhook"]:
120+
"""List webhooks.
121+
122+
Returns:
123+
Iterator[Webhook]: An iterator of webhook objects.
124+
125+
"""
126+
while True:
127+
page = None
128+
129+
response: WebhookList = Cdp.api_clients.webhooks.list_webhooks(limit=100, page=page)
130+
131+
for webhook_model in response.data:
132+
yield cls(webhook_model)
133+
134+
if not response.has_more:
135+
break
136+
137+
page = response.next_page
138+
139+
@staticmethod
140+
def delete(webhook_id: str) -> None:
141+
"""Delete a webhook by its ID.
142+
143+
Args:
144+
webhook_id (str): The ID of the webhook to delete.
145+
146+
"""
147+
Cdp.api_clients.webhooks.delete_webhook(webhook_id)
148+
149+
def update(
150+
self,
151+
notification_uri: str | None = None,
152+
event_type_filter: WebhookEventTypeFilter | None = None
153+
) -> "Webhook":
154+
"""Update the webhook with a new notification URI, and/or a new list of addresses to monitor.
155+
156+
Args:
157+
notification_uri (str): The new URI for webhook notifications.
158+
event_type_filter (WebhookEventTypeFilter): The new eventTypeFilter that contains a new list (replacement) of addresses to monitor for the webhook.
159+
160+
Returns:
161+
Webhook: The updated webhook object.
162+
163+
"""
164+
# Fallback to current properties if no new values are provided
165+
final_notification_uri = notification_uri or self.notification_uri
166+
final_event_type_filter = event_type_filter or self.event_type_filter
167+
168+
update_webhook_request = UpdateWebhookRequest(
169+
event_type_filter=final_event_type_filter,
170+
event_filters=self.event_filters,
171+
notification_uri=final_notification_uri,
172+
)
173+
174+
# Update the webhook via the API client
175+
result = Cdp.api_clients.webhooks.update_webhook(
176+
self.id,
177+
update_webhook_request,
178+
)
179+
180+
# Update the internal model with the API response
181+
self._model = result
182+
183+
return self
184+
185+
def __str__(self) -> str:
186+
"""Return a string representation of the Webhook object.
187+
188+
Returns:
189+
str: A string representation of the Webhook.
190+
191+
"""
192+
return f"Webhook: (id: {self.id}, network_id: {self.network_id}, notification_uri: {self.notification_uri}, event_type: {self.event_type}, event_type_filter: {self.event_type_filter}, event_filters: {self.event_filters})"
193+
194+
def __repr__(self) -> str:
195+
"""Return a detailed string representation of the Webhook object.
196+
197+
Returns:
198+
str: A string that represents the Webhook object.
199+
200+
"""
201+
return str(self)

‎docs/conf.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
project = 'CDP SDK'
1616
author = 'Coinbase Developer Platform'
17-
release = '0.0.6'
17+
release = '0.0.7'
1818

1919
# -- General configuration ---------------------------------------------------
2020
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

‎pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "cdp-sdk"
3-
version = "0.0.6"
3+
version = "0.0.7"
44
description = "CDP Python SDK"
55
readme = "README.md"
66
authors = [{name = "John Peterson", email = "john.peterson@coinbase.com"}]

‎tests/factories/webhook_factory.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import pytest
2+
3+
from cdp.webhook import Webhook, WebhookEventType, WebhookModel
4+
5+
6+
@pytest.fixture
7+
def webhook_factory():
8+
"""Create and return a factory for Webhook fixtures."""
9+
def _create_webhook(
10+
webhook_id="webhook-123",
11+
network_id="base-sepolia",
12+
notification_uri="https://example.com/webhook",
13+
event_type=WebhookEventType.WALLET_ACTIVITY,
14+
event_type_filter=None,
15+
event_filters=None
16+
):
17+
model = WebhookModel(
18+
id=webhook_id,
19+
network_id=network_id,
20+
notification_uri=notification_uri,
21+
event_type=event_type,
22+
event_type_filter=event_type_filter,
23+
event_filters=event_filters or []
24+
)
25+
return Webhook(model)
26+
27+
return _create_webhook

‎tests/test_wallet.py

+31
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77

88
from cdp.client.models.create_address_request import CreateAddressRequest
99
from cdp.client.models.create_wallet_request import CreateWalletRequest, CreateWalletRequestWallet
10+
from cdp.client.models.create_wallet_webhook_request import CreateWalletWebhookRequest
1011
from cdp.contract_invocation import ContractInvocation
1112
from cdp.payload_signature import PayloadSignature
1213
from cdp.smart_contract import SmartContract
1314
from cdp.trade import Trade
1415
from cdp.transfer import Transfer
1516
from cdp.wallet import Wallet
1617
from cdp.wallet_address import WalletAddress
18+
from cdp.webhook import Webhook
1719

1820

1921
@patch("cdp.Cdp.use_server_signer", False)
@@ -614,3 +616,32 @@ def test_wallet_deploy_multi_token_with_server_signer(wallet_factory):
614616
mock_default_address.deploy_multi_token.assert_called_once_with(
615617
"https://example.com/multi-token/{id}.json"
616618
)
619+
620+
@patch("cdp.Cdp.api_clients")
621+
def test_create_webhook(mock_api_clients, wallet_factory, webhook_factory):
622+
"""Test Wallet create_webhook method."""
623+
mock_api_clients.webhooks.create_wallet_webhook.return_value = webhook_factory()
624+
625+
# Create a wallet instance using the factory
626+
wallet = wallet_factory()
627+
628+
# Define the notification URI to pass into the create_webhook method
629+
notification_uri = "https://example.com/webhook"
630+
631+
# Call the create_webhook method
632+
webhook = wallet.create_webhook(notification_uri)
633+
634+
# Create the expected request object
635+
expected_request = CreateWalletWebhookRequest(notification_uri=notification_uri)
636+
637+
# Assert that the API client was called with the correct parameters
638+
mock_api_clients.webhooks.create_wallet_webhook.assert_called_once_with(
639+
wallet_id=wallet.id,
640+
create_wallet_webhook_request=expected_request
641+
)
642+
643+
# Assert that the returned webhook is an instance of Webhook
644+
assert isinstance(webhook, Webhook)
645+
646+
# Additional assertions to check the returned webhook object
647+
assert webhook.notification_uri == notification_uri

‎tests/test_webhook.py

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from unittest.mock import patch
2+
3+
from cdp.client.models.create_webhook_request import CreateWebhookRequest
4+
from cdp.client.models.update_webhook_request import UpdateWebhookRequest
5+
from cdp.client.models.webhook import WebhookEventFilter, WebhookEventType, WebhookEventTypeFilter
6+
from cdp.webhook import Webhook, WebhookModel
7+
8+
9+
@patch("cdp.Cdp.api_clients")
10+
def test_webhook_creation(mock_api_clients, webhook_factory):
11+
"""Test Webhook creation method."""
12+
mock_api_clients.webhooks.create_webhook.return_value = webhook_factory()
13+
14+
# Define input parameters for the webhook creation
15+
notification_uri = "https://example.com/webhook"
16+
event_type = WebhookEventType.WALLET_ACTIVITY
17+
event_type_filter = WebhookEventTypeFilter()
18+
event_filters = [WebhookEventFilter()]
19+
20+
expected_request = CreateWebhookRequest(
21+
network_id="base-sepolia",
22+
event_type=event_type,
23+
event_type_filter=event_type_filter,
24+
event_filters=event_filters,
25+
notification_uri=notification_uri
26+
)
27+
28+
webhook = Webhook.create(
29+
notification_uri=notification_uri,
30+
event_type=event_type,
31+
event_type_filter=event_type_filter,
32+
event_filters=event_filters,
33+
network_id="base-sepolia"
34+
)
35+
36+
mock_api_clients.webhooks.create_webhook.assert_called_once_with(expected_request)
37+
38+
# Check that the returned object is a Webhook instance
39+
assert isinstance(webhook, Webhook)
40+
assert webhook.notification_uri == notification_uri
41+
assert webhook.event_type == event_type
42+
43+
44+
@patch("cdp.Cdp.api_clients")
45+
def test_webhook_delete(mock_api_clients):
46+
"""Test Webhook delete method."""
47+
webhook_id = "webhook-123"
48+
49+
Webhook.delete(webhook_id)
50+
51+
mock_api_clients.webhooks.delete_webhook.assert_called_once_with(webhook_id)
52+
53+
54+
@patch("cdp.Cdp.api_clients")
55+
def test_webhook_update(mock_api_clients, webhook_factory):
56+
"""Test Webhook update method."""
57+
webhook_model = webhook_factory()
58+
59+
# Create a Webhook instance
60+
webhook = Webhook(model=webhook_model)
61+
62+
assert webhook.notification_uri == "https://example.com/webhook"
63+
64+
# Define new values for the update
65+
new_notification_uri = "https://new.example.com/webhook"
66+
67+
# Mock the API response for update
68+
mock_api_clients.webhooks.update_webhook.return_value = WebhookModel(
69+
id=webhook.id,
70+
network_id=webhook.network_id,
71+
notification_uri=new_notification_uri,
72+
event_type=webhook.event_type,
73+
event_type_filter=webhook.event_type_filter,
74+
event_filters=webhook.event_filters
75+
)
76+
77+
expected_request = UpdateWebhookRequest(
78+
event_type_filter=webhook.event_type_filter,
79+
event_filters=webhook.event_filters,
80+
notification_uri=new_notification_uri
81+
)
82+
83+
updated_webhook_model = webhook.update(
84+
notification_uri=new_notification_uri,
85+
)
86+
87+
updated_webhook = Webhook(model=updated_webhook_model)
88+
89+
# Verify that the API client was called with the correct arguments
90+
mock_api_clients.webhooks.update_webhook.assert_called_once_with(
91+
webhook.id, expected_request
92+
)
93+
94+
# Assert that the returned object is the updated webhook
95+
assert isinstance(updated_webhook, Webhook)
96+
assert updated_webhook.notification_uri == new_notification_uri
97+
assert updated_webhook.id == webhook.id

0 commit comments

Comments
 (0)
Please sign in to comment.