razvan commited on
Commit
759d803
Β·
verified Β·
1 Parent(s): 730d4dc

Upload builderbrain/arc_bridge.py

Browse files
Files changed (1) hide show
  1. builderbrain/arc_bridge.py +156 -41
builderbrain/arc_bridge.py CHANGED
@@ -2,15 +2,26 @@
2
  Arc/Circle Integration Bridge
3
  =============================
4
 
5
- Handles:
6
- - Gateway + CCTP for cross-chain USDC movement
7
- - Nanopayments for per-trade micropayments
8
- - Wallets (embedded + strategy wallets)
9
- - Paymaster for gas abstraction
10
- - USYC yield on idle capital
11
- - Contract interactions for on-chain tracking
12
 
13
- This is the settlement layer for BuilderBrain.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  """
15
 
16
  import json
@@ -22,33 +33,67 @@ from datetime import datetime
22
 
23
  @dataclass
24
  class NanopaymentConfig:
25
- """Configuration for Arc Nanopayments."""
 
 
 
 
 
 
 
 
 
 
26
  enabled: bool = True
27
  fee_per_trade_bps: float = 5.0 # 5 bps per trade
28
  fee_per_insight_usd: float = 0.01 # 1c per insight
29
- min_payment_usd: float = 0.001 # 0.1c minimum
30
  max_payment_usd: float = 10.0
31
  settlement_interval_sec: int = 3600 # hourly batching
32
 
33
 
34
  @dataclass
35
  class WalletConfig:
36
- """Embedded wallet configuration."""
 
 
 
 
 
 
 
 
37
  wallet_type: str = "embedded" # 'embedded', 'eoa', 'smart_contract'
38
- chain_id: int = 42161 # Arbitrum (Arc default)
39
- gas_abstraction: bool = True # Paymaster enabled
40
  daily_limit_usd: float = 1000.0
41
  max_single_trade_usd: float = 500.0
42
 
43
 
44
  @dataclass
45
  class USYCConfig:
46
- """USYC yield configuration for idle capital."""
 
 
 
 
 
 
 
 
 
 
47
  enabled: bool = True
48
  apy: float = 0.043 # 4.3% current
49
  auto_rotate_threshold: float = 0.70 # rotate 70% to USYC when risk-off
50
  risk_off_signals: List[str] = None
51
 
 
 
 
 
 
 
52
  def __post_init__(self):
53
  if self.risk_off_signals is None:
54
  self.risk_off_signals = [
@@ -68,22 +113,25 @@ class GatewayRoute:
68
  fee_usd: float
69
  cctp_burn_tx: Optional[str] = None
70
  cctp_mint_tx: Optional[str] = None
 
 
71
 
72
 
73
  class ArcBridge:
74
  """
75
  Bridge to Arc/Circle infrastructure.
76
 
77
- In production, this would:
78
- - Call Arc Gateway API for CCTP transfers
79
- - Submit Nanopayment batches
80
- - Interact with USYC contract for yield
81
- - Use Paymaster for gas abstraction
82
 
83
- For hackathon, we simulate the integration patterns
84
- with structured logging and mock transactions.
 
 
85
  """
86
 
 
87
  SUPPORTED_CHAINS = [
88
  "arbitrum", "base", "optimism", "ethereum", "polygon"
89
  ]
@@ -93,12 +141,15 @@ class ArcBridge:
93
  nanopayment: Optional[NanopaymentConfig] = None,
94
  wallet: Optional[WalletConfig] = None,
95
  usyc: Optional[USYCConfig] = None,
 
96
  ):
97
  self.nanopayment = nanopayment or NanopaymentConfig()
98
  self.wallet = wallet or WalletConfig()
99
  self.usyc = usyc or USYCConfig()
 
100
 
101
- # Simulated state
 
102
  self.balances: Dict[str, float] = {"usdc": 0.0, "usyc": 0.0}
103
  self.pending_payments: List[Dict] = []
104
  self.gateway_routes: List[GatewayRoute] = []
@@ -117,40 +168,62 @@ class ArcBridge:
117
  """
118
  Route USDC between chains via Gateway + CCTP.
119
 
120
- In production: call Arc Gateway API, initiate CCTP burn,
121
- wait for attestation, mint on destination.
 
 
 
 
 
122
  """
 
 
 
 
 
 
 
 
 
 
123
  if from_chain not in self.SUPPORTED_CHAINS:
124
  raise ValueError(f"Unsupported chain: {from_chain}")
125
  if to_chain not in self.SUPPORTED_CHAINS:
126
  raise ValueError(f"Unsupported chain: {to_chain}")
127
 
128
- # Simulate CCTP timing and fees
129
  route = GatewayRoute(
130
  from_chain=from_chain,
131
  to_chain=to_chain,
132
  amount_usd=amount_usd,
133
  estimated_time_sec=1800 if from_chain == "ethereum" else 600,
134
- fee_usd=amount_usd * 0.0005, # 5 bps CCTP fee
 
135
  )
136
 
137
- # Simulate burn
138
  route.cctp_burn_tx = f"0x{hashlib.sha256(f'burn_{amount_usd}'.encode()).hexdigest()[:40]}"
139
 
140
  self.gateway_routes.append(route)
141
-
142
- # Update balances
143
  self.balances["usdc"] -= amount_usd + route.fee_usd
144
 
145
  return route
146
 
147
  def get_balance(self, chain: str = "arbitrum") -> Dict:
148
- """Get USDC balance on a chain."""
 
 
 
 
 
 
 
 
 
149
  return {
150
  "chain": chain,
151
  "usdc": self.balances.get("usdc", 0),
152
  "usyc": self.balances.get("usyc", 0),
153
  "total_usd": self.balances.get("usdc", 0) + self.balances.get("usyc", 0),
 
154
  }
155
 
156
  # ────────────────────────────── Nanopayments ──────────────────────────────
@@ -161,7 +234,17 @@ class ArcBridge:
161
  trace_id: str,
162
  amount_usd: Optional[float] = None,
163
  ) -> Dict:
164
- """Charge per-insight fee via Nanopayments."""
 
 
 
 
 
 
 
 
 
 
165
  if not self.nanopayment.enabled:
166
  return {"status": "skipped", "reason": "nanopayments_disabled"}
167
 
@@ -175,6 +258,7 @@ class ArcBridge:
175
  "amount_usd": amount,
176
  "timestamp": datetime.utcnow().isoformat(),
177
  "status": "pending_batch",
 
178
  }
179
 
180
  self.pending_payments.append(payment)
@@ -186,7 +270,7 @@ class ArcBridge:
186
  trade_id: str,
187
  notional_usd: float,
188
  ) -> Dict:
189
- """Charge per-trade fee via Nanopayments."""
190
  if not self.nanopayment.enabled:
191
  return {"status": "skipped", "reason": "nanopayments_disabled"}
192
 
@@ -201,20 +285,25 @@ class ArcBridge:
201
  "notional_usd": notional_usd,
202
  "timestamp": datetime.utcnow().isoformat(),
203
  "status": "pending_batch",
 
204
  }
205
 
206
  self.pending_payments.append(payment)
207
  return payment
208
 
209
  def batch_settle(self) -> Dict:
210
- """Batch settle pending nanopayments."""
 
 
 
 
 
211
  if not self.pending_payments:
212
  return {"settled": 0, "total_usd": 0}
213
 
214
  total = sum(p["amount_usd"] for p in self.pending_payments)
215
  count = len(self.pending_payments)
216
 
217
- # Simulate batch settlement
218
  batch_tx = f"0x{hashlib.sha256(f'batch_{count}'.encode()).hexdigest()[:40]}"
219
 
220
  for p in self.pending_payments:
@@ -228,6 +317,7 @@ class ArcBridge:
228
  "total_usd": total,
229
  "batch_tx": batch_tx,
230
  "avg_fee_usd": total / count if count > 0 else 0,
 
231
  }
232
 
233
  # ────────────────────────────── Paymaster ──────────────────────────────
@@ -238,14 +328,22 @@ class ArcBridge:
238
  tx_data: Dict,
239
  ) -> Dict:
240
  """
241
- Sponsor gas for a user transaction via Paymaster.
 
 
 
 
 
 
 
 
 
242
 
243
- All UX-facing actions are gas-abstracted and USDC-denominated.
244
  """
245
  if not self.wallet.gas_abstraction:
246
  return {"status": "rejected", "reason": "gas_abstraction_disabled"}
247
 
248
- # Estimate gas cost in USDC
249
  gas_estimate_usd = tx_data.get("gas_estimate", 0.50)
250
 
251
  op = {
@@ -254,7 +352,9 @@ class ArcBridge:
254
  "gas_sponsored_usd": gas_estimate_usd,
255
  "timestamp": datetime.utcnow().isoformat(),
256
  "status": "sponsored",
 
257
  "paymaster_tx": f"0x{hashlib.sha256(json.dumps(tx_data).encode()).hexdigest()[:40]}",
 
258
  }
259
 
260
  self.paymaster_ops.append(op)
@@ -263,7 +363,13 @@ class ArcBridge:
263
  # ────────────────────────────── USYC ──────────────────────────────
264
 
265
  def rotate_to_usyc(self, amount_usd: float) -> Dict:
266
- """Rotate idle USDC to USYC for yield."""
 
 
 
 
 
 
267
  if not self.usyc.enabled:
268
  return {"status": "skipped", "reason": "usyc_disabled"}
269
 
@@ -278,10 +384,12 @@ class ArcBridge:
278
  "amount_usd": amount_usd,
279
  "estimated_apy": self.usyc.apy,
280
  "projected_annual_yield": amount_usd * self.usyc.apy,
 
 
281
  }
282
 
283
  def rotate_from_usyc(self, amount_usd: float) -> Dict:
284
- """Rotate USYC back to USDC for trading."""
285
  if amount_usd > self.balances.get("usyc", 0):
286
  amount_usd = self.balances.get("usyc", 0)
287
 
@@ -291,6 +399,7 @@ class ArcBridge:
291
  return {
292
  "status": "rotated",
293
  "amount_usd": amount_usd,
 
294
  }
295
 
296
  def auto_risk_off_rotation(self, risk_signal: str) -> Dict:
@@ -316,7 +425,8 @@ class ArcBridge:
316
  """
317
  Record on-chain strategy performance metrics.
318
 
319
- In production: call Arc contract to update strategy leaderboard.
 
320
  """
321
  metrics = {
322
  "strategy_id": strategy_id,
@@ -326,6 +436,7 @@ class ArcBridge:
326
  "pnl_usd": pnl_usd,
327
  "timestamp": datetime.utcnow().isoformat(),
328
  "contract_tx": f"0x{hashlib.sha256(f'{strategy_id}_{pnl_usd}'.encode()).hexdigest()[:40]}",
 
329
  }
330
 
331
  self.contract_calls.append(metrics)
@@ -343,5 +454,9 @@ class ArcBridge:
343
  "total_contract_calls": len(self.contract_calls),
344
  "nanopayment_config": asdict(self.nanopayment),
345
  "wallet_config": asdict(self.wallet),
346
- "usyc_config": asdict(self.usyc),
 
 
 
 
347
  }
 
2
  Arc/Circle Integration Bridge
3
  =============================
4
 
5
+ This module provides BOTH:
6
+ 1. REAL API client integration patterns (documented with actual endpoints)
7
+ 2. SIMULATED stubs for hackathon demo without credentials
 
 
 
 
8
 
9
+ Real APIs (from docs):
10
+ - Circle Gateway: https://gateway-api-testnet.circle.com/v1/
11
+ - GET /gateway-info (enumerate domains/chains)
12
+ - POST /balances (query unified USDC balance)
13
+ - POST /transfer (create transfer attestation)
14
+ - Arc Nanopayments: x402 HTTP 402 protocol + EIP-3009 signatures
15
+ (No public REST batch endpoint; uses buyer signatures + Gateway settlement)
16
+ - USYC (Arc Testnet): 0xe9185F0c5F296Ed1797AaE4238D26CCaBEadb86C
17
+ - Teller: 0x9fdF14c5B14173D74C08Af27AebFf39240dC105A
18
+ - Oracle: 0x52b56c7642E71dc54714d879127d97cd0B3D4581
19
+ - Paymaster: ERC-4337 UserOperations (Pimlico on Arc EntryPoint)
20
+ - Wallets: Circle Dev-Controlled Wallets API (accountType: "SCA")
21
+
22
+ See circle_gateway_client.py for the real Circle Gateway HTTP client.
23
+
24
+ Status: Simulated for hackathon demo. Integration paths documented.
25
  """
26
 
27
  import json
 
33
 
34
  @dataclass
35
  class NanopaymentConfig:
36
+ """
37
+ Configuration for Arc Nanopayments.
38
+
39
+ REAL ARCHITECTURE (from docs):
40
+ - Buyers sign EIP-3009 authorizations via x402 client
41
+ - Sellers verify off-chain and log in virtual ledger DB
42
+ - Settlement via Circle Gateway batch transfer (one on-chain tx)
43
+ - Min payment: 0.000001 USDC (1e-6)
44
+
45
+ This config is app-level; actual settlement uses Circle Gateway.
46
+ """
47
  enabled: bool = True
48
  fee_per_trade_bps: float = 5.0 # 5 bps per trade
49
  fee_per_insight_usd: float = 0.01 # 1c per insight
50
+ min_payment_usd: float = 0.00001 # 1e-5 USDC (docs say 1e-6 supported)
51
  max_payment_usd: float = 10.0
52
  settlement_interval_sec: int = 3600 # hourly batching
53
 
54
 
55
  @dataclass
56
  class WalletConfig:
57
+ """
58
+ Embedded wallet configuration.
59
+
60
+ REAL ARCHITECTURE:
61
+ - Use Circle Dev-Controlled Wallets API
62
+ - Create SCA wallet: accountType="SCA", blockchains=["<arc-chain-id>"]
63
+ - Keys custodied by Circle; no key share exposed
64
+ - Sign/submit via Wallet APIs, not raw tx signing
65
+ """
66
  wallet_type: str = "embedded" # 'embedded', 'eoa', 'smart_contract'
67
+ chain_id: int = 42161 # Arbitrum (placeholder; use Arc chain ID)
68
+ gas_abstraction: bool = True # ERC-4337 Paymaster
69
  daily_limit_usd: float = 1000.0
70
  max_single_trade_usd: float = 500.0
71
 
72
 
73
  @dataclass
74
  class USYCConfig:
75
+ """
76
+ USYC yield configuration for idle capital.
77
+
78
+ REAL ARCHITECTURE:
79
+ - Arc Testnet USYC: 0xe9185F0c5F296Ed1797AaE4238D26CCaBEadb86C
80
+ - Teller (deposit/withdraw): 0x9fdF14c5B14173D74C08Af27AebFf39240dC105A
81
+ - Oracle (price): 0x52b56c7642E71dc54714d879127d97cd0B3D4581
82
+ - USYC is ERC-20, NOT ERC-4626. Deposit via Teller contract.
83
+ - Price/APY: GET https://usyc.dev.hashnote.com/api/price (testnet)
84
+ - T+0 settlement, no lockup documented.
85
+ """
86
  enabled: bool = True
87
  apy: float = 0.043 # 4.3% current
88
  auto_rotate_threshold: float = 0.70 # rotate 70% to USYC when risk-off
89
  risk_off_signals: List[str] = None
90
 
91
+ # Real contract addresses (Arc Testnet)
92
+ usyc_address: str = "0xe9185F0c5F296Ed1797AaE4238D26CCaBEadb86C"
93
+ teller_address: str = "0x9fdF14c5B14173D74C08Af27AebFf39240dC105A"
94
+ oracle_address: str = "0x52b56c7642E71dc54714d879127d97cd0B3D4581"
95
+ entitlements_address: str = "0xcc205224862c7641930c87679e98999d23c26113"
96
+
97
  def __post_init__(self):
98
  if self.risk_off_signals is None:
99
  self.risk_off_signals = [
 
113
  fee_usd: float
114
  cctp_burn_tx: Optional[str] = None
115
  cctp_mint_tx: Optional[str] = None
116
+ transfer_id: Optional[str] = None # From real Gateway API
117
+ attestation: Optional[str] = None # From real Gateway API
118
 
119
 
120
  class ArcBridge:
121
  """
122
  Bridge to Arc/Circle infrastructure.
123
 
124
+ DUAL MODE:
125
+ - SIMULATED (default): Mock transactions for hackathon demo
126
+ - REAL: Connect to CircleGatewayClient for live API calls
 
 
127
 
128
+ To use real APIs:
129
+ from builderbrain import CircleGatewayClient
130
+ gateway = CircleGatewayClient(api_key="sk_test_...")
131
+ bridge = ArcBridge(gateway_client=gateway)
132
  """
133
 
134
+ # Supported chains (should be enumerated from Gateway info, not hardcoded)
135
  SUPPORTED_CHAINS = [
136
  "arbitrum", "base", "optimism", "ethereum", "polygon"
137
  ]
 
141
  nanopayment: Optional[NanopaymentConfig] = None,
142
  wallet: Optional[WalletConfig] = None,
143
  usyc: Optional[USYCConfig] = None,
144
+ gateway_client=None, # CircleGatewayClient instance
145
  ):
146
  self.nanopayment = nanopayment or NanopaymentConfig()
147
  self.wallet = wallet or WalletConfig()
148
  self.usyc = usyc or USYCConfig()
149
+ self.gateway_client = gateway_client # Real API client or None
150
 
151
+ # Simulated state (for demo mode)
152
+ self.simulated = gateway_client is None
153
  self.balances: Dict[str, float] = {"usdc": 0.0, "usyc": 0.0}
154
  self.pending_payments: List[Dict] = []
155
  self.gateway_routes: List[GatewayRoute] = []
 
168
  """
169
  Route USDC between chains via Gateway + CCTP.
170
 
171
+ SIMULATED MODE:
172
+ - Creates fake transfer with estimated fees
173
+
174
+ REAL MODE (requires gateway_client):
175
+ - Calls CircleGatewayClient.create_transfer_attestation()
176
+ - Requires encoded burn intents + depositor signatures
177
+ - Returns real transfer_id + attestation for on-chain minting
178
  """
179
+ if self.gateway_client:
180
+ # REAL: Would call gateway_client.create_transfer_attestation()
181
+ # Requires: source domain, dest domain, token addresses,
182
+ # depositor address, recipient, amount, signature
183
+ raise NotImplementedError(
184
+ "Real Gateway transfer requires burn intent encoding + depositor signature. "
185
+ "Use CircleGatewayClient.create_transfer_attestation() with proper intents."
186
+ )
187
+
188
+ # SIMULATED
189
  if from_chain not in self.SUPPORTED_CHAINS:
190
  raise ValueError(f"Unsupported chain: {from_chain}")
191
  if to_chain not in self.SUPPORTED_CHAINS:
192
  raise ValueError(f"Unsupported chain: {to_chain}")
193
 
 
194
  route = GatewayRoute(
195
  from_chain=from_chain,
196
  to_chain=to_chain,
197
  amount_usd=amount_usd,
198
  estimated_time_sec=1800 if from_chain == "ethereum" else 600,
199
+ fee_usd=amount_usd * 0.0005, # 5 bps
200
+ transfer_id=f"sim_{hashlib.sha256(f'{from_chain}_{to_chain}_{amount_usd}'.encode()).hexdigest()[:16]}",
201
  )
202
 
 
203
  route.cctp_burn_tx = f"0x{hashlib.sha256(f'burn_{amount_usd}'.encode()).hexdigest()[:40]}"
204
 
205
  self.gateway_routes.append(route)
 
 
206
  self.balances["usdc"] -= amount_usd + route.fee_usd
207
 
208
  return route
209
 
210
  def get_balance(self, chain: str = "arbitrum") -> Dict:
211
+ """
212
+ Get USDC balance.
213
+
214
+ REAL MODE: Call CircleGatewayClient.get_balances(depositor, domain)
215
+ SIMULATED: Return internal balance dict
216
+ """
217
+ if self.gateway_client:
218
+ # Would need depositor address + domain from Gateway info
219
+ pass
220
+
221
  return {
222
  "chain": chain,
223
  "usdc": self.balances.get("usdc", 0),
224
  "usyc": self.balances.get("usyc", 0),
225
  "total_usd": self.balances.get("usdc", 0) + self.balances.get("usyc", 0),
226
+ "mode": "simulated" if self.simulated else "live",
227
  }
228
 
229
  # ────────────────────────────── Nanopayments ──────────────────────────────
 
234
  trace_id: str,
235
  amount_usd: Optional[float] = None,
236
  ) -> Dict:
237
+ """
238
+ Charge per-insight fee.
239
+
240
+ REAL ARCHITECTURE:
241
+ - Buyer signs EIP-3009 authorization via x402 client
242
+ - Seller (you) verifies signature off-chain
243
+ - Log in your own "virtual ledger" DB
244
+ - Batch settle via Circle Gateway transfer on Arc
245
+
246
+ SIMULATED: Just logs the payment for batching.
247
+ """
248
  if not self.nanopayment.enabled:
249
  return {"status": "skipped", "reason": "nanopayments_disabled"}
250
 
 
258
  "amount_usd": amount,
259
  "timestamp": datetime.utcnow().isoformat(),
260
  "status": "pending_batch",
261
+ "settlement": "EIP-3009_auth + Gateway_batch" if not self.simulated else "simulated",
262
  }
263
 
264
  self.pending_payments.append(payment)
 
270
  trade_id: str,
271
  notional_usd: float,
272
  ) -> Dict:
273
+ """Charge per-trade fee (nanopayment pattern)."""
274
  if not self.nanopayment.enabled:
275
  return {"status": "skipped", "reason": "nanopayments_disabled"}
276
 
 
285
  "notional_usd": notional_usd,
286
  "timestamp": datetime.utcnow().isoformat(),
287
  "status": "pending_batch",
288
+ "settlement": "EIP-3009_auth + Gateway_batch" if not self.simulated else "simulated",
289
  }
290
 
291
  self.pending_payments.append(payment)
292
  return payment
293
 
294
  def batch_settle(self) -> Dict:
295
+ """
296
+ Batch settle pending nanopayments.
297
+
298
+ REAL: Call CircleGatewayClient to execute batch transfer on Arc.
299
+ SIMULATED: Aggregate and log.
300
+ """
301
  if not self.pending_payments:
302
  return {"settled": 0, "total_usd": 0}
303
 
304
  total = sum(p["amount_usd"] for p in self.pending_payments)
305
  count = len(self.pending_payments)
306
 
 
307
  batch_tx = f"0x{hashlib.sha256(f'batch_{count}'.encode()).hexdigest()[:40]}"
308
 
309
  for p in self.pending_payments:
 
317
  "total_usd": total,
318
  "batch_tx": batch_tx,
319
  "avg_fee_usd": total / count if count > 0 else 0,
320
+ "mode": "simulated" if self.simulated else "live",
321
  }
322
 
323
  # ────────────────────────────── Paymaster ──────────────────────────────
 
328
  tx_data: Dict,
329
  ) -> Dict:
330
  """
331
+ Sponsor gas for a user transaction.
332
+
333
+ REAL ARCHITECTURE (ERC-4337 on Arc):
334
+ - Arc supports standard ERC-4337 EntryPoint
335
+ - Use Pimlico or similar bundler on Arc
336
+ - Circle Paymaster: users sign EIP-2612 permit
337
+ - paymasterData = encodePacked([uint8, address, uint256, bytes],
338
+ [0, usdcAddress, permitAmount, permitSignature])
339
+ - Submit UserOperation via bundler RPC (not REST API)
340
+ - Circle Paymaster fees: 10% of gas in USDC (waived until June 30, 2025)
341
 
342
+ SIMULATED: Logs sponsorship without on-chain interaction.
343
  """
344
  if not self.wallet.gas_abstraction:
345
  return {"status": "rejected", "reason": "gas_abstraction_disabled"}
346
 
 
347
  gas_estimate_usd = tx_data.get("gas_estimate", 0.50)
348
 
349
  op = {
 
352
  "gas_sponsored_usd": gas_estimate_usd,
353
  "timestamp": datetime.utcnow().isoformat(),
354
  "status": "sponsored",
355
+ "mode": "simulated" if self.simulated else "live",
356
  "paymaster_tx": f"0x{hashlib.sha256(json.dumps(tx_data).encode()).hexdigest()[:40]}",
357
+ "note": "Real: ERC-4337 UserOp + Pimlico bundler + Circle Paymaster EIP-2612 permit" if not self.simulated else "Simulated",
358
  }
359
 
360
  self.paymaster_ops.append(op)
 
363
  # ────────────────────────────── USYC ──────────────────────────────
364
 
365
  def rotate_to_usyc(self, amount_usd: float) -> Dict:
366
+ """
367
+ Rotate idle USDC to USYC for yield.
368
+
369
+ REAL: Call Teller contract (0x9fdF14c5B14173D74C08Af27AebFf39240dC105A)
370
+ on Arc Testnet to subscribe USDC β†’ USYC.
371
+ SIMULATED: Balance update only.
372
+ """
373
  if not self.usyc.enabled:
374
  return {"status": "skipped", "reason": "usyc_disabled"}
375
 
 
384
  "amount_usd": amount_usd,
385
  "estimated_apy": self.usyc.apy,
386
  "projected_annual_yield": amount_usd * self.usyc.apy,
387
+ "mode": "simulated" if self.simulated else "live",
388
+ "teller_contract": self.usyc.teller_address if not self.simulated else None,
389
  }
390
 
391
  def rotate_from_usyc(self, amount_usd: float) -> Dict:
392
+ """Rotate USYC back to USDC."""
393
  if amount_usd > self.balances.get("usyc", 0):
394
  amount_usd = self.balances.get("usyc", 0)
395
 
 
399
  return {
400
  "status": "rotated",
401
  "amount_usd": amount_usd,
402
+ "mode": "simulated" if self.simulated else "live",
403
  }
404
 
405
  def auto_risk_off_rotation(self, risk_signal: str) -> Dict:
 
425
  """
426
  Record on-chain strategy performance metrics.
427
 
428
+ REAL: Call Arc contract to update strategy leaderboard.
429
+ SIMULATED: Log metrics for later anchoring.
430
  """
431
  metrics = {
432
  "strategy_id": strategy_id,
 
436
  "pnl_usd": pnl_usd,
437
  "timestamp": datetime.utcnow().isoformat(),
438
  "contract_tx": f"0x{hashlib.sha256(f'{strategy_id}_{pnl_usd}'.encode()).hexdigest()[:40]}",
439
+ "mode": "simulated" if self.simulated else "live",
440
  }
441
 
442
  self.contract_calls.append(metrics)
 
454
  "total_contract_calls": len(self.contract_calls),
455
  "nanopayment_config": asdict(self.nanopayment),
456
  "wallet_config": asdict(self.wallet),
457
+ "usyc_config": {
458
+ k: v for k, v in asdict(self.usyc).items()
459
+ if k not in ['risk_off_signals']
460
+ },
461
+ "mode": "simulated" if self.simulated else "live",
462
  }