mech-client 0.8.1__py3-none-any.whl → 0.8.2__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.
- mech_client/__init__.py +1 -1
- {mech_client-0.8.1.dist-info → mech_client-0.8.2.dist-info}/METADATA +1 -1
- {mech_client-0.8.1.dist-info → mech_client-0.8.2.dist-info}/RECORD +28 -6
- scripts/__init__.py +1 -0
- scripts/benchmark.sh +32 -0
- scripts/bump.py +316 -0
- scripts/deposit_native.py +118 -0
- scripts/deposit_token.py +187 -0
- scripts/nvm_subscribe.py +51 -0
- scripts/nvm_subscription/contracts/agreement_manager.py +44 -0
- scripts/nvm_subscription/contracts/base_contract.py +75 -0
- scripts/nvm_subscription/contracts/did_registry.py +91 -0
- scripts/nvm_subscription/contracts/escrow_payment.py +85 -0
- scripts/nvm_subscription/contracts/lock_payment.py +76 -0
- scripts/nvm_subscription/contracts/nft.py +47 -0
- scripts/nvm_subscription/contracts/nft_sales.py +119 -0
- scripts/nvm_subscription/contracts/subscription_provider.py +107 -0
- scripts/nvm_subscription/contracts/token.py +87 -0
- scripts/nvm_subscription/contracts/transfer_nft.py +81 -0
- scripts/nvm_subscription/envs/base.env +10 -0
- scripts/nvm_subscription/envs/gnosis.env +10 -0
- scripts/nvm_subscription/manager.py +265 -0
- scripts/nvm_subscription/resources/networks.json +56 -0
- scripts/utils.py +127 -0
- scripts/whitelist.py +5 -0
- {mech_client-0.8.1.dist-info → mech_client-0.8.2.dist-info}/LICENSE +0 -0
- {mech_client-0.8.1.dist-info → mech_client-0.8.2.dist-info}/WHEEL +0 -0
- {mech_client-0.8.1.dist-info → mech_client-0.8.2.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# subscription/contracts/nft_sales.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import List, Any, Dict, Union
|
|
4
|
+
from web3 import Web3
|
|
5
|
+
from eth_typing import ChecksumAddress
|
|
6
|
+
from web3.types import ENS
|
|
7
|
+
|
|
8
|
+
from .base_contract import BaseContract
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class NFTSalesTemplateContract(BaseContract):
|
|
14
|
+
"""
|
|
15
|
+
Wrapper class for the NFTSalesTemplate smart contract. Provides a method
|
|
16
|
+
to build a transaction for creating an agreement and paying escrow.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, w3: Web3):
|
|
20
|
+
"""
|
|
21
|
+
Initialize the NFTSalesTemplateContract.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
w3 (Web3): An instance of Web3 connected to an Ethereum network.
|
|
25
|
+
"""
|
|
26
|
+
logger.debug("Initializing NFTSalesTemplateContract")
|
|
27
|
+
super().__init__(w3, name="NFTSalesTemplate")
|
|
28
|
+
logger.info("NFTSalesTemplateContract initialized")
|
|
29
|
+
|
|
30
|
+
def build_create_agreement_tx(
|
|
31
|
+
self,
|
|
32
|
+
agreement_id_seed: str,
|
|
33
|
+
did: str,
|
|
34
|
+
condition_seeds: List[bytes],
|
|
35
|
+
timelocks: List[int],
|
|
36
|
+
timeouts: List[int],
|
|
37
|
+
publisher: str,
|
|
38
|
+
service_index: int,
|
|
39
|
+
reward_address: str,
|
|
40
|
+
token_address: str,
|
|
41
|
+
amounts: List[int],
|
|
42
|
+
receivers: List[str],
|
|
43
|
+
sender: str,
|
|
44
|
+
value_eth: float,
|
|
45
|
+
gas: int = 600_000,
|
|
46
|
+
chain_id: int = 100
|
|
47
|
+
) -> Dict[str, Any]:
|
|
48
|
+
"""
|
|
49
|
+
Build a transaction dictionary to create an agreement and pay escrow.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
agreement_id_seed (str): Unique identifier seed for the agreement.
|
|
53
|
+
did (str): Decentralized identifier.
|
|
54
|
+
condition_seeds (List[bytes]): List of hashed condition values.
|
|
55
|
+
timelocks (List[int]): Time locks for each condition.
|
|
56
|
+
timeouts (List[int]): Timeouts for each condition.
|
|
57
|
+
publisher (str): Ethereum address of the publisher.
|
|
58
|
+
service_index (int): Index of the service in the agreement.
|
|
59
|
+
reward_address (str): Address to receive the reward.
|
|
60
|
+
token_address (str): ERC20 token address.
|
|
61
|
+
amounts (List[int]): Payment amounts.
|
|
62
|
+
receivers (List[str]): List of payment receiver addresses.
|
|
63
|
+
sender (str): Ethereum address initiating the transaction.
|
|
64
|
+
value_eth (float): ETH value to include in the transaction.
|
|
65
|
+
gas (int, optional): Gas limit. Defaults to 600,000.
|
|
66
|
+
chain_id (int, optional): Ethereum network chain ID. Defaults to 100.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Dict[str, Any]: Unsigned transaction dictionary.
|
|
70
|
+
"""
|
|
71
|
+
logger.debug("Building transaction for createAgreementAndPayEscrow")
|
|
72
|
+
logger.debug(f"agreement_id_seed: {agreement_id_seed}")
|
|
73
|
+
logger.debug(f"did: {did}")
|
|
74
|
+
logger.debug(f"condition_seeds: {condition_seeds}")
|
|
75
|
+
logger.debug(f"timelocks: {timelocks}, timeouts: {timeouts}")
|
|
76
|
+
logger.debug(f"publisher: {publisher}, service_index: {service_index}")
|
|
77
|
+
logger.debug(f"reward_address: {reward_address}, token_address: {token_address}")
|
|
78
|
+
logger.debug(f"amounts: {amounts}, receivers: {receivers}")
|
|
79
|
+
logger.debug(f"sender: {sender}, value_eth: {value_eth}")
|
|
80
|
+
|
|
81
|
+
# Convert sender to a checksum address to ensure type safety
|
|
82
|
+
sender_address: ChecksumAddress = self.w3.to_checksum_address(sender)
|
|
83
|
+
nonce = self.w3.eth.get_transaction_count(sender_address)
|
|
84
|
+
logger.debug(f"Nonce for sender {sender_address}: {nonce}")
|
|
85
|
+
|
|
86
|
+
latest_block = self.w3.eth.get_block("latest")
|
|
87
|
+
base_fee = latest_block["baseFeePerGas"]
|
|
88
|
+
max_priority_fee = self.w3.eth.max_priority_fee
|
|
89
|
+
|
|
90
|
+
# Build the transaction using the contract method
|
|
91
|
+
tx = self.functions().createAgreementAndPayEscrow(
|
|
92
|
+
agreement_id_seed,
|
|
93
|
+
did,
|
|
94
|
+
condition_seeds,
|
|
95
|
+
timelocks,
|
|
96
|
+
timeouts,
|
|
97
|
+
self.w3.to_checksum_address(publisher),
|
|
98
|
+
service_index,
|
|
99
|
+
self.w3.to_checksum_address(reward_address),
|
|
100
|
+
self.w3.to_checksum_address(token_address),
|
|
101
|
+
amounts,
|
|
102
|
+
[self.w3.to_checksum_address(r) for r in receivers]
|
|
103
|
+
).build_transaction({
|
|
104
|
+
"from": sender_address,
|
|
105
|
+
"value": self.w3.to_wei(value_eth, "ether"),
|
|
106
|
+
"chainId": chain_id,
|
|
107
|
+
"gas": gas,
|
|
108
|
+
"nonce": nonce,
|
|
109
|
+
})
|
|
110
|
+
gas = self.w3.eth.estimate_gas(tx)
|
|
111
|
+
tx.update({
|
|
112
|
+
"gas": gas,
|
|
113
|
+
"maxFeePerGas": base_fee + max_priority_fee,
|
|
114
|
+
"maxPriorityFeePerGas": max_priority_fee,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
logger.info(f"Transaction built successfully for agreement ID: {agreement_id_seed}")
|
|
118
|
+
logger.debug(f"Transaction details: {tx}")
|
|
119
|
+
return tx
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# subscription/contracts/subscription_provider.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Union, List, Dict, Any
|
|
4
|
+
from web3 import Web3
|
|
5
|
+
from eth_typing import ChecksumAddress
|
|
6
|
+
from web3.types import ENS
|
|
7
|
+
|
|
8
|
+
from .base_contract import BaseContract
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SubscriptionProvider(BaseContract):
|
|
14
|
+
"""
|
|
15
|
+
Wrapper for the Token smart contract. Supports token balance
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, w3: Web3):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the Subscription provider instance.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
w3 (Web3): A connected Web3 instance.
|
|
24
|
+
"""
|
|
25
|
+
logger.debug("Initializing Subscription provider")
|
|
26
|
+
super().__init__(w3, name="SubscriptionProvider")
|
|
27
|
+
logger.info("Subscription provider initialized")
|
|
28
|
+
|
|
29
|
+
def build_create_fulfill_tx(
|
|
30
|
+
self,
|
|
31
|
+
agreement_id_seed: str,
|
|
32
|
+
did: str,
|
|
33
|
+
fulfill_for_delegate_params: tuple,
|
|
34
|
+
fulfill_params: tuple,
|
|
35
|
+
sender: str,
|
|
36
|
+
value_eth: float,
|
|
37
|
+
gas: int = 450_000,
|
|
38
|
+
chain_id: int = 100,
|
|
39
|
+
) -> Dict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Build a transaction dictionary to create a fulfill tx.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
agreement_id_seed (str): Unique identifier seed for the agreement.
|
|
45
|
+
did (str): Decentralized identifier.
|
|
46
|
+
condition_seeds (List[bytes]): List of hashed condition values.
|
|
47
|
+
timelocks (List[int]): Time locks for each condition.
|
|
48
|
+
timeouts (List[int]): Timeouts for each condition.
|
|
49
|
+
publisher (str): Ethereum address of the publisher.
|
|
50
|
+
service_index (int): Index of the service in the agreement.
|
|
51
|
+
reward_address (str): Address to receive the reward.
|
|
52
|
+
token_address (str): ERC20 token address.
|
|
53
|
+
amounts (List[int]): Payment amounts.
|
|
54
|
+
receivers (List[str]): List of payment receiver addresses.
|
|
55
|
+
sender (str): Ethereum address initiating the transaction.
|
|
56
|
+
value_eth (float): ETH value to include in the transaction.
|
|
57
|
+
gas (int, optional): Gas limit. Defaults to 450,000.
|
|
58
|
+
chain_id (int, optional): Ethereum network chain ID. Defaults to 100.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Dict[str, Any]: Unsigned transaction dictionary.
|
|
62
|
+
"""
|
|
63
|
+
logger.debug("Building transaction for fulfill")
|
|
64
|
+
logger.debug(f"agreement_id_seed: {agreement_id_seed}")
|
|
65
|
+
logger.debug(f"did: {did}")
|
|
66
|
+
logger.debug(f"sender: {sender}, value_eth: {value_eth}")
|
|
67
|
+
|
|
68
|
+
# Convert sender to a checksum address to ensure type safety
|
|
69
|
+
sender_address: ChecksumAddress = self.w3.to_checksum_address(sender)
|
|
70
|
+
nonce = self.w3.eth.get_transaction_count(sender_address)
|
|
71
|
+
logger.debug(f"Nonce for sender {sender_address}: {nonce}")
|
|
72
|
+
|
|
73
|
+
latest_block = self.w3.eth.get_block("latest")
|
|
74
|
+
base_fee = latest_block["baseFeePerGas"]
|
|
75
|
+
max_priority_fee = self.w3.eth.max_priority_fee
|
|
76
|
+
|
|
77
|
+
logger.debug(f"fulfill_for_delegate_params: {fulfill_for_delegate_params}")
|
|
78
|
+
logger.debug(f"fulfill_params: {fulfill_params}")
|
|
79
|
+
|
|
80
|
+
# Build the transaction using the contract method
|
|
81
|
+
tx = (
|
|
82
|
+
self.functions()
|
|
83
|
+
.fulfill(
|
|
84
|
+
agreement_id_seed, did, fulfill_for_delegate_params, fulfill_params
|
|
85
|
+
)
|
|
86
|
+
.build_transaction(
|
|
87
|
+
{
|
|
88
|
+
"from": sender_address,
|
|
89
|
+
"value": self.w3.to_wei(value_eth, "ether"),
|
|
90
|
+
"chainId": chain_id,
|
|
91
|
+
"gas": gas,
|
|
92
|
+
"nonce": nonce,
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
gas = self.w3.eth.estimate_gas(tx)
|
|
97
|
+
tx.update(
|
|
98
|
+
{
|
|
99
|
+
"gas": gas,
|
|
100
|
+
"maxFeePerGas": base_fee + max_priority_fee,
|
|
101
|
+
"maxPriorityFeePerGas": max_priority_fee,
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
logger.info(f"Transaction built successfully for fulfill: {agreement_id_seed}")
|
|
106
|
+
logger.debug(f"Transaction details: {tx}")
|
|
107
|
+
return tx
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# subscription/contracts/transfer_nft.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Union
|
|
4
|
+
from web3 import Web3
|
|
5
|
+
from eth_typing import ChecksumAddress
|
|
6
|
+
from web3.types import ENS
|
|
7
|
+
|
|
8
|
+
from .base_contract import BaseContract
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SubscriptionToken(BaseContract):
|
|
14
|
+
"""
|
|
15
|
+
Wrapper for the Token smart contract. Supports approve token
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, w3: Web3):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the Token instance.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
w3 (Web3): A connected Web3 instance.
|
|
24
|
+
"""
|
|
25
|
+
logger.debug("Initializing Subscription Token")
|
|
26
|
+
super().__init__(w3, name="SubscriptionToken")
|
|
27
|
+
logger.info("Token initialized")
|
|
28
|
+
|
|
29
|
+
def build_approve_token_tx(
|
|
30
|
+
self,
|
|
31
|
+
sender: Union[ChecksumAddress, ENS],
|
|
32
|
+
to: Union[ChecksumAddress, ENS],
|
|
33
|
+
amount: int,
|
|
34
|
+
gas: int = 60_000,
|
|
35
|
+
chain_id: int = 100,
|
|
36
|
+
) -> bytes:
|
|
37
|
+
"""
|
|
38
|
+
Compute the hash of parameters for the transfer condition.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
sender (ChecksumAddress | ENS): Address sending the approve tx.
|
|
42
|
+
to_address (ChecksumAddress | ENS): Address getting the approval.
|
|
43
|
+
amount (int): Number of tokens to approve.
|
|
44
|
+
gas (int, optional): Gas limit. Defaults to 60,000.
|
|
45
|
+
chain_id (int, optional): Ethereum network chain ID. Defaults to 100.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Dict[str, Any]: Unsigned transaction dictionary.
|
|
49
|
+
"""
|
|
50
|
+
logger.debug("Approving token...")
|
|
51
|
+
sender_address: ChecksumAddress = self.w3.to_checksum_address(sender)
|
|
52
|
+
to_address: ChecksumAddress = self.w3.to_checksum_address(to)
|
|
53
|
+
nonce = self.w3.eth.get_transaction_count(sender_address)
|
|
54
|
+
logger.debug(f"Nonce for sender {sender_address}: {nonce}")
|
|
55
|
+
|
|
56
|
+
latest_block = self.w3.eth.get_block("latest")
|
|
57
|
+
base_fee = latest_block["baseFeePerGas"]
|
|
58
|
+
max_priority_fee = self.w3.eth.max_priority_fee
|
|
59
|
+
|
|
60
|
+
tx = (
|
|
61
|
+
self.functions()
|
|
62
|
+
.approve(
|
|
63
|
+
to_address,
|
|
64
|
+
amount,
|
|
65
|
+
)
|
|
66
|
+
.build_transaction(
|
|
67
|
+
{
|
|
68
|
+
"from": sender,
|
|
69
|
+
"value": 0,
|
|
70
|
+
"chainId": chain_id,
|
|
71
|
+
"gas": gas,
|
|
72
|
+
"nonce": nonce,
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
gas = self.w3.eth.estimate_gas(tx)
|
|
77
|
+
tx.update(
|
|
78
|
+
{
|
|
79
|
+
"gas": gas,
|
|
80
|
+
"maxFeePerGas": base_fee + max_priority_fee,
|
|
81
|
+
"maxPriorityFeePerGas": max_priority_fee,
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
logger.info("Transaction built successfully for token approve")
|
|
86
|
+
logger.debug(f"Transaction details: {tx}")
|
|
87
|
+
return tx
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# subscription/contracts/transfer_nft.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Union
|
|
4
|
+
from web3 import Web3
|
|
5
|
+
from eth_typing import ChecksumAddress
|
|
6
|
+
from web3.types import ENS
|
|
7
|
+
|
|
8
|
+
from .base_contract import BaseContract
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TransferNFTConditionContract(BaseContract):
|
|
14
|
+
"""
|
|
15
|
+
Wrapper for the TransferNFTCondition smart contract. Supports hash and ID generation.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, w3: Web3):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the TransferNFTConditionContract instance.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
w3 (Web3): A connected Web3 instance.
|
|
24
|
+
"""
|
|
25
|
+
logger.debug("Initializing TransferNFTConditionContract")
|
|
26
|
+
super().__init__(w3, name="TransferNFTCondition")
|
|
27
|
+
logger.info("TransferNFTConditionContract initialized")
|
|
28
|
+
|
|
29
|
+
def hash_values(
|
|
30
|
+
self,
|
|
31
|
+
did: str,
|
|
32
|
+
from_address: Union[ChecksumAddress, ENS],
|
|
33
|
+
to_address: Union[ChecksumAddress, ENS],
|
|
34
|
+
amount: int,
|
|
35
|
+
lock_condition_id: bytes,
|
|
36
|
+
nft_contract_address: Union[ChecksumAddress, ENS],
|
|
37
|
+
_is_transfer: bool,
|
|
38
|
+
) -> bytes:
|
|
39
|
+
"""
|
|
40
|
+
Compute the hash of parameters for the transfer condition.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
did (str): Decentralized identifier.
|
|
44
|
+
from_address (ChecksumAddress | ENS): Address sending the NFT.
|
|
45
|
+
to_address (ChecksumAddress | ENS): Address receiving the NFT.
|
|
46
|
+
amount (int): Number of tokens to transfer.
|
|
47
|
+
lock_condition_id (bytes): Lock payment condition ID.
|
|
48
|
+
nft_contract_address (ChecksumAddress | ENS): NFT contract address.
|
|
49
|
+
is_escrow (bool): Indicates if escrow is involved.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
bytes: Hashed value.
|
|
53
|
+
"""
|
|
54
|
+
logger.debug("Computing transfer NFT hash value")
|
|
55
|
+
hash_ = self.functions().hashValues(
|
|
56
|
+
did,
|
|
57
|
+
from_address,
|
|
58
|
+
to_address,
|
|
59
|
+
amount,
|
|
60
|
+
lock_condition_id,
|
|
61
|
+
nft_contract_address,
|
|
62
|
+
_is_transfer
|
|
63
|
+
).call()
|
|
64
|
+
logger.debug(f"Transfer NFT hash: {hash_.hex()}")
|
|
65
|
+
return hash_
|
|
66
|
+
|
|
67
|
+
def generate_id(self, agreement_id: bytes, hash_value: bytes) -> bytes:
|
|
68
|
+
"""
|
|
69
|
+
Generate the condition ID for the transfer NFT condition.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
agreement_id (str): ID of the agreement.
|
|
73
|
+
hash_value (bytes): Hashed condition parameters.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
bytes: Condition ID.
|
|
77
|
+
"""
|
|
78
|
+
logger.debug("Generating transfer NFT condition ID")
|
|
79
|
+
condition_id = self.functions().generateId(agreement_id, hash_value).call()
|
|
80
|
+
logger.info(f"Transfer NFT condition ID: {condition_id.hex()}")
|
|
81
|
+
return condition_id
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export NETWORK_NAME="BASE"
|
|
2
|
+
export CHAIN_ID=8453
|
|
3
|
+
export SUBSCRIPTION_NFT_ADDRESS="0xd5318d1a17819f65771b6c9277534c08dd765498"
|
|
4
|
+
export OLAS_MARKETPLACE_ADDRESS="0xaaFBeef195BDAb1Bb6f3Dc9cEbA875Cd72499230"
|
|
5
|
+
export RECEIVER_PLAN="0xaaFBeef195BDAb1Bb6f3Dc9cEbA875Cd72499230"
|
|
6
|
+
export TOKEN_ADDRESS="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" #USDC
|
|
7
|
+
export PLAN_DID="did:nv:6f74c18fae7e5c3589b99d7cd0ba317593f00dee53c81a2ba4ac2244232f99da"
|
|
8
|
+
export SUBSCRIPTION_CREDITS="1000000"
|
|
9
|
+
export PLAN_FEE_NVM="10000"
|
|
10
|
+
export PLAN_PRICE_MECHS="990000"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export NETWORK_NAME="GNOSIS"
|
|
2
|
+
export CHAIN_ID=100
|
|
3
|
+
export SUBSCRIPTION_NFT_ADDRESS="0x1b5DeaD7309b56ca7663b3301A503e077Be18cba"
|
|
4
|
+
export OLAS_MARKETPLACE_ADDRESS="0x7D686bD1fD3CFF6E45a40165154D61043af7D67c"
|
|
5
|
+
export RECEIVER_PLAN="0x7D686bD1fD3CFF6E45a40165154D61043af7D67c"
|
|
6
|
+
export TOKEN_ADDRESS="0x0000000000000000000000000000000000000000" # xDAI
|
|
7
|
+
export PLAN_DID="did:nv:b0b28402e5a7229804579d4ac55b98a1dd94660d7a7eb4add78e5ca856f2aab7"
|
|
8
|
+
export SUBSCRIPTION_CREDITS="1000000"
|
|
9
|
+
export PLAN_FEE_NVM="10000000000000000"
|
|
10
|
+
export PLAN_PRICE_MECHS="990000000000000000"
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# subscription/manager.py
|
|
2
|
+
from enum import Enum
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import Any, Dict, List, Literal, Union
|
|
11
|
+
from web3 import Web3
|
|
12
|
+
from eth_account import Account
|
|
13
|
+
from eth_typing import ChecksumAddress
|
|
14
|
+
|
|
15
|
+
from .contracts.did_registry import DIDRegistryContract
|
|
16
|
+
from .contracts.nft_sales import NFTSalesTemplateContract
|
|
17
|
+
from .contracts.lock_payment import LockPaymentConditionContract
|
|
18
|
+
from .contracts.transfer_nft import TransferNFTConditionContract
|
|
19
|
+
from .contracts.escrow_payment import EscrowPaymentConditionContract
|
|
20
|
+
from .contracts.agreement_manager import AgreementStorageManagerContract
|
|
21
|
+
from .contracts.token import SubscriptionToken
|
|
22
|
+
from .contracts.nft import SubscriptionNFT
|
|
23
|
+
from .contracts.subscription_provider import SubscriptionProvider
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
28
|
+
config_path = os.path.join(BASE_DIR, 'resources', 'networks.json')
|
|
29
|
+
CONFIGS = json.load(open(config_path, 'r'))
|
|
30
|
+
|
|
31
|
+
class Network(Enum):
|
|
32
|
+
GNOSIS = "GNOSIS"
|
|
33
|
+
BASE = "BASE"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_variable_value(variable: str) -> str:
|
|
37
|
+
try:
|
|
38
|
+
var = os.getenv(variable)
|
|
39
|
+
if var is None:
|
|
40
|
+
raise ValueError(f"Environment variable {variable} not found")
|
|
41
|
+
return var
|
|
42
|
+
except Exception as e:
|
|
43
|
+
raise e
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class NVMSubscriptionManager:
|
|
47
|
+
"""
|
|
48
|
+
Manages the process of creating NFT-based subscription agreements
|
|
49
|
+
using a series of smart contracts.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, network: str, private_key: str):
|
|
53
|
+
"""
|
|
54
|
+
Initialize the SubscriptionManager, including contract instances
|
|
55
|
+
and Web3 connection.
|
|
56
|
+
"""
|
|
57
|
+
self.url = os.getenv("MECHX_CHAIN_RPC", CONFIGS[network]["nvm"]['web3ProviderUri'])
|
|
58
|
+
self.web3 = Web3(Web3.HTTPProvider(self.url))
|
|
59
|
+
|
|
60
|
+
self.account = Account.from_key(private_key)
|
|
61
|
+
self.sender: ChecksumAddress = self.web3.to_checksum_address(self.account.address)
|
|
62
|
+
|
|
63
|
+
self.did_registry = DIDRegistryContract(self.web3)
|
|
64
|
+
self.nft_sales = NFTSalesTemplateContract(self.web3)
|
|
65
|
+
self.lock_payment = LockPaymentConditionContract(self.web3)
|
|
66
|
+
self.transfer_nft = TransferNFTConditionContract(self.web3)
|
|
67
|
+
self.escrow_payment = EscrowPaymentConditionContract(self.web3)
|
|
68
|
+
self.subscription_nft = SubscriptionNFT(self.web3)
|
|
69
|
+
self.subscription_provider = SubscriptionProvider(self.web3)
|
|
70
|
+
|
|
71
|
+
# load the subscription token to be used for base
|
|
72
|
+
if network == 'BASE':
|
|
73
|
+
self.subscription_token = SubscriptionToken(self.web3)
|
|
74
|
+
|
|
75
|
+
self.agreement_storage_manager = AgreementStorageManagerContract(self.web3)
|
|
76
|
+
|
|
77
|
+
self.token_address = self.web3.to_checksum_address(get_variable_value("TOKEN_ADDRESS"))
|
|
78
|
+
self.subscription_nft_address = self.web3.to_checksum_address(get_variable_value("SUBSCRIPTION_NFT_ADDRESS"))
|
|
79
|
+
self.subscription_credits = int(os.getenv("SUBSCRIPTION_CREDITS", "1"))
|
|
80
|
+
self.amounts = [int(os.getenv("PLAN_FEE_NVM", "0")), int(os.getenv("PLAN_PRICE_MECHS", "0"))]
|
|
81
|
+
self.subscription_id = CONFIGS[network]["nvm"]["subscription_id"]
|
|
82
|
+
|
|
83
|
+
logger.info("SubscriptionManager initialized")
|
|
84
|
+
|
|
85
|
+
def _generate_agreement_id_seed(self, length: int = 64) -> str:
|
|
86
|
+
"""Generate a random hex string prefixed with 0x."""
|
|
87
|
+
seed = ''
|
|
88
|
+
while len(seed) < length:
|
|
89
|
+
seed += uuid.uuid4().hex
|
|
90
|
+
return '0x' + seed[:length]
|
|
91
|
+
|
|
92
|
+
def create_subscription(self, did: str, wallet_pvt_key: str, chain_id: int) -> Dict[str, Any]:
|
|
93
|
+
"""
|
|
94
|
+
Execute the workflow to create a subscription for the given DID.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
did (str): Decentralized Identifier for the asset.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Dict[str, Any]: A dictionary containing transaction status and receipt.
|
|
101
|
+
"""
|
|
102
|
+
logger.info(f"Creating subscription for DID: {did}")
|
|
103
|
+
|
|
104
|
+
did = did.replace("did:nv:", "0x")
|
|
105
|
+
|
|
106
|
+
ddo = self.did_registry.get_ddo(did)
|
|
107
|
+
service = next((s for s in ddo.get("service", []) if s.get("type") == "nft-sales"), None)
|
|
108
|
+
if not service:
|
|
109
|
+
logger.error("No nft-sales service found in DDO")
|
|
110
|
+
return {"status": "error", "message": "No nft-sales service in DDO"}
|
|
111
|
+
|
|
112
|
+
self.publisher = service["templateId"]
|
|
113
|
+
|
|
114
|
+
conditions = service["attributes"]["serviceAgreementTemplate"]["conditions"]
|
|
115
|
+
reward_address = self.escrow_payment.address
|
|
116
|
+
receivers = conditions[0]["parameters"][-1]["value"]
|
|
117
|
+
|
|
118
|
+
agreement_id_seed = self._generate_agreement_id_seed()
|
|
119
|
+
agreement_id = self.agreement_storage_manager.agreement_id(agreement_id_seed, self.sender)
|
|
120
|
+
|
|
121
|
+
# Condition hashes
|
|
122
|
+
lock_hash = self.lock_payment.hash_values(did, reward_address, self.token_address, self.amounts, receivers)
|
|
123
|
+
lock_id = self.lock_payment.generate_id(agreement_id, lock_hash)
|
|
124
|
+
|
|
125
|
+
transfer_hash = self.transfer_nft.hash_values(
|
|
126
|
+
did,
|
|
127
|
+
ddo["owner"],
|
|
128
|
+
self.sender,
|
|
129
|
+
self.subscription_credits,
|
|
130
|
+
lock_id,
|
|
131
|
+
self.subscription_nft_address,
|
|
132
|
+
False
|
|
133
|
+
)
|
|
134
|
+
transfer_id = self.transfer_nft.generate_id(agreement_id, transfer_hash)
|
|
135
|
+
|
|
136
|
+
escrow_hash = self.escrow_payment.hash_values(
|
|
137
|
+
did,
|
|
138
|
+
self.amounts,
|
|
139
|
+
receivers,
|
|
140
|
+
self.sender,
|
|
141
|
+
reward_address,
|
|
142
|
+
self.token_address,
|
|
143
|
+
lock_id,
|
|
144
|
+
transfer_id
|
|
145
|
+
)
|
|
146
|
+
escrow_id = self.escrow_payment.generate_id(agreement_id, escrow_hash)
|
|
147
|
+
|
|
148
|
+
user_credit_balance_before = self.subscription_nft.get_balance(
|
|
149
|
+
self.sender, self.subscription_id
|
|
150
|
+
)
|
|
151
|
+
print(f"Sender credits Before Purchase: {user_credit_balance_before}")
|
|
152
|
+
|
|
153
|
+
# we set value as xdai is used as subscription for gnosis
|
|
154
|
+
value_eth = 1
|
|
155
|
+
if chain_id == 8453:
|
|
156
|
+
# for base, usdc is used and so we don't send any value
|
|
157
|
+
value_eth = 0
|
|
158
|
+
|
|
159
|
+
approve_tx = self.subscription_token.build_approve_token_tx(
|
|
160
|
+
sender=self.sender,
|
|
161
|
+
to=self.lock_payment.address,
|
|
162
|
+
amount=10**6,
|
|
163
|
+
chain_id=chain_id,
|
|
164
|
+
)
|
|
165
|
+
signed_tx = self.web3.eth.account.sign_transaction(approve_tx, private_key=wallet_pvt_key)
|
|
166
|
+
tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction)
|
|
167
|
+
receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash)
|
|
168
|
+
|
|
169
|
+
if receipt["status"] == 1:
|
|
170
|
+
logger.info("Approve transaction validated successfully")
|
|
171
|
+
logger.info({"status": "success", "tx_hash": tx_hash.hex()})
|
|
172
|
+
else:
|
|
173
|
+
logger.error("Approve transaction failed")
|
|
174
|
+
return {"status": "failed", "receipt": dict(receipt)}
|
|
175
|
+
|
|
176
|
+
# Build transaction
|
|
177
|
+
tx = self.nft_sales.build_create_agreement_tx(
|
|
178
|
+
agreement_id_seed=agreement_id_seed,
|
|
179
|
+
did=did,
|
|
180
|
+
condition_seeds=[lock_hash, transfer_hash, escrow_hash],
|
|
181
|
+
timelocks=[0, 0, 0],
|
|
182
|
+
timeouts=[0, 90, 0],
|
|
183
|
+
publisher=self.sender,
|
|
184
|
+
service_index=0,
|
|
185
|
+
reward_address=reward_address,
|
|
186
|
+
token_address=self.token_address,
|
|
187
|
+
amounts=self.amounts,
|
|
188
|
+
receivers=receivers,
|
|
189
|
+
sender=self.sender,
|
|
190
|
+
value_eth=value_eth,
|
|
191
|
+
chain_id=chain_id
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
signed_tx = self.web3.eth.account.sign_transaction(tx, private_key=wallet_pvt_key)
|
|
195
|
+
tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction)
|
|
196
|
+
receipt = self.web3.eth.wait_for_transaction_receipt(transaction_hash=tx_hash)
|
|
197
|
+
|
|
198
|
+
if receipt["status"] == 1:
|
|
199
|
+
logger.info("Subscription transaction validated successfully")
|
|
200
|
+
logger.info({"status": "success", "tx_hash": tx_hash.hex()})
|
|
201
|
+
else:
|
|
202
|
+
logger.error("Subscription transaction failed")
|
|
203
|
+
return {"status": "failed", "receipt": dict(receipt)}
|
|
204
|
+
|
|
205
|
+
fulfill_for_delegate_params = (
|
|
206
|
+
# nftHolder
|
|
207
|
+
ddo["owner"],
|
|
208
|
+
# nftReceiver
|
|
209
|
+
self.sender,
|
|
210
|
+
# nftAmount
|
|
211
|
+
self.subscription_credits,
|
|
212
|
+
# lockPaymentCondition
|
|
213
|
+
"0x" + lock_id.hex(),
|
|
214
|
+
# nftContractAddress
|
|
215
|
+
self.subscription_nft_address,
|
|
216
|
+
# transfer
|
|
217
|
+
False,
|
|
218
|
+
# expirationBlock
|
|
219
|
+
0,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
fulfill_params = (
|
|
223
|
+
# amounts
|
|
224
|
+
self.amounts,
|
|
225
|
+
# receivers
|
|
226
|
+
receivers,
|
|
227
|
+
# returnAddress
|
|
228
|
+
self.sender,
|
|
229
|
+
# lockPaymentAddress
|
|
230
|
+
reward_address,
|
|
231
|
+
# tokenAddress
|
|
232
|
+
self.token_address,
|
|
233
|
+
# lockCondition
|
|
234
|
+
"0x" + lock_id.hex(),
|
|
235
|
+
# releaseCondition
|
|
236
|
+
"0x" + transfer_id.hex(),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
fulfill_tx = self.subscription_provider.build_create_fulfill_tx(
|
|
240
|
+
agreement_id_seed="0x" + agreement_id.hex(),
|
|
241
|
+
did=did,
|
|
242
|
+
fulfill_for_delegate_params=fulfill_for_delegate_params,
|
|
243
|
+
fulfill_params=fulfill_params,
|
|
244
|
+
sender=self.sender,
|
|
245
|
+
value_eth=0,
|
|
246
|
+
chain_id=chain_id,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
signed_tx = self.web3.eth.account.sign_transaction(
|
|
250
|
+
fulfill_tx, private_key=wallet_pvt_key
|
|
251
|
+
)
|
|
252
|
+
tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction)
|
|
253
|
+
receipt = self.web3.eth.wait_for_transaction_receipt(transaction_hash=tx_hash)
|
|
254
|
+
|
|
255
|
+
user_credit_balance_after = self.subscription_nft.get_balance(
|
|
256
|
+
self.sender, self.subscription_id
|
|
257
|
+
)
|
|
258
|
+
print(f"Sender credits After Purchase: {user_credit_balance_after}")
|
|
259
|
+
|
|
260
|
+
if receipt["status"] == 1:
|
|
261
|
+
logger.info("Subscription purchased transaction successfully")
|
|
262
|
+
return {"status": "success", "tx_hash": tx_hash.hex()}
|
|
263
|
+
else:
|
|
264
|
+
logger.error("Subscription purchase transaction failed")
|
|
265
|
+
return {"status": "failed", "receipt": dict(receipt)}
|