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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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]