ward-protocol 0.1.0__tar.gz
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.
- ward_protocol-0.1.0/PKG-INFO +80 -0
- ward_protocol-0.1.0/README.md +44 -0
- ward_protocol-0.1.0/setup.cfg +4 -0
- ward_protocol-0.1.0/setup.py +42 -0
- ward_protocol-0.1.0/ward/__init__.py +0 -0
- ward_protocol-0.1.0/ward/database.py +266 -0
- ward_protocol-0.1.0/ward/escrow.py +370 -0
- ward_protocol-0.1.0/ward/models/__init__.py +7 -0
- ward_protocol-0.1.0/ward/models/loan.py +81 -0
- ward_protocol-0.1.0/ward/models/loan_broker.py +82 -0
- ward_protocol-0.1.0/ward/models/vault.py +104 -0
- ward_protocol-0.1.0/ward/monitor.py +342 -0
- ward_protocol-0.1.0/ward/payment.py +259 -0
- ward_protocol-0.1.0/ward/policy.py +375 -0
- ward_protocol-0.1.0/ward/pool.py +454 -0
- ward_protocol-0.1.0/ward/premium.py +275 -0
- ward_protocol-0.1.0/ward/utils/__init__.py +0 -0
- ward_protocol-0.1.0/ward/utils/calculations.py +125 -0
- ward_protocol-0.1.0/ward/validator.py +277 -0
- ward_protocol-0.1.0/ward_protocol.egg-info/PKG-INFO +80 -0
- ward_protocol-0.1.0/ward_protocol.egg-info/SOURCES.txt +22 -0
- ward_protocol-0.1.0/ward_protocol.egg-info/dependency_links.txt +1 -0
- ward_protocol-0.1.0/ward_protocol.egg-info/requires.txt +8 -0
- ward_protocol-0.1.0/ward_protocol.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ward-protocol
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: XLS-66 Lending Protocol insurance and monitoring SDK
|
|
5
|
+
Home-page: https://github.com/wflores9/ward-protocol
|
|
6
|
+
Author: Will Flores
|
|
7
|
+
Author-email: wflores@wardprotocol.org
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: xrpl-py>=2.5.0
|
|
20
|
+
Requires-Dist: asyncpg>=0.29.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
24
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
25
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: description-content-type
|
|
31
|
+
Dynamic: home-page
|
|
32
|
+
Dynamic: provides-extra
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
36
|
+
|
|
37
|
+
# Ward Protocol SDK
|
|
38
|
+
|
|
39
|
+
Python SDK for monitoring XLS-66 Lending Protocol defaults and calculating vault depositor losses.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
```bash
|
|
43
|
+
pip install -e .
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
```python
|
|
48
|
+
import asyncio
|
|
49
|
+
from ward.monitor import XLS66Monitor, DefaultEvent
|
|
50
|
+
|
|
51
|
+
async def main():
|
|
52
|
+
# Connect to XRPL
|
|
53
|
+
monitor = XLS66Monitor("wss://xrplcluster.com")
|
|
54
|
+
|
|
55
|
+
# Handle defaults
|
|
56
|
+
@monitor.on_default
|
|
57
|
+
async def handle_default(event: DefaultEvent):
|
|
58
|
+
print(f"Default: {event.vault_loss / 1_000_000:.2f} XRP lost")
|
|
59
|
+
|
|
60
|
+
# Start monitoring
|
|
61
|
+
await monitor.start()
|
|
62
|
+
|
|
63
|
+
asyncio.run(main())
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
|
|
68
|
+
- ✅ Real-time XLS-66 default detection via WebSocket
|
|
69
|
+
- ✅ Automatic vault loss calculation
|
|
70
|
+
- ✅ Share value impact analysis
|
|
71
|
+
- ✅ LoanBroker and Vault state tracking
|
|
72
|
+
- ✅ Async/await support
|
|
73
|
+
|
|
74
|
+
## Examples
|
|
75
|
+
|
|
76
|
+
See `/examples` directory for complete examples.
|
|
77
|
+
|
|
78
|
+
## Documentation
|
|
79
|
+
|
|
80
|
+
Full documentation: https://github.com/wflores9/ward-protocol/docs
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Ward Protocol SDK
|
|
2
|
+
|
|
3
|
+
Python SDK for monitoring XLS-66 Lending Protocol defaults and calculating vault depositor losses.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
```bash
|
|
7
|
+
pip install -e .
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
```python
|
|
12
|
+
import asyncio
|
|
13
|
+
from ward.monitor import XLS66Monitor, DefaultEvent
|
|
14
|
+
|
|
15
|
+
async def main():
|
|
16
|
+
# Connect to XRPL
|
|
17
|
+
monitor = XLS66Monitor("wss://xrplcluster.com")
|
|
18
|
+
|
|
19
|
+
# Handle defaults
|
|
20
|
+
@monitor.on_default
|
|
21
|
+
async def handle_default(event: DefaultEvent):
|
|
22
|
+
print(f"Default: {event.vault_loss / 1_000_000:.2f} XRP lost")
|
|
23
|
+
|
|
24
|
+
# Start monitoring
|
|
25
|
+
await monitor.start()
|
|
26
|
+
|
|
27
|
+
asyncio.run(main())
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- ✅ Real-time XLS-66 default detection via WebSocket
|
|
33
|
+
- ✅ Automatic vault loss calculation
|
|
34
|
+
- ✅ Share value impact analysis
|
|
35
|
+
- ✅ LoanBroker and Vault state tracking
|
|
36
|
+
- ✅ Async/await support
|
|
37
|
+
|
|
38
|
+
## Examples
|
|
39
|
+
|
|
40
|
+
See `/examples` directory for complete examples.
|
|
41
|
+
|
|
42
|
+
## Documentation
|
|
43
|
+
|
|
44
|
+
Full documentation: https://github.com/wflores9/ward-protocol/docs
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Ward Protocol SDK setup."""
|
|
2
|
+
|
|
3
|
+
from setuptools import setup, find_packages
|
|
4
|
+
|
|
5
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
6
|
+
long_description = fh.read()
|
|
7
|
+
|
|
8
|
+
setup(
|
|
9
|
+
name="ward-protocol",
|
|
10
|
+
version="0.1.0",
|
|
11
|
+
author="Will Flores",
|
|
12
|
+
author_email="wflores@wardprotocol.org",
|
|
13
|
+
description="XLS-66 Lending Protocol insurance and monitoring SDK",
|
|
14
|
+
long_description=long_description,
|
|
15
|
+
long_description_content_type="text/markdown",
|
|
16
|
+
url="https://github.com/wflores9/ward-protocol",
|
|
17
|
+
packages=find_packages(),
|
|
18
|
+
classifiers=[
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.8",
|
|
25
|
+
"Programming Language :: Python :: 3.9",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
],
|
|
29
|
+
python_requires=">=3.8",
|
|
30
|
+
install_requires=[
|
|
31
|
+
"xrpl-py>=2.5.0",
|
|
32
|
+
"asyncpg>=0.29.0",
|
|
33
|
+
],
|
|
34
|
+
extras_require={
|
|
35
|
+
"dev": [
|
|
36
|
+
"pytest>=7.0.0",
|
|
37
|
+
"pytest-asyncio>=0.21.0",
|
|
38
|
+
"black>=23.0.0",
|
|
39
|
+
"mypy>=1.0.0",
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Database connection and operations for Ward Protocol."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional, List, Dict, Any
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
import asyncpg
|
|
7
|
+
from contextlib import asynccontextmanager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class WardDatabase:
|
|
11
|
+
"""PostgreSQL database connection for Ward Protocol."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, connection_string: Optional[str] = None):
|
|
14
|
+
"""
|
|
15
|
+
Initialize database connection.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
connection_string: PostgreSQL connection string
|
|
19
|
+
(defaults to DATABASE_URL env var)
|
|
20
|
+
"""
|
|
21
|
+
self.connection_string = connection_string or os.getenv(
|
|
22
|
+
'DATABASE_URL',
|
|
23
|
+
'postgresql://ward:ward@localhost/ward_protocol'
|
|
24
|
+
)
|
|
25
|
+
self.pool: Optional[asyncpg.Pool] = None
|
|
26
|
+
|
|
27
|
+
async def connect(self):
|
|
28
|
+
"""Create connection pool."""
|
|
29
|
+
self.pool = await asyncpg.create_pool(
|
|
30
|
+
self.connection_string,
|
|
31
|
+
min_size=2,
|
|
32
|
+
max_size=10
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
async def disconnect(self):
|
|
36
|
+
"""Close connection pool."""
|
|
37
|
+
if self.pool:
|
|
38
|
+
await self.pool.close()
|
|
39
|
+
|
|
40
|
+
@asynccontextmanager
|
|
41
|
+
async def transaction(self):
|
|
42
|
+
"""Context manager for database transactions."""
|
|
43
|
+
async with self.pool.acquire() as conn:
|
|
44
|
+
async with conn.transaction():
|
|
45
|
+
yield conn
|
|
46
|
+
|
|
47
|
+
# ===== DEFAULT EVENTS =====
|
|
48
|
+
|
|
49
|
+
async def log_default_event(
|
|
50
|
+
self,
|
|
51
|
+
loan_id: str,
|
|
52
|
+
loan_broker_id: str,
|
|
53
|
+
vault_id: str,
|
|
54
|
+
borrower_address: str,
|
|
55
|
+
default_amount: int,
|
|
56
|
+
default_covered: int,
|
|
57
|
+
vault_loss: int,
|
|
58
|
+
tx_hash: str,
|
|
59
|
+
ledger_index: int
|
|
60
|
+
) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Log a default event.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
event_id (UUID)
|
|
66
|
+
"""
|
|
67
|
+
async with self.pool.acquire() as conn:
|
|
68
|
+
row = await conn.fetchrow(
|
|
69
|
+
"""
|
|
70
|
+
INSERT INTO default_events (
|
|
71
|
+
loan_id, loan_broker_id, vault_id, borrower_address,
|
|
72
|
+
default_amount, default_covered, vault_loss,
|
|
73
|
+
tx_hash, ledger_index
|
|
74
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
75
|
+
RETURNING event_id
|
|
76
|
+
""",
|
|
77
|
+
loan_id, loan_broker_id, vault_id, borrower_address,
|
|
78
|
+
default_amount, default_covered, vault_loss,
|
|
79
|
+
tx_hash, ledger_index
|
|
80
|
+
)
|
|
81
|
+
return str(row['event_id'])
|
|
82
|
+
|
|
83
|
+
async def get_default_events(
|
|
84
|
+
self,
|
|
85
|
+
vault_id: Optional[str] = None,
|
|
86
|
+
limit: int = 100
|
|
87
|
+
) -> List[Dict[str, Any]]:
|
|
88
|
+
"""Get default events, optionally filtered by vault."""
|
|
89
|
+
async with self.pool.acquire() as conn:
|
|
90
|
+
if vault_id:
|
|
91
|
+
rows = await conn.fetch(
|
|
92
|
+
"""
|
|
93
|
+
SELECT * FROM default_events
|
|
94
|
+
WHERE vault_id = $1
|
|
95
|
+
ORDER BY detected_at DESC
|
|
96
|
+
LIMIT $2
|
|
97
|
+
""",
|
|
98
|
+
vault_id, limit
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
rows = await conn.fetch(
|
|
102
|
+
"""
|
|
103
|
+
SELECT * FROM default_events
|
|
104
|
+
ORDER BY detected_at DESC
|
|
105
|
+
LIMIT $1
|
|
106
|
+
""",
|
|
107
|
+
limit
|
|
108
|
+
)
|
|
109
|
+
return [dict(row) for row in rows]
|
|
110
|
+
|
|
111
|
+
# ===== CLAIMS =====
|
|
112
|
+
|
|
113
|
+
async def create_claim(
|
|
114
|
+
self,
|
|
115
|
+
policy_id: str,
|
|
116
|
+
loan_id: str,
|
|
117
|
+
loan_manage_tx_hash: str,
|
|
118
|
+
loan_broker_id: str,
|
|
119
|
+
vault_id: str,
|
|
120
|
+
default_amount: int,
|
|
121
|
+
default_covered: int,
|
|
122
|
+
vault_loss: int,
|
|
123
|
+
claim_payout: int
|
|
124
|
+
) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Create a new claim.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
claim_id (UUID)
|
|
130
|
+
"""
|
|
131
|
+
async with self.pool.acquire() as conn:
|
|
132
|
+
row = await conn.fetchrow(
|
|
133
|
+
"""
|
|
134
|
+
INSERT INTO claims (
|
|
135
|
+
policy_id, loan_id, loan_manage_tx_hash,
|
|
136
|
+
loan_broker_id, vault_id,
|
|
137
|
+
default_amount, default_covered, vault_loss,
|
|
138
|
+
claim_payout, status
|
|
139
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'pending')
|
|
140
|
+
RETURNING claim_id
|
|
141
|
+
""",
|
|
142
|
+
policy_id, loan_id, loan_manage_tx_hash,
|
|
143
|
+
loan_broker_id, vault_id,
|
|
144
|
+
default_amount, default_covered, vault_loss,
|
|
145
|
+
claim_payout
|
|
146
|
+
)
|
|
147
|
+
return str(row['claim_id'])
|
|
148
|
+
|
|
149
|
+
async def update_claim_status(
|
|
150
|
+
self,
|
|
151
|
+
claim_id: str,
|
|
152
|
+
status: str,
|
|
153
|
+
escrow_tx_hash: Optional[str] = None,
|
|
154
|
+
settlement_tx_hash: Optional[str] = None,
|
|
155
|
+
rejection_reason: Optional[str] = None
|
|
156
|
+
):
|
|
157
|
+
"""Update claim status."""
|
|
158
|
+
async with self.pool.acquire() as conn:
|
|
159
|
+
if status == 'validated':
|
|
160
|
+
await conn.execute(
|
|
161
|
+
"""
|
|
162
|
+
UPDATE claims
|
|
163
|
+
SET status = $1, validated_at = NOW()
|
|
164
|
+
WHERE claim_id = $2
|
|
165
|
+
""",
|
|
166
|
+
status, claim_id
|
|
167
|
+
)
|
|
168
|
+
elif status == 'escrowed' and escrow_tx_hash:
|
|
169
|
+
await conn.execute(
|
|
170
|
+
"""
|
|
171
|
+
UPDATE claims
|
|
172
|
+
SET status = $1, escrow_tx_hash = $2
|
|
173
|
+
WHERE claim_id = $3
|
|
174
|
+
""",
|
|
175
|
+
status, escrow_tx_hash, claim_id
|
|
176
|
+
)
|
|
177
|
+
elif status == 'settled' and settlement_tx_hash:
|
|
178
|
+
await conn.execute(
|
|
179
|
+
"""
|
|
180
|
+
UPDATE claims
|
|
181
|
+
SET status = $1, settlement_tx_hash = $2, settled_at = NOW()
|
|
182
|
+
WHERE claim_id = $3
|
|
183
|
+
""",
|
|
184
|
+
status, settlement_tx_hash, claim_id
|
|
185
|
+
)
|
|
186
|
+
elif status == 'rejected' and rejection_reason:
|
|
187
|
+
await conn.execute(
|
|
188
|
+
"""
|
|
189
|
+
UPDATE claims
|
|
190
|
+
SET status = $1, rejection_reason = $2
|
|
191
|
+
WHERE claim_id = $3
|
|
192
|
+
""",
|
|
193
|
+
status, rejection_reason, claim_id
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
async def get_claims(
|
|
197
|
+
self,
|
|
198
|
+
policy_id: Optional[str] = None,
|
|
199
|
+
status: Optional[str] = None,
|
|
200
|
+
limit: int = 100
|
|
201
|
+
) -> List[Dict[str, Any]]:
|
|
202
|
+
"""Get claims with optional filters."""
|
|
203
|
+
async with self.pool.acquire() as conn:
|
|
204
|
+
query = "SELECT * FROM claims WHERE 1=1"
|
|
205
|
+
params = []
|
|
206
|
+
param_num = 1
|
|
207
|
+
|
|
208
|
+
if policy_id:
|
|
209
|
+
query += f" AND policy_id = ${param_num}"
|
|
210
|
+
params.append(policy_id)
|
|
211
|
+
param_num += 1
|
|
212
|
+
|
|
213
|
+
if status:
|
|
214
|
+
query += f" AND status = ${param_num}"
|
|
215
|
+
params.append(status)
|
|
216
|
+
param_num += 1
|
|
217
|
+
|
|
218
|
+
query += f" ORDER BY created_at DESC LIMIT ${param_num}"
|
|
219
|
+
params.append(limit)
|
|
220
|
+
|
|
221
|
+
rows = await conn.fetch(query, *params)
|
|
222
|
+
return [dict(row) for row in rows]
|
|
223
|
+
|
|
224
|
+
# ===== MONITORED VAULTS =====
|
|
225
|
+
|
|
226
|
+
async def upsert_vault_state(
|
|
227
|
+
self,
|
|
228
|
+
vault_id: str,
|
|
229
|
+
loan_broker_id: str,
|
|
230
|
+
asset_type: str,
|
|
231
|
+
assets_total: int,
|
|
232
|
+
assets_available: int,
|
|
233
|
+
loss_unrealized: int,
|
|
234
|
+
shares_total: int,
|
|
235
|
+
share_value: float,
|
|
236
|
+
ledger_index: int
|
|
237
|
+
):
|
|
238
|
+
"""Insert or update vault state."""
|
|
239
|
+
async with self.pool.acquire() as conn:
|
|
240
|
+
await conn.execute(
|
|
241
|
+
"""
|
|
242
|
+
INSERT INTO monitored_vaults (
|
|
243
|
+
vault_id, loan_broker_id, asset_type,
|
|
244
|
+
assets_total, assets_available, loss_unrealized,
|
|
245
|
+
shares_total, share_value, last_updated_ledger,
|
|
246
|
+
last_checked
|
|
247
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW())
|
|
248
|
+
ON CONFLICT (vault_id) DO UPDATE SET
|
|
249
|
+
assets_total = EXCLUDED.assets_total,
|
|
250
|
+
assets_available = EXCLUDED.assets_available,
|
|
251
|
+
loss_unrealized = EXCLUDED.loss_unrealized,
|
|
252
|
+
shares_total = EXCLUDED.shares_total,
|
|
253
|
+
share_value = EXCLUDED.share_value,
|
|
254
|
+
last_updated_ledger = EXCLUDED.last_updated_ledger,
|
|
255
|
+
last_checked = NOW()
|
|
256
|
+
""",
|
|
257
|
+
vault_id, loan_broker_id, asset_type,
|
|
258
|
+
assets_total, assets_available, loss_unrealized,
|
|
259
|
+
shares_total, share_value, ledger_index
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
async def get_monitored_vaults(self) -> List[Dict[str, Any]]:
|
|
263
|
+
"""Get all monitored vaults."""
|
|
264
|
+
async with self.pool.acquire() as conn:
|
|
265
|
+
rows = await conn.fetch("SELECT * FROM monitored_vaults")
|
|
266
|
+
return [dict(row) for row in rows]
|