Skip to content

Commit 942bed3

Browse files
committed
feat: support credit payment in store messages
1 parent 50ba6ed commit 942bed3

File tree

4 files changed

+83
-2
lines changed

4 files changed

+83
-2
lines changed

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ dynamic = [ "version" ]
3030
dependencies = [
3131
"aiohttp>=3.8.3",
3232
"aioresponses>=0.7.6",
33-
"aleph-message>=1.0.5",
33+
"aleph-message>=1.1",
3434
"aleph-superfluid>=0.3",
3535
"base58==2.1.1", # Needed now as default with _load_account changement
3636
"coincurve; python_version>='3.9'",
@@ -115,6 +115,7 @@ include = [
115115
python = [ "3.9", "3.10", "3.11" ]
116116

117117
[tool.hatch.envs.testing]
118+
python = "3.13"
118119
features = [
119120
"cosmos",
120121
"dns",

src/aleph/sdk/client/abstract.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ async def create_store(
388388
extra_fields: Optional[dict] = None,
389389
channel: Optional[str] = settings.DEFAULT_CHANNEL,
390390
sync: bool = False,
391+
payment: Optional[Payment] = None,
391392
) -> Tuple[AlephMessage, MessageStatus]:
392393
"""
393394
Create a STORE message to store a file on the aleph.im network.
@@ -404,6 +405,7 @@ async def create_store(
404405
:param extra_fields: Extra fields to add to the STORE message (Default: None)
405406
:param channel: Channel to post the message to (Default: "TEST")
406407
:param sync: If true, waits for the message to be processed by the API server (Default: False)
408+
:param payment: Payment method used to pay for storage (Default: hold on ETH)
407409
"""
408410
raise NotImplementedError(
409411
"Did you mean to import `AuthenticatedAlephHttpClient`?"

src/aleph/sdk/client/authenticated_http.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
AggregateContent,
1313
AggregateMessage,
1414
AlephMessage,
15+
Chain,
1516
ForgetContent,
1617
ForgetMessage,
1718
InstanceMessage,
@@ -24,7 +25,7 @@
2425
StoreContent,
2526
StoreMessage,
2627
)
27-
from aleph_message.models.execution.base import Encoding, Payment
28+
from aleph_message.models.execution.base import Encoding, Payment, PaymentType
2829
from aleph_message.models.execution.environment import (
2930
HostRequirements,
3031
HypervisorType,
@@ -350,8 +351,12 @@ async def create_store(
350351
extra_fields: Optional[dict] = None,
351352
channel: Optional[str] = settings.DEFAULT_CHANNEL,
352353
sync: bool = False,
354+
payment: Optional[Payment] = None,
353355
) -> Tuple[StoreMessage, MessageStatus]:
354356
address = address or settings.ADDRESS_TO_USE or self.account.get_address()
357+
payment = payment or Payment(
358+
chain=Chain.ETH, type=PaymentType.hold, receiver=None
359+
)
355360

356361
extra_fields = extra_fields or {}
357362

@@ -374,6 +379,7 @@ async def create_store(
374379
extra_fields=extra_fields,
375380
channel=channel,
376381
sync=sync,
382+
payment=payment,
377383
)
378384
elif storage_engine == StorageEnum.ipfs:
379385
# We do not support authenticated upload for IPFS yet. Use the legacy method
@@ -397,6 +403,7 @@ async def create_store(
397403
"item_type": storage_engine,
398404
"item_hash": file_hash,
399405
"time": time.time(),
406+
"payment": payment,
400407
}
401408
if extra_fields is not None:
402409
values.update(extra_fields)
@@ -660,6 +667,7 @@ async def _upload_file_native(
660667
extra_fields: Optional[dict] = None,
661668
channel: Optional[str] = settings.DEFAULT_CHANNEL,
662669
sync: bool = False,
670+
payment: Optional[Payment] = None,
663671
) -> Tuple[StoreMessage, MessageStatus]:
664672
file_hash = hashlib.sha256(file_content).hexdigest()
665673
if magic and guess_mime_type:
@@ -674,6 +682,7 @@ async def _upload_file_native(
674682
item_hash=ItemHash(file_hash),
675683
mime_type=mime_type, # type: ignore
676684
time=time.time(),
685+
payment=payment,
677686
**(extra_fields or {}),
678687
)
679688
message, _ = await self._storage_push_file_with_message(

tests/unit/test_asynchronous.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,3 +293,72 @@ async def test_create_instance_insufficient_funds_error(
293293
receiver=None,
294294
),
295295
)
296+
297+
298+
@pytest.mark.asyncio
299+
async def test_create_instance_with_credit_payment(mock_session_with_post_success):
300+
"""Test that an instance can be created with credit payment."""
301+
async with mock_session_with_post_success as session:
302+
instance_message, message_status = await session.create_instance(
303+
rootfs="cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe",
304+
rootfs_size=1,
305+
channel="TEST",
306+
metadata={"tags": ["test"]},
307+
payment=Payment(
308+
chain=Chain.ETH,
309+
receiver=None,
310+
type=PaymentType.credit,
311+
),
312+
)
313+
314+
assert instance_message.content.payment.type == PaymentType.credit
315+
assert instance_message.content.payment.chain == Chain.ETH
316+
assert instance_message.content.payment.receiver is None
317+
318+
assert mock_session_with_post_success.http_session.post.assert_called_once
319+
assert isinstance(instance_message, InstanceMessage)
320+
321+
322+
@pytest.mark.asyncio
323+
async def test_create_store_with_credit_payment(mock_session_with_post_success):
324+
"""Test that a store message can be created with credit payment."""
325+
mock_ipfs_push_file = AsyncMock()
326+
mock_ipfs_push_file.return_value = "QmRTV3h1jLcACW4FRfdisokkQAk4E4qDhUzGpgdrd4JAFy"
327+
328+
mock_session_with_post_success.ipfs_push_file = mock_ipfs_push_file
329+
330+
async with mock_session_with_post_success as session:
331+
store_message, message_status = await session.create_store(
332+
file_content=b"HELLO",
333+
channel="TEST",
334+
storage_engine=StorageEnum.ipfs,
335+
payment=Payment(
336+
chain=Chain.ETH,
337+
receiver=None,
338+
type=PaymentType.credit,
339+
),
340+
)
341+
342+
assert store_message.content.payment.type == PaymentType.credit
343+
assert store_message.content.payment.chain == Chain.ETH
344+
assert isinstance(store_message, StoreMessage)
345+
346+
347+
@pytest.mark.asyncio
348+
async def test_create_store_default_payment(mock_session_with_post_success):
349+
"""Test that a store message defaults to hold payment on ETH."""
350+
mock_ipfs_push_file = AsyncMock()
351+
mock_ipfs_push_file.return_value = "QmRTV3h1jLcACW4FRfdisokkQAk4E4qDhUzGpgdrd4JAFy"
352+
353+
mock_session_with_post_success.ipfs_push_file = mock_ipfs_push_file
354+
355+
async with mock_session_with_post_success as session:
356+
store_message, message_status = await session.create_store(
357+
file_content=b"HELLO",
358+
channel="TEST",
359+
storage_engine=StorageEnum.ipfs,
360+
)
361+
362+
assert store_message.content.payment.type == PaymentType.hold
363+
assert store_message.content.payment.chain == Chain.ETH
364+
assert isinstance(store_message, StoreMessage)

0 commit comments

Comments
 (0)