agentstr 0.1.11__py3-none-any.whl → 0.1.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,288 +1,22 @@
1
- import ast
2
1
  import json
3
2
  import logging
4
- import re
5
- from typing import Any, Dict, List, Optional, Tuple, Union, cast
6
-
7
- from agentstr.nostr import (
8
- EventId,
9
- Keys,
10
- NostrClient,
11
- ProductData,
12
- ShippingCost,
13
- ShippingMethod,
14
- StallData,
15
- )
3
+ import time
4
+ from typing import Any, List, Optional, Tuple, Union
5
+
6
+ from agentstr.models import AgentProfile, MerchantProduct, MerchantStall
7
+ from agentstr.nostr import EventId, NostrClient
16
8
 
17
9
  try:
18
- from phi.tools import Toolkit
10
+ from agno.tools import Toolkit
19
11
  except ImportError:
20
- raise ImportError(
21
- "`phidata` not installed. Please install using `pip install phidata`"
22
- )
23
-
24
- from pydantic import BaseModel, ConfigDict, Field, validate_call
25
-
26
-
27
- class Profile:
28
-
29
- logger = logging.getLogger("Profile")
30
- WEB_URL: str = "https://primal.net/p/"
31
-
32
- def __init__(self, name: str, about: str, picture: str, nsec: Optional[str] = None):
33
- """Initialize the profile.
34
-
35
- Args:
36
- name: Name for the merchant
37
- about: brief description about the merchant
38
- picture: url to a png file with a picture for the merchant
39
- nsec: optional private key to be used by this Merchant
40
- """
41
-
42
- # Set log handling for MerchantProfile
43
- if not Profile.logger.hasHandlers():
44
- console_handler = logging.StreamHandler()
45
- console_handler.setLevel(logging.INFO)
46
- formatter = logging.Formatter(
47
- "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
48
- )
49
- console_handler.setFormatter(formatter)
50
- Profile.logger.addHandler(console_handler)
51
-
52
- self.name = name
53
- self.about = about
54
- self.picture = picture
55
-
56
- if nsec:
57
- self.private_key = nsec
58
- keys = Keys.parse(self.private_key)
59
- self.public_key = keys.public_key().to_bech32()
60
- Profile.logger.info(
61
- f"Pre-defined private key reused for {self.name}: {self.private_key}"
62
- )
63
- Profile.logger.info(
64
- f"Pre-defined public key reused for {self.name}: {self.public_key}"
65
- )
66
- else:
67
- keys = Keys.generate()
68
- self.private_key = keys.secret_key().to_bech32()
69
- self.public_key = keys.public_key().to_bech32()
70
- Profile.logger.info(
71
- f"New private key created for {self.name}: {self.private_key}"
72
- )
73
- Profile.logger.info(
74
- f"New public key created for {self.name}: {self.public_key}"
75
- )
76
-
77
- self.url = str(self.WEB_URL) + str(self.public_key)
78
-
79
- def __str__(self) -> str:
80
- return (
81
- "Merchant Profile:\n"
82
- "Name = {}\n"
83
- "Description = {}\n"
84
- "Picture = {}\n"
85
- "URL = {}\n"
86
- "Private key = {}\n"
87
- "Public key = {}".format(
88
- self.name,
89
- self.about,
90
- self.picture,
91
- self.url,
92
- self.private_key,
93
- self.public_key,
94
- )
95
- )
96
-
97
- def to_dict(self) -> dict:
98
- return {
99
- "name": self.name,
100
- "description": self.about,
101
- "picture": self.picture,
102
- "public key": self.public_key,
103
- "private key": self.private_key,
104
- }
105
-
106
- def get_about(self) -> str:
107
- """
108
- Returns a description of the Merchant
109
-
110
- Returns:
111
- str: description of the Merchant
112
- """
113
- return self.about
114
-
115
- def get_name(self) -> str:
116
- """
117
- Returns the Merchant's name
12
+ raise ImportError("`agno` not installed. Please install using `pip install agno`")
118
13
 
119
- Returns:
120
- str: Merchant's name
121
- """
122
- return self.name
123
-
124
- def get_picture(self) -> str:
125
- """
126
- Returns the picture associated with the Merchant.
127
-
128
- Returns:
129
- str: URL to the picture associated with the Merchant
130
- """
131
- return self.picture
132
-
133
- def get_private_key(self) -> str:
134
- """
135
- Returns the private key.
136
-
137
- Returns:
138
- str: private key in bech32 format
139
- """
140
- return str(self.private_key)
141
-
142
- def get_public_key(self) -> str:
143
- """
144
- Returns the public key.
14
+ from pydantic import BaseModel, ConfigDict
145
15
 
146
- Returns:
147
- str: public key in bech32 format
148
- """
149
- return str(self.public_key)
150
-
151
- def get_url(self) -> str:
152
- return str(self.url)
153
-
154
-
155
- class MerchantProduct(BaseModel):
156
- model_config = ConfigDict(arbitrary_types_allowed=True)
157
-
158
- id: str
159
- stall_id: str
160
- name: str
161
- description: str
162
- images: List[str]
163
- currency: str
164
- price: float
165
- quantity: int
166
- shipping: List[ShippingCost]
167
- categories: Optional[List[str]] = []
168
- specs: Optional[List[List[str]]] = []
169
-
170
- @classmethod
171
- def from_product_data(cls, product: ProductData) -> "MerchantProduct":
172
- return cls(
173
- id=product.id,
174
- stall_id=product.stall_id,
175
- name=product.name,
176
- description=product.description,
177
- images=product.images,
178
- currency=product.currency,
179
- price=product.price,
180
- quantity=product.quantity,
181
- shipping=product.shipping,
182
- categories=product.categories if product.categories is not None else [],
183
- specs=product.specs if product.specs is not None else [],
184
- )
185
-
186
- def to_product_data(self) -> ProductData:
187
- return ProductData(
188
- id=self.id,
189
- stall_id=self.stall_id,
190
- name=self.name,
191
- description=self.description,
192
- images=self.images,
193
- currency=self.currency,
194
- price=self.price,
195
- quantity=self.quantity,
196
- shipping=self.shipping,
197
- categories=self.categories,
198
- specs=self.specs,
199
- )
200
-
201
- def to_dict(self) -> dict:
202
- """
203
- Returns a dictionary representation of the MerchantProduct.
204
- ShippingCost class is not serializable, so we need to convert it to a dictionary.
205
-
206
- Returns:
207
- dict: dictionary representation of the MerchantProduct
208
- """
209
- shipping_dicts = []
210
- for shipping in self.shipping:
211
- shipping_dicts.append({"id": shipping.id, "cost": shipping.cost})
212
-
213
- return {
214
- "id": self.id,
215
- "stall_id": self.stall_id,
216
- "name": self.name,
217
- "description": self.description,
218
- "images": self.images,
219
- "currency": self.currency,
220
- "price": self.price,
221
- "quantity": self.quantity,
222
- "shipping": shipping_dicts,
223
- "categories": self.categories,
224
- "specs": self.specs,
225
- }
226
-
227
-
228
- class MerchantStall(BaseModel):
229
- model_config = ConfigDict(arbitrary_types_allowed=True)
230
-
231
- id: str
232
- name: str
233
- description: str
234
- currency: str
235
- shipping: List[ShippingMethod]
236
-
237
- @classmethod
238
- def from_stall_data(cls, stall: StallData) -> "MerchantStall":
239
- return cls(
240
- id=stall.id(),
241
- name=stall.name(),
242
- description=stall.description(),
243
- currency=stall.currency(),
244
- shipping=stall.shipping(),
245
- )
246
-
247
- def to_stall_data(self) -> StallData:
248
- return StallData(
249
- self.id,
250
- self.name,
251
- self.description,
252
- self.currency,
253
- self.shipping, # No conversion needed
254
- )
255
16
 
256
- def to_dict(self) -> dict:
257
- """
258
- Returns a dictionary representation of the MerchantStall.
259
- ShippingMethod class is not serializable, so we need to convert it to a dictionary.
260
- We can only access cost and id from the ShippingMethod class. We can't access name or regions.
261
-
262
- Returns:
263
- dict: dictionary representation of the MerchantStall
264
- """
265
- shipping_dicts = []
266
- for shipping in self.shipping:
267
- shipping_dicts.append(
268
- {
269
- "cost": shipping.get_shipping_cost().cost,
270
- "id": shipping.get_shipping_cost().id,
271
- }
272
- )
273
-
274
- return {
275
- "id": self.id,
276
- "name": self.name,
277
- "description": self.description,
278
- "currency": self.currency,
279
- "shipping zones": [shipping_dicts],
280
- }
281
-
282
-
283
- class Merchant(Toolkit):
17
+ class MerchantTools(Toolkit):
284
18
  """
285
- Merchant is a toolkit that allows a merchant to publish products and stalls to Nostr.
19
+ MerchantTools is a toolkit that allows a merchant to publish products and stalls to Nostr.
286
20
 
287
21
  TBD:
288
22
  - Better differentiation between products and stalls in the database and products and stalls published.
@@ -301,7 +35,7 @@ class Merchant(Toolkit):
301
35
 
302
36
  def __init__(
303
37
  self,
304
- merchant_profile: Profile,
38
+ merchant_profile: AgentProfile,
305
39
  relay: str,
306
40
  stalls: List[MerchantStall],
307
41
  products: List[MerchantProduct],
@@ -317,9 +51,7 @@ class Merchant(Toolkit):
317
51
  super().__init__(name="merchant")
318
52
  self.relay = relay
319
53
  self.merchant_profile = merchant_profile
320
- self._nostr_client = NostrClient(
321
- self.relay, self.merchant_profile.get_private_key()
322
- )
54
+ self._nostr_client = NostrClient(relay, merchant_profile.get_private_key())
323
55
 
324
56
  # initialize the Product DB with no event id
325
57
  self.product_db = [(product, None) for product in products]
@@ -327,7 +59,7 @@ class Merchant(Toolkit):
327
59
  # initialize the Stall DB with no event id
328
60
  self.stall_db = [(stall, None) for stall in stalls]
329
61
 
330
- # Register wrapped versions of the methods
62
+ # Register methods
331
63
  self.register(self.get_profile)
332
64
  self.register(self.get_relay)
333
65
  self.register(self.get_products)
@@ -347,19 +79,25 @@ class Merchant(Toolkit):
347
79
 
348
80
  def get_profile(self) -> str:
349
81
  """
350
- Retrieves merchant profile in JSON format
82
+ Get the merchant profile in JSON format
351
83
 
352
84
  Returns:
353
85
  str: merchant profile in JSON format
354
86
  """
355
- return json.dumps(self.merchant_profile.to_dict())
87
+ return json.dumps(self.merchant_profile.to_json())
356
88
 
357
89
  def get_relay(self) -> str:
90
+ """
91
+ Get the Nostr relay the merchant is using
92
+
93
+ Returns:
94
+ str: Nostr relay
95
+ """
358
96
  return self.relay
359
97
 
360
98
  def get_products(self) -> str:
361
99
  """
362
- Retrieves all the merchant products
100
+ Get all the merchant products
363
101
 
364
102
  Returns:
365
103
  str: JSON string containing all products
@@ -368,7 +106,7 @@ class Merchant(Toolkit):
368
106
 
369
107
  def get_stalls(self) -> str:
370
108
  """
371
- Retrieves all the merchant stalls in JSON format
109
+ Get all the merchant stalls in JSON format
372
110
 
373
111
  Returns:
374
112
  str: JSON string containing all stalls
@@ -379,10 +117,13 @@ class Merchant(Toolkit):
379
117
  self,
380
118
  ) -> str:
381
119
  """
382
- Publishes or updates all products in the Merchant's Product DB
120
+ Publishes or updates to Nostrall products in the Merchant's Product DB
383
121
 
384
122
  Returns:
385
123
  str: JSON array with status of all product publishing operations
124
+
125
+ Raises:
126
+ ValueError: if NostrClient is not initialized
386
127
  """
387
128
 
388
129
  if self._nostr_client is None:
@@ -393,9 +134,9 @@ class Merchant(Toolkit):
393
134
  for i, (product, _) in enumerate(self.product_db):
394
135
  try:
395
136
  # Convert MerchantProduct to ProductData for nostr_client
396
- product_data = product.to_product_data()
137
+ # product_data = product.to_product_data()
397
138
  # Publish using the SDK's synchronous method
398
- event_id = self._nostr_client.publish_product(product_data)
139
+ event_id = self._nostr_client.publish_product(product)
399
140
  self.product_db[i] = (product, event_id)
400
141
  results.append(
401
142
  {
@@ -404,8 +145,10 @@ class Merchant(Toolkit):
404
145
  "product_name": product.name,
405
146
  }
406
147
  )
148
+ # Pause for 0.5 seconds to avoid rate limiting
149
+ time.sleep(0.5)
407
150
  except Exception as e:
408
- Profile.logger.error(f"Unable to publish product {product}. Error {e}")
151
+ logging.error(f"Unable to publish product {product}. Error {e}")
409
152
  results.append(
410
153
  {"status": "error", "message": str(e), "product_name": product.name}
411
154
  )
@@ -416,10 +159,13 @@ class Merchant(Toolkit):
416
159
  self,
417
160
  ) -> str:
418
161
  """
419
- Publishes or updates all stalls managed by the merchant and adds the corresponding EventId to the Stall DB
162
+ Publishes or updates to Nostr all stalls managed by the merchant and adds the corresponding EventId to the Stall DB
420
163
 
421
164
  Returns:
422
165
  str: JSON array with status of all stall publishing operations
166
+
167
+ Raises:
168
+ ValueError: if NostrClient is not initialized
423
169
  """
424
170
  if self._nostr_client is None:
425
171
  raise ValueError("NostrClient not initialized")
@@ -427,9 +173,9 @@ class Merchant(Toolkit):
427
173
 
428
174
  for i, (stall, _) in enumerate(self.stall_db):
429
175
  try:
430
- # Convert MerchantStall to StallData for nostr_client
431
- stall_data = stall.to_stall_data()
432
- event_id = self._nostr_client.publish_stall(stall_data)
176
+ # We don't need to convert MerchantStall to StallData for nostr_client
177
+ # stall_data = stall.to_stall_data()
178
+ event_id = self._nostr_client.publish_stall(stall)
433
179
  self.stall_db[i] = (stall, event_id)
434
180
  results.append(
435
181
  {
@@ -438,8 +184,10 @@ class Merchant(Toolkit):
438
184
  "stall_name": stall.name,
439
185
  }
440
186
  )
187
+ # Pause for 0.5 seconds to avoid rate limiting
188
+ time.sleep(0.5)
441
189
  except Exception as e:
442
- Profile.logger.error(f"Unable to publish stall {stall}. Error {e}")
190
+ logging.error(f"Unable to publish stall {stall}. Error {e}")
443
191
  results.append(
444
192
  {"status": "error", "message": str(e), "stall_name": stall.name}
445
193
  )
@@ -448,13 +196,16 @@ class Merchant(Toolkit):
448
196
 
449
197
  def publish_new_product(self, product: MerchantProduct) -> str:
450
198
  """
451
- Publishes a new product that is not currently in the Merchant's Product DB and adds it to the Product DB
199
+ Publishes to Nostra new product that is not currently in the Merchant's Product DB and adds it to the Product DB
452
200
 
453
201
  Args:
454
202
  product: MerchantProduct to be published
455
203
 
456
204
  Returns:
457
205
  str: JSON string with status of the operation
206
+
207
+ Raises:
208
+ ValueError: if NostrClient is not initialized
458
209
  """
459
210
  if self._nostr_client is None:
460
211
  raise ValueError("NostrClient not initialized")
@@ -480,12 +231,15 @@ class Merchant(Toolkit):
480
231
 
481
232
  def publish_product_by_name(self, arguments: str) -> str:
482
233
  """
483
- Publishes or updates a given product from the Merchant's Product DB
234
+ Publishes or updates to Nostra given product from the Merchant's Product DB
484
235
  Args:
485
236
  arguments: JSON string that may contain {"name": "product_name"} or just "product_name"
486
237
 
487
238
  Returns:
488
239
  str: JSON string with status of the operation
240
+
241
+ Raises:
242
+ ValueError: if NostrClient is not initialized
489
243
  """
490
244
  if self._nostr_client is None:
491
245
  raise ValueError("NostrClient not initialized")
@@ -508,9 +262,9 @@ class Merchant(Toolkit):
508
262
  if product.name == name:
509
263
  try:
510
264
  # Convert MerchantProduct to ProductData for nostr_client
511
- product_data = product.to_product_data()
265
+ # product_data = product.to_product_data()
512
266
  # Publish using the SDK's synchronous method
513
- event_id = self._nostr_client.publish_product(product_data)
267
+ event_id = self._nostr_client.publish_product(product)
514
268
  # Update the product_db with the new event_id
515
269
  self.product_db[i] = (product, event_id)
516
270
  return json.dumps(
@@ -520,6 +274,8 @@ class Merchant(Toolkit):
520
274
  "product_name": product.name,
521
275
  }
522
276
  )
277
+ # Pause for 0.5 seconds to avoid rate limiting
278
+ time.sleep(0.5)
523
279
  except Exception as e:
524
280
  return json.dumps(
525
281
  {
@@ -540,7 +296,7 @@ class Merchant(Toolkit):
540
296
 
541
297
  def publish_products_by_stall_name(self, arguments: Union[str, dict]) -> str:
542
298
  """
543
- Publishes or updates all products sold by the merchant in a given stall
299
+ Publishes or updates to Nostr all products sold by the merchant in a given stall
544
300
 
545
301
  Args:
546
302
  arguments: str or dict with the stall name. Can be in formats:
@@ -550,6 +306,9 @@ class Merchant(Toolkit):
550
306
 
551
307
  Returns:
552
308
  str: JSON array with status of all product publishing operations
309
+
310
+ Raises:
311
+ ValueError: if NostrClient is not initialized
553
312
  """
554
313
  if self._nostr_client is None:
555
314
  raise ValueError("NostrClient not initialized")
@@ -604,8 +363,8 @@ class Merchant(Toolkit):
604
363
  for i, (product, _) in enumerate(self.product_db):
605
364
  if product.stall_id == stall_id:
606
365
  try:
607
- product_data = product.to_product_data()
608
- event_id = self._nostr_client.publish_product(product_data)
366
+ # product_data = product.to_product_data()
367
+ event_id = self._nostr_client.publish_product(product)
609
368
  self.product_db[i] = (product, event_id)
610
369
  results.append(
611
370
  {
@@ -615,6 +374,8 @@ class Merchant(Toolkit):
615
374
  "stall_name": stall_name,
616
375
  }
617
376
  )
377
+ # Pause for 0.5 seconds to avoid rate limiting
378
+ time.sleep(0.5)
618
379
  except Exception as e:
619
380
  results.append(
620
381
  {
@@ -645,7 +406,7 @@ class Merchant(Toolkit):
645
406
 
646
407
  def publish_profile(self) -> str:
647
408
  """
648
- Publishes the profile on Nostr
409
+ Publishes the profile to Nostr
649
410
 
650
411
  Returns:
651
412
  str: JSON of the event that published the profile
@@ -668,22 +429,25 @@ class Merchant(Toolkit):
668
429
 
669
430
  def publish_new_stall(self, stall: MerchantStall) -> str:
670
431
  """
671
- Publishes a new stall that is not currently in the Merchant's Stall DB and adds it to the Stall DB
432
+ Publishes to Nostr a new stall that is not currently in the Merchant's Stall DB and adds it to the Stall DB
672
433
 
673
434
  Args:
674
435
  stall: MerchantStall to be published
675
436
 
676
437
  Returns:
677
438
  str: JSON string with status of the operation
439
+
440
+ Raises:
441
+ ValueError: if NostrClient is not initialized
678
442
  """
679
443
  if self._nostr_client is None:
680
444
  raise ValueError("NostrClient not initialized")
681
445
 
682
446
  try:
683
- # Convert to StallData for SDK
684
- stall_data = stall.to_stall_data()
685
- # Publish using the SDK's synchronous method
686
- event_id = self._nostr_client.publish_stall(stall_data)
447
+ # We don't ned to convert to StallData. nostr_client.publish_stall() accepts a MerchantStall
448
+ # stall_data = stall.to_stall_data()
449
+ # Publish using the synchronous method
450
+ event_id = self._nostr_client.publish_stall(stall)
687
451
  # we need to add the stall event id to the stall db
688
452
  self.stall_db.append((stall, event_id))
689
453
  return json.dumps(
@@ -699,6 +463,21 @@ class Merchant(Toolkit):
699
463
  )
700
464
 
701
465
  def publish_stall_by_name(self, arguments: Union[str, dict]) -> str:
466
+ """
467
+ Publishes or updates to Nostr a given stall by name
468
+
469
+ Args:
470
+ arguments: str or dict with the stall name. Can be in formats:
471
+ - {"name": "stall_name"}
472
+ - {"arguments": "{\"name\": \"stall_name\"}"}
473
+ - "stall_name"
474
+
475
+ Returns:
476
+ str: JSON array with status of the operation
477
+
478
+ Raises:
479
+ ValueError: if NostrClient is not initialized
480
+ """
702
481
  if self._nostr_client is None:
703
482
  raise ValueError("NostrClient not initialized")
704
483
 
@@ -735,8 +514,9 @@ class Merchant(Toolkit):
735
514
  for i, (stall, _) in enumerate(self.stall_db):
736
515
  if stall.name == stall_name:
737
516
  try:
738
- stall_data = stall.to_stall_data()
739
- event_id = self._nostr_client.publish_stall(stall_data)
517
+ # We are not passing StallData to nostr_client.publish_stall() anymore
518
+ # stall_data = stall.to_stall_data()
519
+ event_id = self._nostr_client.publish_stall(stall)
740
520
  self.stall_db[i] = (stall, event_id)
741
521
  return json.dumps(
742
522
  {
@@ -745,6 +525,8 @@ class Merchant(Toolkit):
745
525
  "stall_name": stall.name,
746
526
  }
747
527
  )
528
+ # Pause for 0.5 seconds to avoid rate limiting
529
+ time.sleep(0.5)
748
530
  except Exception as e:
749
531
  return json.dumps(
750
532
  [
@@ -774,10 +556,13 @@ class Merchant(Toolkit):
774
556
 
775
557
  def remove_all_products(self) -> str:
776
558
  """
777
- Removes all published products from Nostr
559
+ Removes from Nostr all products published by the merchant
778
560
 
779
561
  Returns:
780
562
  str: JSON array with status of all product removal operations
563
+
564
+ Raises:
565
+ ValueError: if NostrClient is not initialized
781
566
  """
782
567
  if self._nostr_client is None:
783
568
  raise ValueError("NostrClient not initialized")
@@ -810,6 +595,8 @@ class Merchant(Toolkit):
810
595
  "event_id": str(event_id),
811
596
  }
812
597
  )
598
+ # Pause for 0.5 seconds to avoid rate limiting
599
+ time.sleep(0.5)
813
600
  except Exception as e:
814
601
  results.append(
815
602
  {"status": "error", "message": str(e), "product_name": product.name}
@@ -819,10 +606,13 @@ class Merchant(Toolkit):
819
606
 
820
607
  def remove_all_stalls(self) -> str:
821
608
  """
822
- Removes all stalls and their products from Nostr
609
+ Removes from Nostr all stalls from the merchant and their corresponding products
823
610
 
824
611
  Returns:
825
612
  str: JSON array with status of all removal operations
613
+
614
+ Raises:
615
+ ValueError: if NostrClient is not initialized
826
616
  """
827
617
  if self._nostr_client is None:
828
618
  raise ValueError("NostrClient not initialized")
@@ -897,6 +687,8 @@ class Merchant(Toolkit):
897
687
  "event_id": str(stall_event_id),
898
688
  }
899
689
  )
690
+ # Pause for 0.5 seconds to avoid rate limiting
691
+ time.sleep(0.5)
900
692
  except Exception as e:
901
693
  results.append(
902
694
  {"status": "error", "message": str(e), "stall_name": stall_name}
@@ -906,13 +698,16 @@ class Merchant(Toolkit):
906
698
 
907
699
  def remove_product_by_name(self, arguments: str) -> str:
908
700
  """
909
- Deletes a product with the given name from Nostr
701
+ Removes from Nostr a product with the given name
910
702
 
911
703
  Args:
912
704
  arguments: JSON string that may contain {"name": "product_name"} or just "product_name"
913
705
 
914
706
  Returns:
915
707
  str: JSON string with status of the operation
708
+
709
+ Raises:
710
+ ValueError: if NostrClient is not initialized
916
711
  """
917
712
  if self._nostr_client is None:
918
713
  raise ValueError("NostrClient not initialized")
@@ -949,6 +744,8 @@ class Merchant(Toolkit):
949
744
  )
950
745
  # Remove the event_id, keeping the product in the database
951
746
  self.product_db[i] = (product, None)
747
+ # Pause for 0.5 seconds to avoid rate limiting
748
+ time.sleep(0.5)
952
749
  return json.dumps(
953
750
  {
954
751
  "status": "success",
@@ -972,7 +769,8 @@ class Merchant(Toolkit):
972
769
  )
973
770
 
974
771
  def remove_stall_by_name(self, arguments: Union[str, dict]) -> str:
975
- """Remove a stall and its products by name
772
+ """
773
+ Remove from Nostr a stall and its products by name
976
774
 
977
775
  Args:
978
776
  arguments: str or dict with the stall name. Can be in formats:
@@ -982,6 +780,9 @@ class Merchant(Toolkit):
982
780
 
983
781
  Returns:
984
782
  str: JSON array with status of the operation
783
+
784
+ Raises:
785
+ ValueError: if NostrClient is not initialized
985
786
  """
986
787
  if self._nostr_client is None:
987
788
  raise ValueError("NostrClient not initialized")
@@ -1063,6 +864,8 @@ class Merchant(Toolkit):
1063
864
  "event_id": str(event_id),
1064
865
  }
1065
866
  )
867
+ # Pause for 0.5 seconds to avoid rate limiting
868
+ time.sleep(0.5)
1066
869
  except Exception as e:
1067
870
  results.append(
1068
871
  {
@@ -1117,15 +920,15 @@ class Merchant(Toolkit):
1117
920
  [{"status": "error", "message": str(e), "stall_name": "unknown"}]
1118
921
  )
1119
922
 
1120
- def get_event_id(self, response: Any) -> str:
1121
- """Convert any response to a string event ID.
923
+ # def get_event_id(self, response: Any) -> str:
924
+ # """Convert any response to a string event ID.
1122
925
 
1123
- Args:
1124
- response: Response that might contain an event ID
926
+ # Args:
927
+ # response: Response that might contain an event ID
1125
928
 
1126
- Returns:
1127
- str: String representation of event ID or empty string if None
1128
- """
1129
- if response is None:
1130
- return ""
1131
- return str(response)
929
+ # Returns:
930
+ # str: String representation of event ID or empty string if None
931
+ # """
932
+ # if response is None:
933
+ # return ""
934
+ # return str(response)