Skip to content

Commit aa6afa2

Browse files
committed
feat: allow making payments using auth client
1 parent 5901da4 commit aa6afa2

File tree

6 files changed

+533
-0
lines changed

6 files changed

+533
-0
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ license = {text = "MIT"}
99
readme = "README.rst"
1010
requires-python = ">=3.9"
1111
dependencies = [
12+
"cosmpy==0.9.2",
1213
"requests==2.32.3",
1314
"secp256k1==0.14.0",
1415
]

src/nuc/authority.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22
Authority service APIs.
33
"""
44

5+
import logging
6+
import hashlib
57
from datetime import datetime, timedelta, timezone
68
from dataclasses import dataclass
79
import secrets
810
import json
911
import requests
1012
from secp256k1 import PrivateKey, PublicKey
1113

14+
from nuc.payer import Payer
15+
16+
logger = logging.getLogger(__name__)
17+
1218

1319
DEFAULT_REQUEST_TIMEOUT: float = 10
1420

@@ -102,6 +108,45 @@ def request_token(self, key: PrivateKey) -> str:
102108
response = response.json()
103109
return response["token"]
104110

111+
def pay_subscription(
112+
self,
113+
our_public_key: PublicKey,
114+
payer: Payer,
115+
) -> None:
116+
"""
117+
Pay for a subscription.
118+
119+
Arguments
120+
---------
121+
122+
our_public_key
123+
The public key the subscription is for.
124+
payer
125+
The payer that will be used.
126+
"""
127+
public_key = self.about().public_key.serialize()
128+
payload = json.dumps(
129+
{
130+
"nonce": secrets.token_bytes(16).hex(),
131+
"service_public_key": public_key.hex(),
132+
}
133+
).encode("utf8")
134+
# Note: add proper value later on
135+
tx_hash = payer.pay(hashlib.sha256(payload).digest(), amount_unil=1)
136+
137+
request = {
138+
"tx_hash": tx_hash,
139+
"payload": payload.hex(),
140+
"public_key": our_public_key.serialize().hex(),
141+
}
142+
143+
response = requests.post(
144+
f"{self._base_url}/api/v1/payments/validate",
145+
json=request,
146+
timeout=self._timeout_seconds,
147+
)
148+
response.raise_for_status()
149+
105150
def about(self) -> AuthorityServiceAbout:
106151
"""
107152
Get information about the authority service.

src/nuc/payer.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""
2+
Nilchain payer.
3+
"""
4+
5+
import logging
6+
from cosmpy.aerial.tx import Transaction
7+
from cosmpy.aerial.wallet import Address, LocalWallet
8+
from cosmpy.crypto.keypairs import PrivateKey as NilchainPrivateKey
9+
from cosmpy.aerial.client import (
10+
LedgerClient,
11+
NetworkConfig,
12+
prepare_and_broadcast_basic_transaction,
13+
)
14+
15+
# pylint: disable=E0611
16+
from .proto.tx_pb2 import MsgPayFor, Amount
17+
18+
logger = logging.getLogger(__name__)
19+
20+
21+
DEFAULT_QUERY_TIMEOUT_SECONDS = 30
22+
DEFAULT_QUERY_POLL_SECONDS = 1
23+
24+
25+
class Payer:
26+
"""
27+
A payer that allows making payments on nilchain.
28+
"""
29+
30+
def __init__(
31+
self,
32+
wallet_private_key: NilchainPrivateKey,
33+
chain_id: str,
34+
grpc_endpoint: str,
35+
gas_limit: int,
36+
query_timeout_seconds: int = DEFAULT_QUERY_TIMEOUT_SECONDS,
37+
query_poll_seconds: int = DEFAULT_QUERY_POLL_SECONDS,
38+
) -> None:
39+
self.wallet = LocalWallet(wallet_private_key, "nillion")
40+
self.gas_limit = gas_limit
41+
payments_config = NetworkConfig(
42+
chain_id=chain_id,
43+
url=f"grpc+{grpc_endpoint}/",
44+
fee_minimum_gas_price=0,
45+
fee_denomination="unil",
46+
staking_denomination="unil",
47+
faucet_url=None,
48+
)
49+
self.client = LedgerClient(
50+
payments_config,
51+
query_interval_secs=query_poll_seconds,
52+
query_timeout_secs=query_timeout_seconds,
53+
)
54+
55+
def pay(self, resource: bytes, amount_unil: int) -> str:
56+
"""
57+
Perform a `MsgPayFor` payment for the given resource.
58+
59+
Arguments
60+
---------
61+
62+
resource
63+
The resource to use in the transaction.
64+
amount_unil
65+
The amount of unil to send in the payment.
66+
"""
67+
68+
transaction = Transaction()
69+
message = MsgPayFor(
70+
resource=resource,
71+
from_address=str(Address(self.wallet.public_key(), "nillion")),
72+
amount=[Amount(denom="unil", amount=str(amount_unil))],
73+
)
74+
transaction.add_message(message)
75+
76+
submitted_transaction = prepare_and_broadcast_basic_transaction(
77+
self.client, transaction, self.wallet, gas_limit=self.gas_limit
78+
)
79+
80+
tx_hash = submitted_transaction.tx_hash
81+
logger.info("Waiting for transaction %s to be committed", tx_hash)
82+
submitted_transaction.wait_to_complete()
83+
return tx_hash

src/nuc/proto/tx_pb2.py

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/nuc/proto/tx_pb2.pyi

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from google.protobuf.internal import containers as _containers
2+
from google.protobuf import descriptor as _descriptor
3+
from google.protobuf import message as _message
4+
from typing import (
5+
ClassVar as _ClassVar,
6+
Iterable as _Iterable,
7+
Mapping as _Mapping,
8+
Optional as _Optional,
9+
Union as _Union,
10+
)
11+
12+
DESCRIPTOR: _descriptor.FileDescriptor
13+
14+
class MsgPayFor(_message.Message):
15+
__slots__ = ("resource", "from_address", "amount")
16+
RESOURCE_FIELD_NUMBER: _ClassVar[int]
17+
FROM_ADDRESS_FIELD_NUMBER: _ClassVar[int]
18+
AMOUNT_FIELD_NUMBER: _ClassVar[int]
19+
resource: bytes
20+
from_address: str
21+
amount: _containers.RepeatedCompositeFieldContainer[Amount]
22+
def __init__(
23+
self,
24+
resource: _Optional[bytes] = ...,
25+
from_address: _Optional[str] = ...,
26+
amount: _Optional[_Iterable[_Union[Amount, _Mapping]]] = ...,
27+
) -> None: ...
28+
29+
class Amount(_message.Message):
30+
__slots__ = ("denom", "amount")
31+
DENOM_FIELD_NUMBER: _ClassVar[int]
32+
AMOUNT_FIELD_NUMBER: _ClassVar[int]
33+
denom: str
34+
amount: str
35+
def __init__(
36+
self, denom: _Optional[str] = ..., amount: _Optional[str] = ...
37+
) -> None: ...

0 commit comments

Comments
 (0)