synapse-filecoin-sdk 0.1.0__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.
- pynapse/__init__.py +6 -0
- pynapse/_version.py +1 -0
- pynapse/contracts/__init__.py +34 -0
- pynapse/contracts/abi_registry.py +11 -0
- pynapse/contracts/addresses.json +30 -0
- pynapse/contracts/erc20_abi.json +92 -0
- pynapse/contracts/errorsAbi.json +933 -0
- pynapse/contracts/filecoinPayV1Abi.json +2424 -0
- pynapse/contracts/filecoinWarmStorageServiceAbi.json +2363 -0
- pynapse/contracts/filecoinWarmStorageServiceStateViewAbi.json +651 -0
- pynapse/contracts/generated.py +35 -0
- pynapse/contracts/payments_abi.json +205 -0
- pynapse/contracts/pdpVerifierAbi.json +1266 -0
- pynapse/contracts/providerIdSetAbi.json +161 -0
- pynapse/contracts/serviceProviderRegistryAbi.json +1479 -0
- pynapse/contracts/sessionKeyRegistryAbi.json +147 -0
- pynapse/core/__init__.py +68 -0
- pynapse/core/abis.py +25 -0
- pynapse/core/chains.py +97 -0
- pynapse/core/constants.py +27 -0
- pynapse/core/errors.py +22 -0
- pynapse/core/piece.py +263 -0
- pynapse/core/rand.py +14 -0
- pynapse/core/typed_data.py +320 -0
- pynapse/core/utils.py +30 -0
- pynapse/evm/__init__.py +3 -0
- pynapse/evm/client.py +26 -0
- pynapse/filbeam/__init__.py +3 -0
- pynapse/filbeam/service.py +39 -0
- pynapse/payments/__init__.py +17 -0
- pynapse/payments/service.py +826 -0
- pynapse/pdp/__init__.py +21 -0
- pynapse/pdp/server.py +331 -0
- pynapse/pdp/types.py +38 -0
- pynapse/pdp/verifier.py +82 -0
- pynapse/retriever/__init__.py +12 -0
- pynapse/retriever/async_chain.py +227 -0
- pynapse/retriever/chain.py +209 -0
- pynapse/session/__init__.py +12 -0
- pynapse/session/key.py +30 -0
- pynapse/session/permissions.py +57 -0
- pynapse/session/registry.py +90 -0
- pynapse/sp_registry/__init__.py +11 -0
- pynapse/sp_registry/capabilities.py +25 -0
- pynapse/sp_registry/pdp_capabilities.py +102 -0
- pynapse/sp_registry/service.py +446 -0
- pynapse/sp_registry/types.py +52 -0
- pynapse/storage/__init__.py +57 -0
- pynapse/storage/async_context.py +682 -0
- pynapse/storage/async_manager.py +757 -0
- pynapse/storage/context.py +680 -0
- pynapse/storage/manager.py +758 -0
- pynapse/synapse.py +191 -0
- pynapse/utils/__init__.py +25 -0
- pynapse/utils/constants.py +25 -0
- pynapse/utils/errors.py +3 -0
- pynapse/utils/metadata.py +35 -0
- pynapse/utils/piece_url.py +16 -0
- pynapse/warm_storage/__init__.py +13 -0
- pynapse/warm_storage/service.py +513 -0
- synapse_filecoin_sdk-0.1.0.dist-info/METADATA +74 -0
- synapse_filecoin_sdk-0.1.0.dist-info/RECORD +64 -0
- synapse_filecoin_sdk-0.1.0.dist-info/WHEEL +4 -0
- synapse_filecoin_sdk-0.1.0.dist-info/licenses/LICENSE.md +228 -0
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from eth_account import Account
|
|
7
|
+
from web3 import AsyncWeb3, Web3
|
|
8
|
+
|
|
9
|
+
from pynapse.contracts import ERC20_ABI, PAYMENTS_ABI
|
|
10
|
+
from pynapse.core.chains import Chain
|
|
11
|
+
from pynapse.utils.constants import TOKENS
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class AccountInfo:
|
|
16
|
+
funds: int
|
|
17
|
+
lockup_current: int
|
|
18
|
+
lockup_rate: int
|
|
19
|
+
lockup_last_settled_at: int
|
|
20
|
+
funded_until_epoch: int
|
|
21
|
+
available_funds: int
|
|
22
|
+
current_lockup_rate: int
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class ServiceApproval:
|
|
27
|
+
"""Operator approval status and allowances."""
|
|
28
|
+
is_approved: bool
|
|
29
|
+
rate_allowance: int
|
|
30
|
+
lockup_allowance: int
|
|
31
|
+
max_lockup_period: int
|
|
32
|
+
rate_usage: int = 0
|
|
33
|
+
lockup_usage: int = 0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class RailInfo:
|
|
38
|
+
"""Information about a payment rail."""
|
|
39
|
+
rail_id: int
|
|
40
|
+
token: str
|
|
41
|
+
from_address: str
|
|
42
|
+
to_address: str
|
|
43
|
+
operator: str
|
|
44
|
+
validator: str
|
|
45
|
+
payment_rate: int
|
|
46
|
+
lockup_period: int
|
|
47
|
+
lockup_fixed: int
|
|
48
|
+
settled_up_to: int
|
|
49
|
+
end_epoch: int
|
|
50
|
+
commission_rate_bps: int
|
|
51
|
+
service_fee_recipient: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class SettlementResult:
|
|
56
|
+
"""Result of a settlement operation."""
|
|
57
|
+
total_settled_amount: int
|
|
58
|
+
total_net_payee_amount: int
|
|
59
|
+
total_operator_commission: int
|
|
60
|
+
total_network_fee: int
|
|
61
|
+
final_settled_epoch: int
|
|
62
|
+
note: int
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class SyncPaymentsService:
|
|
66
|
+
def __init__(self, web3: Web3, chain: Chain, account_address: str, private_key: Optional[str] = None) -> None:
|
|
67
|
+
self._web3 = web3
|
|
68
|
+
self._chain = chain
|
|
69
|
+
self._account = account_address
|
|
70
|
+
self._private_key = private_key
|
|
71
|
+
self._payments = self._web3.eth.contract(address=chain.contracts.payments, abi=PAYMENTS_ABI)
|
|
72
|
+
self._erc20 = self._web3.eth.contract(address=chain.contracts.usdfc, abi=ERC20_ABI)
|
|
73
|
+
|
|
74
|
+
def balance(self, token: str = TOKENS["USDFC"]) -> int:
|
|
75
|
+
if token != TOKENS["USDFC"]:
|
|
76
|
+
raise ValueError("Only USDFC is supported for payments contract balance")
|
|
77
|
+
funds, _, _, _ = self._payments.functions.accounts(self._chain.contracts.usdfc, self._account).call()
|
|
78
|
+
return int(funds)
|
|
79
|
+
|
|
80
|
+
def account_info(self, token: str = TOKENS["USDFC"]) -> AccountInfo:
|
|
81
|
+
if token != TOKENS["USDFC"]:
|
|
82
|
+
raise ValueError("Only USDFC is supported for payments contract account info")
|
|
83
|
+
funds, lockup_current, lockup_rate, lockup_last = self._payments.functions.accounts(
|
|
84
|
+
self._chain.contracts.usdfc, self._account
|
|
85
|
+
).call()
|
|
86
|
+
funded_until, _, available, current_lockup_rate = self._payments.functions.getAccountInfoIfSettled(
|
|
87
|
+
self._chain.contracts.usdfc, self._account
|
|
88
|
+
).call()
|
|
89
|
+
return AccountInfo(
|
|
90
|
+
funds=int(funds),
|
|
91
|
+
lockup_current=int(lockup_current),
|
|
92
|
+
lockup_rate=int(lockup_rate),
|
|
93
|
+
lockup_last_settled_at=int(lockup_last),
|
|
94
|
+
funded_until_epoch=int(funded_until),
|
|
95
|
+
available_funds=int(available),
|
|
96
|
+
current_lockup_rate=int(current_lockup_rate),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def wallet_balance(self, token: Optional[str] = None) -> int:
|
|
100
|
+
if token is None or token == TOKENS["FIL"]:
|
|
101
|
+
return int(self._web3.eth.get_balance(self._account))
|
|
102
|
+
if token == TOKENS["USDFC"]:
|
|
103
|
+
return int(self._erc20.functions.balanceOf(self._account).call())
|
|
104
|
+
raise ValueError(f"Unsupported token {token}")
|
|
105
|
+
|
|
106
|
+
def allowance(self, spender: str, token: str = TOKENS["USDFC"]) -> int:
|
|
107
|
+
if token != TOKENS["USDFC"]:
|
|
108
|
+
raise ValueError("Only USDFC is supported for allowance")
|
|
109
|
+
return int(self._erc20.functions.allowance(self._account, spender).call())
|
|
110
|
+
|
|
111
|
+
def approve(self, spender: str, amount: int, token: str = TOKENS["USDFC"]) -> str:
|
|
112
|
+
if token != TOKENS["USDFC"]:
|
|
113
|
+
raise ValueError("Only USDFC is supported for approve")
|
|
114
|
+
if not self._private_key:
|
|
115
|
+
raise ValueError("private_key required for approve")
|
|
116
|
+
txn = self._erc20.functions.approve(spender, amount).build_transaction(
|
|
117
|
+
{
|
|
118
|
+
"from": self._account,
|
|
119
|
+
"nonce": self._web3.eth.get_transaction_count(self._account),
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
signed = self._web3.eth.account.sign_transaction(txn, private_key=self._private_key)
|
|
123
|
+
tx_hash = self._web3.eth.send_raw_transaction(signed.rawTransaction)
|
|
124
|
+
return tx_hash.hex()
|
|
125
|
+
|
|
126
|
+
def deposit(self, amount: int, to: Optional[str] = None, token: str = TOKENS["USDFC"]) -> str:
|
|
127
|
+
if token != TOKENS["USDFC"]:
|
|
128
|
+
raise ValueError("Only USDFC is supported for deposit")
|
|
129
|
+
if not self._private_key:
|
|
130
|
+
raise ValueError("private_key required for deposit")
|
|
131
|
+
to_addr = to or self._account
|
|
132
|
+
txn = self._payments.functions.deposit(self._chain.contracts.usdfc, to_addr, amount).build_transaction(
|
|
133
|
+
{
|
|
134
|
+
"from": self._account,
|
|
135
|
+
"nonce": self._web3.eth.get_transaction_count(self._account),
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
signed = self._web3.eth.account.sign_transaction(txn, private_key=self._private_key)
|
|
139
|
+
tx_hash = self._web3.eth.send_raw_transaction(signed.rawTransaction)
|
|
140
|
+
return tx_hash.hex()
|
|
141
|
+
|
|
142
|
+
def withdraw(self, amount: int, token: str = TOKENS["USDFC"]) -> str:
|
|
143
|
+
if token != TOKENS["USDFC"]:
|
|
144
|
+
raise ValueError("Only USDFC is supported for withdraw")
|
|
145
|
+
if not self._private_key:
|
|
146
|
+
raise ValueError("private_key required for withdraw")
|
|
147
|
+
txn = self._payments.functions.withdraw(self._chain.contracts.usdfc, amount).build_transaction(
|
|
148
|
+
{
|
|
149
|
+
"from": self._account,
|
|
150
|
+
"nonce": self._web3.eth.get_transaction_count(self._account),
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
signed = self._web3.eth.account.sign_transaction(txn, private_key=self._private_key)
|
|
154
|
+
tx_hash = self._web3.eth.send_raw_transaction(signed.rawTransaction)
|
|
155
|
+
return tx_hash.hex()
|
|
156
|
+
|
|
157
|
+
def service_approval(self, service: str, token: str = TOKENS["USDFC"]) -> ServiceApproval:
|
|
158
|
+
"""
|
|
159
|
+
Get the operator approval status and allowances for a service.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
service: The service contract address to check
|
|
163
|
+
token: The token to check approval for (defaults to USDFC)
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
ServiceApproval with approval status and allowances
|
|
167
|
+
"""
|
|
168
|
+
if token != TOKENS["USDFC"]:
|
|
169
|
+
raise ValueError("Only USDFC is supported for service_approval")
|
|
170
|
+
|
|
171
|
+
result = self._payments.functions.operatorApprovals(
|
|
172
|
+
self._chain.contracts.usdfc, self._account, service
|
|
173
|
+
).call()
|
|
174
|
+
|
|
175
|
+
# Result format: (isApproved, rateAllowance, lockupAllowance, rateUsage, lockupUsage, maxLockupPeriod)
|
|
176
|
+
return ServiceApproval(
|
|
177
|
+
is_approved=bool(result[0]),
|
|
178
|
+
rate_allowance=int(result[1]),
|
|
179
|
+
lockup_allowance=int(result[2]),
|
|
180
|
+
max_lockup_period=int(result[5]),
|
|
181
|
+
rate_usage=int(result[3]),
|
|
182
|
+
lockup_usage=int(result[4]),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def approve_service(
|
|
186
|
+
self,
|
|
187
|
+
service: str,
|
|
188
|
+
rate_allowance: int,
|
|
189
|
+
lockup_allowance: int,
|
|
190
|
+
max_lockup_period: int,
|
|
191
|
+
token: str = TOKENS["USDFC"],
|
|
192
|
+
) -> str:
|
|
193
|
+
"""
|
|
194
|
+
Approve a service contract to act as an operator for payment rails.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
service: The service contract address to approve
|
|
198
|
+
rate_allowance: Maximum payment rate per epoch the operator can set
|
|
199
|
+
lockup_allowance: Maximum lockup amount the operator can set
|
|
200
|
+
max_lockup_period: Maximum lockup period in epochs the operator can set
|
|
201
|
+
token: The token to approve for (defaults to USDFC)
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Transaction hash
|
|
205
|
+
"""
|
|
206
|
+
if token != TOKENS["USDFC"]:
|
|
207
|
+
raise ValueError("Only USDFC is supported for approve_service")
|
|
208
|
+
if not self._private_key:
|
|
209
|
+
raise ValueError("private_key required for approve_service")
|
|
210
|
+
|
|
211
|
+
txn = self._payments.functions.setOperatorApproval(
|
|
212
|
+
self._chain.contracts.usdfc,
|
|
213
|
+
service,
|
|
214
|
+
True, # approve
|
|
215
|
+
rate_allowance,
|
|
216
|
+
lockup_allowance,
|
|
217
|
+
max_lockup_period,
|
|
218
|
+
).build_transaction(
|
|
219
|
+
{
|
|
220
|
+
"from": self._account,
|
|
221
|
+
"nonce": self._web3.eth.get_transaction_count(self._account),
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
signed = self._web3.eth.account.sign_transaction(txn, private_key=self._private_key)
|
|
225
|
+
tx_hash = self._web3.eth.send_raw_transaction(signed.rawTransaction)
|
|
226
|
+
return tx_hash.hex()
|
|
227
|
+
|
|
228
|
+
def revoke_service(self, service: str, token: str = TOKENS["USDFC"]) -> str:
|
|
229
|
+
"""
|
|
230
|
+
Revoke a service contract's operator approval.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
service: The service contract address to revoke
|
|
234
|
+
token: The token to revoke approval for (defaults to USDFC)
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Transaction hash
|
|
238
|
+
"""
|
|
239
|
+
if token != TOKENS["USDFC"]:
|
|
240
|
+
raise ValueError("Only USDFC is supported for revoke_service")
|
|
241
|
+
if not self._private_key:
|
|
242
|
+
raise ValueError("private_key required for revoke_service")
|
|
243
|
+
|
|
244
|
+
txn = self._payments.functions.setOperatorApproval(
|
|
245
|
+
self._chain.contracts.usdfc,
|
|
246
|
+
service,
|
|
247
|
+
False, # revoke
|
|
248
|
+
0, # rate_allowance (ignored for revoke)
|
|
249
|
+
0, # lockup_allowance (ignored for revoke)
|
|
250
|
+
0, # max_lockup_period (ignored for revoke)
|
|
251
|
+
).build_transaction(
|
|
252
|
+
{
|
|
253
|
+
"from": self._account,
|
|
254
|
+
"nonce": self._web3.eth.get_transaction_count(self._account),
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
signed = self._web3.eth.account.sign_transaction(txn, private_key=self._private_key)
|
|
258
|
+
tx_hash = self._web3.eth.send_raw_transaction(signed.rawTransaction)
|
|
259
|
+
return tx_hash.hex()
|
|
260
|
+
|
|
261
|
+
def get_rail(self, rail_id: int) -> RailInfo:
|
|
262
|
+
"""
|
|
263
|
+
Get detailed information about a specific rail.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
rail_id: The rail ID to query
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Rail information including all parameters and current state
|
|
270
|
+
"""
|
|
271
|
+
result = self._payments.functions.getRail(rail_id).call()
|
|
272
|
+
return RailInfo(
|
|
273
|
+
rail_id=rail_id,
|
|
274
|
+
token=result[0],
|
|
275
|
+
from_address=result[1],
|
|
276
|
+
to_address=result[2],
|
|
277
|
+
operator=result[3],
|
|
278
|
+
validator=result[4],
|
|
279
|
+
payment_rate=int(result[5]),
|
|
280
|
+
lockup_period=int(result[6]),
|
|
281
|
+
lockup_fixed=int(result[7]),
|
|
282
|
+
settled_up_to=int(result[8]),
|
|
283
|
+
end_epoch=int(result[9]),
|
|
284
|
+
commission_rate_bps=int(result[10]),
|
|
285
|
+
service_fee_recipient=result[11],
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
def settle(self, rail_id: int, until_epoch: Optional[int] = None, token: str = TOKENS["USDFC"]) -> str:
|
|
289
|
+
"""
|
|
290
|
+
Settle a payment rail up to a specific epoch.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
rail_id: The rail ID to settle
|
|
294
|
+
until_epoch: The epoch to settle up to (defaults to current block number)
|
|
295
|
+
token: The token to settle (defaults to USDFC)
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Transaction hash
|
|
299
|
+
"""
|
|
300
|
+
if token != TOKENS["USDFC"]:
|
|
301
|
+
raise ValueError("Only USDFC is supported for settle")
|
|
302
|
+
if not self._private_key:
|
|
303
|
+
raise ValueError("private_key required for settle")
|
|
304
|
+
|
|
305
|
+
_until_epoch = until_epoch if until_epoch is not None else self._web3.eth.block_number
|
|
306
|
+
|
|
307
|
+
txn = self._payments.functions.settleRail(
|
|
308
|
+
self._chain.contracts.usdfc, rail_id, _until_epoch
|
|
309
|
+
).build_transaction(
|
|
310
|
+
{
|
|
311
|
+
"from": self._account,
|
|
312
|
+
"nonce": self._web3.eth.get_transaction_count(self._account),
|
|
313
|
+
}
|
|
314
|
+
)
|
|
315
|
+
signed = self._web3.eth.account.sign_transaction(txn, private_key=self._private_key)
|
|
316
|
+
tx_hash = self._web3.eth.send_raw_transaction(signed.rawTransaction)
|
|
317
|
+
return tx_hash.hex()
|
|
318
|
+
|
|
319
|
+
def settle_terminated_rail(self, rail_id: int, token: str = TOKENS["USDFC"]) -> str:
|
|
320
|
+
"""
|
|
321
|
+
Emergency settlement for terminated rails only.
|
|
322
|
+
|
|
323
|
+
Bypasses service contract validation. Can only be called by the client
|
|
324
|
+
after the max settlement epoch has passed.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
rail_id: The rail ID to settle
|
|
328
|
+
token: The token to settle (defaults to USDFC)
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Transaction hash
|
|
332
|
+
"""
|
|
333
|
+
if token != TOKENS["USDFC"]:
|
|
334
|
+
raise ValueError("Only USDFC is supported for settle_terminated_rail")
|
|
335
|
+
if not self._private_key:
|
|
336
|
+
raise ValueError("private_key required for settle_terminated_rail")
|
|
337
|
+
|
|
338
|
+
txn = self._payments.functions.settleTerminatedRailWithoutValidation(
|
|
339
|
+
self._chain.contracts.usdfc, rail_id
|
|
340
|
+
).build_transaction(
|
|
341
|
+
{
|
|
342
|
+
"from": self._account,
|
|
343
|
+
"nonce": self._web3.eth.get_transaction_count(self._account),
|
|
344
|
+
}
|
|
345
|
+
)
|
|
346
|
+
signed = self._web3.eth.account.sign_transaction(txn, private_key=self._private_key)
|
|
347
|
+
tx_hash = self._web3.eth.send_raw_transaction(signed.rawTransaction)
|
|
348
|
+
return tx_hash.hex()
|
|
349
|
+
|
|
350
|
+
def settle_auto(self, rail_id: int, until_epoch: Optional[int] = None, token: str = TOKENS["USDFC"]) -> str:
|
|
351
|
+
"""
|
|
352
|
+
Automatically settle a rail, detecting whether it's terminated or active.
|
|
353
|
+
|
|
354
|
+
For terminated rails: calls settle_terminated_rail()
|
|
355
|
+
For active rails: calls settle() with optional until_epoch
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
rail_id: The rail ID to settle
|
|
359
|
+
until_epoch: The epoch to settle up to (ignored for terminated rails)
|
|
360
|
+
token: The token to settle (defaults to USDFC)
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
Transaction hash
|
|
364
|
+
"""
|
|
365
|
+
rail = self.get_rail(rail_id)
|
|
366
|
+
|
|
367
|
+
if rail.end_epoch > 0:
|
|
368
|
+
# Rail is terminated
|
|
369
|
+
return self.settle_terminated_rail(rail_id, token)
|
|
370
|
+
else:
|
|
371
|
+
# Rail is active
|
|
372
|
+
return self.settle(rail_id, until_epoch, token)
|
|
373
|
+
|
|
374
|
+
def get_rails_as_payer(self, token: str = TOKENS["USDFC"]) -> list:
|
|
375
|
+
"""
|
|
376
|
+
Get all rails where the wallet is the payer.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
token: The token to filter by (defaults to USDFC)
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
List of RailInfo objects
|
|
383
|
+
"""
|
|
384
|
+
if token != TOKENS["USDFC"]:
|
|
385
|
+
raise ValueError("Only USDFC is supported for get_rails_as_payer")
|
|
386
|
+
|
|
387
|
+
results, has_more = self._payments.functions.getRailsForPayerAndToken(
|
|
388
|
+
self._chain.contracts.usdfc, self._account, 0, 100 # offset, limit
|
|
389
|
+
).call()
|
|
390
|
+
|
|
391
|
+
rails = []
|
|
392
|
+
for r in results:
|
|
393
|
+
rails.append(RailInfo(
|
|
394
|
+
rail_id=int(r[0]),
|
|
395
|
+
token=r[1],
|
|
396
|
+
from_address=r[2],
|
|
397
|
+
to_address=r[3],
|
|
398
|
+
operator=r[4],
|
|
399
|
+
validator=r[5],
|
|
400
|
+
payment_rate=int(r[6]),
|
|
401
|
+
lockup_period=int(r[7]),
|
|
402
|
+
lockup_fixed=int(r[8]),
|
|
403
|
+
settled_up_to=int(r[9]),
|
|
404
|
+
end_epoch=int(r[10]),
|
|
405
|
+
commission_rate_bps=int(r[11]),
|
|
406
|
+
service_fee_recipient=r[12],
|
|
407
|
+
))
|
|
408
|
+
return rails
|
|
409
|
+
|
|
410
|
+
def get_rails_as_payee(self, token: str = TOKENS["USDFC"]) -> list:
|
|
411
|
+
"""
|
|
412
|
+
Get all rails where the wallet is the payee.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
token: The token to filter by (defaults to USDFC)
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
List of RailInfo objects
|
|
419
|
+
"""
|
|
420
|
+
if token != TOKENS["USDFC"]:
|
|
421
|
+
raise ValueError("Only USDFC is supported for get_rails_as_payee")
|
|
422
|
+
|
|
423
|
+
results, has_more = self._payments.functions.getRailsForPayeeAndToken(
|
|
424
|
+
self._chain.contracts.usdfc, self._account, 0, 100 # offset, limit
|
|
425
|
+
).call()
|
|
426
|
+
|
|
427
|
+
rails = []
|
|
428
|
+
for r in results:
|
|
429
|
+
rails.append(RailInfo(
|
|
430
|
+
rail_id=int(r[0]),
|
|
431
|
+
token=r[1],
|
|
432
|
+
from_address=r[2],
|
|
433
|
+
to_address=r[3],
|
|
434
|
+
operator=r[4],
|
|
435
|
+
validator=r[5],
|
|
436
|
+
payment_rate=int(r[6]),
|
|
437
|
+
lockup_period=int(r[7]),
|
|
438
|
+
lockup_fixed=int(r[8]),
|
|
439
|
+
settled_up_to=int(r[9]),
|
|
440
|
+
end_epoch=int(r[10]),
|
|
441
|
+
commission_rate_bps=int(r[11]),
|
|
442
|
+
service_fee_recipient=r[12],
|
|
443
|
+
))
|
|
444
|
+
return rails
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
class AsyncPaymentsService:
|
|
448
|
+
def __init__(self, web3: AsyncWeb3, chain: Chain, account_address: str, private_key: Optional[str] = None) -> None:
|
|
449
|
+
self._web3 = web3
|
|
450
|
+
self._chain = chain
|
|
451
|
+
self._account = account_address
|
|
452
|
+
self._private_key = private_key
|
|
453
|
+
self._payments = self._web3.eth.contract(address=chain.contracts.payments, abi=PAYMENTS_ABI)
|
|
454
|
+
self._erc20 = self._web3.eth.contract(address=chain.contracts.usdfc, abi=ERC20_ABI)
|
|
455
|
+
|
|
456
|
+
async def balance(self, token: str = TOKENS["USDFC"]) -> int:
|
|
457
|
+
if token != TOKENS["USDFC"]:
|
|
458
|
+
raise ValueError("Only USDFC is supported for payments contract balance")
|
|
459
|
+
funds, _, _, _ = await self._payments.functions.accounts(self._chain.contracts.usdfc, self._account).call()
|
|
460
|
+
return int(funds)
|
|
461
|
+
|
|
462
|
+
async def account_info(self, token: str = TOKENS["USDFC"]) -> AccountInfo:
|
|
463
|
+
if token != TOKENS["USDFC"]:
|
|
464
|
+
raise ValueError("Only USDFC is supported for payments contract account info")
|
|
465
|
+
funds, lockup_current, lockup_rate, lockup_last = await self._payments.functions.accounts(
|
|
466
|
+
self._chain.contracts.usdfc, self._account
|
|
467
|
+
).call()
|
|
468
|
+
funded_until, _, available, current_lockup_rate = await self._payments.functions.getAccountInfoIfSettled(
|
|
469
|
+
self._chain.contracts.usdfc, self._account
|
|
470
|
+
).call()
|
|
471
|
+
return AccountInfo(
|
|
472
|
+
funds=int(funds),
|
|
473
|
+
lockup_current=int(lockup_current),
|
|
474
|
+
lockup_rate=int(lockup_rate),
|
|
475
|
+
lockup_last_settled_at=int(lockup_last),
|
|
476
|
+
funded_until_epoch=int(funded_until),
|
|
477
|
+
available_funds=int(available),
|
|
478
|
+
current_lockup_rate=int(current_lockup_rate),
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
async def wallet_balance(self, token: Optional[str] = None) -> int:
|
|
482
|
+
if token is None or token == TOKENS["FIL"]:
|
|
483
|
+
return int(await self._web3.eth.get_balance(self._account))
|
|
484
|
+
if token == TOKENS["USDFC"]:
|
|
485
|
+
return int(await self._erc20.functions.balanceOf(self._account).call())
|
|
486
|
+
raise ValueError(f"Unsupported token {token}")
|
|
487
|
+
|
|
488
|
+
async def allowance(self, spender: str, token: str = TOKENS["USDFC"]) -> int:
|
|
489
|
+
if token != TOKENS["USDFC"]:
|
|
490
|
+
raise ValueError("Only USDFC is supported for allowance")
|
|
491
|
+
return int(await self._erc20.functions.allowance(self._account, spender).call())
|
|
492
|
+
|
|
493
|
+
async def approve(self, spender: str, amount: int, token: str = TOKENS["USDFC"]) -> str:
|
|
494
|
+
if token != TOKENS["USDFC"]:
|
|
495
|
+
raise ValueError("Only USDFC is supported for approve")
|
|
496
|
+
if not self._private_key:
|
|
497
|
+
raise ValueError("private_key required for approve")
|
|
498
|
+
txn = await self._erc20.functions.approve(spender, amount).build_transaction(
|
|
499
|
+
{
|
|
500
|
+
"from": self._account,
|
|
501
|
+
"nonce": await self._web3.eth.get_transaction_count(self._account),
|
|
502
|
+
}
|
|
503
|
+
)
|
|
504
|
+
signed = Account.sign_transaction(txn, private_key=self._private_key)
|
|
505
|
+
tx_hash = await self._web3.eth.send_raw_transaction(signed.rawTransaction)
|
|
506
|
+
return tx_hash.hex()
|
|
507
|
+
|
|
508
|
+
async def deposit(self, amount: int, to: Optional[str] = None, token: str = TOKENS["USDFC"]) -> str:
|
|
509
|
+
if token != TOKENS["USDFC"]:
|
|
510
|
+
raise ValueError("Only USDFC is supported for deposit")
|
|
511
|
+
if not self._private_key:
|
|
512
|
+
raise ValueError("private_key required for deposit")
|
|
513
|
+
to_addr = to or self._account
|
|
514
|
+
txn = await self._payments.functions.deposit(self._chain.contracts.usdfc, to_addr, amount).build_transaction(
|
|
515
|
+
{
|
|
516
|
+
"from": self._account,
|
|
517
|
+
"nonce": await self._web3.eth.get_transaction_count(self._account),
|
|
518
|
+
}
|
|
519
|
+
)
|
|
520
|
+
signed = Account.sign_transaction(txn, private_key=self._private_key)
|
|
521
|
+
tx_hash = await self._web3.eth.send_raw_transaction(signed.rawTransaction)
|
|
522
|
+
return tx_hash.hex()
|
|
523
|
+
|
|
524
|
+
async def withdraw(self, amount: int, token: str = TOKENS["USDFC"]) -> str:
|
|
525
|
+
if token != TOKENS["USDFC"]:
|
|
526
|
+
raise ValueError("Only USDFC is supported for withdraw")
|
|
527
|
+
if not self._private_key:
|
|
528
|
+
raise ValueError("private_key required for withdraw")
|
|
529
|
+
txn = await self._payments.functions.withdraw(self._chain.contracts.usdfc, amount).build_transaction(
|
|
530
|
+
{
|
|
531
|
+
"from": self._account,
|
|
532
|
+
"nonce": await self._web3.eth.get_transaction_count(self._account),
|
|
533
|
+
}
|
|
534
|
+
)
|
|
535
|
+
signed = Account.sign_transaction(txn, private_key=self._private_key)
|
|
536
|
+
tx_hash = await self._web3.eth.send_raw_transaction(signed.rawTransaction)
|
|
537
|
+
return tx_hash.hex()
|
|
538
|
+
|
|
539
|
+
async def service_approval(self, service: str, token: str = TOKENS["USDFC"]) -> ServiceApproval:
|
|
540
|
+
"""
|
|
541
|
+
Get the operator approval status and allowances for a service.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
service: The service contract address to check
|
|
545
|
+
token: The token to check approval for (defaults to USDFC)
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
ServiceApproval with approval status and allowances
|
|
549
|
+
"""
|
|
550
|
+
if token != TOKENS["USDFC"]:
|
|
551
|
+
raise ValueError("Only USDFC is supported for service_approval")
|
|
552
|
+
|
|
553
|
+
result = await self._payments.functions.operatorApprovals(
|
|
554
|
+
self._chain.contracts.usdfc, self._account, service
|
|
555
|
+
).call()
|
|
556
|
+
|
|
557
|
+
# Result format: (isApproved, rateAllowance, lockupAllowance, rateUsage, lockupUsage, maxLockupPeriod)
|
|
558
|
+
return ServiceApproval(
|
|
559
|
+
is_approved=bool(result[0]),
|
|
560
|
+
rate_allowance=int(result[1]),
|
|
561
|
+
lockup_allowance=int(result[2]),
|
|
562
|
+
max_lockup_period=int(result[5]),
|
|
563
|
+
rate_usage=int(result[3]),
|
|
564
|
+
lockup_usage=int(result[4]),
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
async def approve_service(
|
|
568
|
+
self,
|
|
569
|
+
service: str,
|
|
570
|
+
rate_allowance: int,
|
|
571
|
+
lockup_allowance: int,
|
|
572
|
+
max_lockup_period: int,
|
|
573
|
+
token: str = TOKENS["USDFC"],
|
|
574
|
+
) -> str:
|
|
575
|
+
"""
|
|
576
|
+
Approve a service contract to act as an operator for payment rails.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
service: The service contract address to approve
|
|
580
|
+
rate_allowance: Maximum payment rate per epoch the operator can set
|
|
581
|
+
lockup_allowance: Maximum lockup amount the operator can set
|
|
582
|
+
max_lockup_period: Maximum lockup period in epochs the operator can set
|
|
583
|
+
token: The token to approve for (defaults to USDFC)
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
Transaction hash
|
|
587
|
+
"""
|
|
588
|
+
if token != TOKENS["USDFC"]:
|
|
589
|
+
raise ValueError("Only USDFC is supported for approve_service")
|
|
590
|
+
if not self._private_key:
|
|
591
|
+
raise ValueError("private_key required for approve_service")
|
|
592
|
+
|
|
593
|
+
txn = await self._payments.functions.setOperatorApproval(
|
|
594
|
+
self._chain.contracts.usdfc,
|
|
595
|
+
service,
|
|
596
|
+
True,
|
|
597
|
+
rate_allowance,
|
|
598
|
+
lockup_allowance,
|
|
599
|
+
max_lockup_period,
|
|
600
|
+
).build_transaction(
|
|
601
|
+
{
|
|
602
|
+
"from": self._account,
|
|
603
|
+
"nonce": await self._web3.eth.get_transaction_count(self._account),
|
|
604
|
+
}
|
|
605
|
+
)
|
|
606
|
+
signed = Account.sign_transaction(txn, private_key=self._private_key)
|
|
607
|
+
tx_hash = await self._web3.eth.send_raw_transaction(signed.rawTransaction)
|
|
608
|
+
return tx_hash.hex()
|
|
609
|
+
|
|
610
|
+
async def revoke_service(self, service: str, token: str = TOKENS["USDFC"]) -> str:
|
|
611
|
+
"""
|
|
612
|
+
Revoke a service contract's operator approval.
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
service: The service contract address to revoke
|
|
616
|
+
token: The token to revoke approval for (defaults to USDFC)
|
|
617
|
+
|
|
618
|
+
Returns:
|
|
619
|
+
Transaction hash
|
|
620
|
+
"""
|
|
621
|
+
if token != TOKENS["USDFC"]:
|
|
622
|
+
raise ValueError("Only USDFC is supported for revoke_service")
|
|
623
|
+
if not self._private_key:
|
|
624
|
+
raise ValueError("private_key required for revoke_service")
|
|
625
|
+
|
|
626
|
+
txn = await self._payments.functions.setOperatorApproval(
|
|
627
|
+
self._chain.contracts.usdfc,
|
|
628
|
+
service,
|
|
629
|
+
False,
|
|
630
|
+
0,
|
|
631
|
+
0,
|
|
632
|
+
0,
|
|
633
|
+
).build_transaction(
|
|
634
|
+
{
|
|
635
|
+
"from": self._account,
|
|
636
|
+
"nonce": await self._web3.eth.get_transaction_count(self._account),
|
|
637
|
+
}
|
|
638
|
+
)
|
|
639
|
+
signed = Account.sign_transaction(txn, private_key=self._private_key)
|
|
640
|
+
tx_hash = await self._web3.eth.send_raw_transaction(signed.rawTransaction)
|
|
641
|
+
return tx_hash.hex()
|
|
642
|
+
|
|
643
|
+
async def get_rail(self, rail_id: int) -> RailInfo:
|
|
644
|
+
"""
|
|
645
|
+
Get detailed information about a specific rail.
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
rail_id: The rail ID to query
|
|
649
|
+
|
|
650
|
+
Returns:
|
|
651
|
+
Rail information including all parameters and current state
|
|
652
|
+
"""
|
|
653
|
+
result = await self._payments.functions.getRail(rail_id).call()
|
|
654
|
+
return RailInfo(
|
|
655
|
+
rail_id=rail_id,
|
|
656
|
+
token=result[0],
|
|
657
|
+
from_address=result[1],
|
|
658
|
+
to_address=result[2],
|
|
659
|
+
operator=result[3],
|
|
660
|
+
validator=result[4],
|
|
661
|
+
payment_rate=int(result[5]),
|
|
662
|
+
lockup_period=int(result[6]),
|
|
663
|
+
lockup_fixed=int(result[7]),
|
|
664
|
+
settled_up_to=int(result[8]),
|
|
665
|
+
end_epoch=int(result[9]),
|
|
666
|
+
commission_rate_bps=int(result[10]),
|
|
667
|
+
service_fee_recipient=result[11],
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
async def settle(self, rail_id: int, until_epoch: Optional[int] = None, token: str = TOKENS["USDFC"]) -> str:
|
|
671
|
+
"""
|
|
672
|
+
Settle a payment rail up to a specific epoch.
|
|
673
|
+
|
|
674
|
+
Args:
|
|
675
|
+
rail_id: The rail ID to settle
|
|
676
|
+
until_epoch: The epoch to settle up to (defaults to current block number)
|
|
677
|
+
token: The token to settle (defaults to USDFC)
|
|
678
|
+
|
|
679
|
+
Returns:
|
|
680
|
+
Transaction hash
|
|
681
|
+
"""
|
|
682
|
+
if token != TOKENS["USDFC"]:
|
|
683
|
+
raise ValueError("Only USDFC is supported for settle")
|
|
684
|
+
if not self._private_key:
|
|
685
|
+
raise ValueError("private_key required for settle")
|
|
686
|
+
|
|
687
|
+
_until_epoch = until_epoch if until_epoch is not None else await self._web3.eth.block_number
|
|
688
|
+
|
|
689
|
+
txn = await self._payments.functions.settleRail(
|
|
690
|
+
self._chain.contracts.usdfc, rail_id, _until_epoch
|
|
691
|
+
).build_transaction(
|
|
692
|
+
{
|
|
693
|
+
"from": self._account,
|
|
694
|
+
"nonce": await self._web3.eth.get_transaction_count(self._account),
|
|
695
|
+
}
|
|
696
|
+
)
|
|
697
|
+
signed = Account.sign_transaction(txn, private_key=self._private_key)
|
|
698
|
+
tx_hash = await self._web3.eth.send_raw_transaction(signed.rawTransaction)
|
|
699
|
+
return tx_hash.hex()
|
|
700
|
+
|
|
701
|
+
async def settle_terminated_rail(self, rail_id: int, token: str = TOKENS["USDFC"]) -> str:
|
|
702
|
+
"""
|
|
703
|
+
Emergency settlement for terminated rails only.
|
|
704
|
+
|
|
705
|
+
Bypasses service contract validation. Can only be called by the client
|
|
706
|
+
after the max settlement epoch has passed.
|
|
707
|
+
|
|
708
|
+
Args:
|
|
709
|
+
rail_id: The rail ID to settle
|
|
710
|
+
token: The token to settle (defaults to USDFC)
|
|
711
|
+
|
|
712
|
+
Returns:
|
|
713
|
+
Transaction hash
|
|
714
|
+
"""
|
|
715
|
+
if token != TOKENS["USDFC"]:
|
|
716
|
+
raise ValueError("Only USDFC is supported for settle_terminated_rail")
|
|
717
|
+
if not self._private_key:
|
|
718
|
+
raise ValueError("private_key required for settle_terminated_rail")
|
|
719
|
+
|
|
720
|
+
txn = await self._payments.functions.settleTerminatedRailWithoutValidation(
|
|
721
|
+
self._chain.contracts.usdfc, rail_id
|
|
722
|
+
).build_transaction(
|
|
723
|
+
{
|
|
724
|
+
"from": self._account,
|
|
725
|
+
"nonce": await self._web3.eth.get_transaction_count(self._account),
|
|
726
|
+
}
|
|
727
|
+
)
|
|
728
|
+
signed = Account.sign_transaction(txn, private_key=self._private_key)
|
|
729
|
+
tx_hash = await self._web3.eth.send_raw_transaction(signed.rawTransaction)
|
|
730
|
+
return tx_hash.hex()
|
|
731
|
+
|
|
732
|
+
async def settle_auto(self, rail_id: int, until_epoch: Optional[int] = None, token: str = TOKENS["USDFC"]) -> str:
|
|
733
|
+
"""
|
|
734
|
+
Automatically settle a rail, detecting whether it's terminated or active.
|
|
735
|
+
|
|
736
|
+
For terminated rails: calls settle_terminated_rail()
|
|
737
|
+
For active rails: calls settle() with optional until_epoch
|
|
738
|
+
|
|
739
|
+
Args:
|
|
740
|
+
rail_id: The rail ID to settle
|
|
741
|
+
until_epoch: The epoch to settle up to (ignored for terminated rails)
|
|
742
|
+
token: The token to settle (defaults to USDFC)
|
|
743
|
+
|
|
744
|
+
Returns:
|
|
745
|
+
Transaction hash
|
|
746
|
+
"""
|
|
747
|
+
rail = await self.get_rail(rail_id)
|
|
748
|
+
|
|
749
|
+
if rail.end_epoch > 0:
|
|
750
|
+
# Rail is terminated
|
|
751
|
+
return await self.settle_terminated_rail(rail_id, token)
|
|
752
|
+
else:
|
|
753
|
+
# Rail is active
|
|
754
|
+
return await self.settle(rail_id, until_epoch, token)
|
|
755
|
+
|
|
756
|
+
async def get_rails_as_payer(self, token: str = TOKENS["USDFC"]) -> list:
|
|
757
|
+
"""
|
|
758
|
+
Get all rails where the wallet is the payer.
|
|
759
|
+
|
|
760
|
+
Args:
|
|
761
|
+
token: The token to filter by (defaults to USDFC)
|
|
762
|
+
|
|
763
|
+
Returns:
|
|
764
|
+
List of RailInfo objects
|
|
765
|
+
"""
|
|
766
|
+
if token != TOKENS["USDFC"]:
|
|
767
|
+
raise ValueError("Only USDFC is supported for get_rails_as_payer")
|
|
768
|
+
|
|
769
|
+
results, has_more = await self._payments.functions.getRailsForPayerAndToken(
|
|
770
|
+
self._chain.contracts.usdfc, self._account, 0, 100 # offset, limit
|
|
771
|
+
).call()
|
|
772
|
+
|
|
773
|
+
rails = []
|
|
774
|
+
for r in results:
|
|
775
|
+
rails.append(RailInfo(
|
|
776
|
+
rail_id=int(r[0]),
|
|
777
|
+
token=r[1],
|
|
778
|
+
from_address=r[2],
|
|
779
|
+
to_address=r[3],
|
|
780
|
+
operator=r[4],
|
|
781
|
+
validator=r[5],
|
|
782
|
+
payment_rate=int(r[6]),
|
|
783
|
+
lockup_period=int(r[7]),
|
|
784
|
+
lockup_fixed=int(r[8]),
|
|
785
|
+
settled_up_to=int(r[9]),
|
|
786
|
+
end_epoch=int(r[10]),
|
|
787
|
+
commission_rate_bps=int(r[11]),
|
|
788
|
+
service_fee_recipient=r[12],
|
|
789
|
+
))
|
|
790
|
+
return rails
|
|
791
|
+
|
|
792
|
+
async def get_rails_as_payee(self, token: str = TOKENS["USDFC"]) -> list:
|
|
793
|
+
"""
|
|
794
|
+
Get all rails where the wallet is the payee.
|
|
795
|
+
|
|
796
|
+
Args:
|
|
797
|
+
token: The token to filter by (defaults to USDFC)
|
|
798
|
+
|
|
799
|
+
Returns:
|
|
800
|
+
List of RailInfo objects
|
|
801
|
+
"""
|
|
802
|
+
if token != TOKENS["USDFC"]:
|
|
803
|
+
raise ValueError("Only USDFC is supported for get_rails_as_payee")
|
|
804
|
+
|
|
805
|
+
results, has_more = await self._payments.functions.getRailsForPayeeAndToken(
|
|
806
|
+
self._chain.contracts.usdfc, self._account, 0, 100 # offset, limit
|
|
807
|
+
).call()
|
|
808
|
+
|
|
809
|
+
rails = []
|
|
810
|
+
for r in results:
|
|
811
|
+
rails.append(RailInfo(
|
|
812
|
+
rail_id=int(r[0]),
|
|
813
|
+
token=r[1],
|
|
814
|
+
from_address=r[2],
|
|
815
|
+
to_address=r[3],
|
|
816
|
+
operator=r[4],
|
|
817
|
+
validator=r[5],
|
|
818
|
+
payment_rate=int(r[6]),
|
|
819
|
+
lockup_period=int(r[7]),
|
|
820
|
+
lockup_fixed=int(r[8]),
|
|
821
|
+
settled_up_to=int(r[9]),
|
|
822
|
+
end_epoch=int(r[10]),
|
|
823
|
+
commission_rate_bps=int(r[11]),
|
|
824
|
+
service_fee_recipient=r[12],
|
|
825
|
+
))
|
|
826
|
+
return rails
|