alberta-buck 0.2.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.
Files changed (249) hide show
  1. alberta_buck/__init__.py +8 -0
  2. alberta_buck/oracle.py +286 -0
  3. alberta_buck/registry/__init__.py +96 -0
  4. alberta_buck/registry/certificate.py +575 -0
  5. alberta_buck/registry/client.py +227 -0
  6. alberta_buck/registry/feature_authority.py +271 -0
  7. alberta_buck/registry/kernel_vectors.py +511 -0
  8. alberta_buck/registry/merkle_service.py +643 -0
  9. alberta_buck/registry/particulars.py +125 -0
  10. alberta_buck/registry/registry.py +561 -0
  11. alberta_buck/registry/regulator.py +321 -0
  12. alberta_buck/registry/tree.py +647 -0
  13. alberta_buck/registry/vectors.py +511 -0
  14. alberta_buck/review/__init__.py +2 -0
  15. alberta_buck/review/examples.py +145 -0
  16. alberta_buck/review/integration.py +92 -0
  17. alberta_buck/review/known_log.py +31 -0
  18. alberta_buck/review/mitigations.py +132 -0
  19. alberta_buck/sim/__init__.py +9 -0
  20. alberta_buck/sim/__main__.py +112 -0
  21. alberta_buck/sim/agents.py +305 -0
  22. alberta_buck/sim/anvil.py +117 -0
  23. alberta_buck/sim/basket_flow.py +622 -0
  24. alberta_buck/sim/basket_model.py +421 -0
  25. alberta_buck/sim/cast.py +59 -0
  26. alberta_buck/sim/catalogue.py +327 -0
  27. alberta_buck/sim/chain.py +27 -0
  28. alberta_buck/sim/compare_ops.py +291 -0
  29. alberta_buck/sim/deploy.py +626 -0
  30. alberta_buck/sim/direct_mint.py +422 -0
  31. alberta_buck/sim/director_agent.py +285 -0
  32. alberta_buck/sim/driver.py +216 -0
  33. alberta_buck/sim/eqmetrics.py +601 -0
  34. alberta_buck/sim/equilibrium_agents.py +4356 -0
  35. alberta_buck/sim/experiment.py +524 -0
  36. alberta_buck/sim/experiments/backdrop-ab.toml +57 -0
  37. alberta_buck/sim/experiments/backdrop.toml +43 -0
  38. alberta_buck/sim/experiments/baseline-5yr.toml +13 -0
  39. alberta_buck/sim/experiments/baseline-capacity.toml +24 -0
  40. alberta_buck/sim/experiments/baseline-savers2x.toml +19 -0
  41. alberta_buck/sim/experiments/catalogue-dump.toml +66 -0
  42. alberta_buck/sim/experiments/catalogue-grind-down.toml +66 -0
  43. alberta_buck/sim/experiments/catalogue-grind-up.toml +65 -0
  44. alberta_buck/sim/experiments/catalogue-none.toml +58 -0
  45. alberta_buck/sim/experiments/catalogue-spike.toml +64 -0
  46. alberta_buck/sim/experiments/catalogue-spike16.toml +64 -0
  47. alberta_buck/sim/experiments/catalogue-squeeze.toml +65 -0
  48. alberta_buck/sim/experiments/catalogue-step.toml +63 -0
  49. alberta_buck/sim/experiments/catalogue-step16.toml +63 -0
  50. alberta_buck/sim/experiments/growth-decline.toml +32 -0
  51. alberta_buck/sim/experiments/growth-scurve.toml +39 -0
  52. alberta_buck/sim/experiments/growth-steady.toml +37 -0
  53. alberta_buck/sim/experiments/isolation.toml +42 -0
  54. alberta_buck/sim/experiments/population-churn.toml +33 -0
  55. alberta_buck/sim/experiments/realistic.toml +42 -0
  56. alberta_buck/sim/experiments/rebalancing-eq-5yr-capacity.toml +36 -0
  57. alberta_buck/sim/experiments/rebalancing-eq-5yr-debtors-mature-basketeers-endog-excursion-raid.toml +89 -0
  58. alberta_buck/sim/experiments/rebalancing-eq-5yr-debtors-mature-basketeers-endog-excursion.toml +82 -0
  59. alberta_buck/sim/experiments/rebalancing-eq-5yr-debtors-mature-basketeers-endog-raid.toml +82 -0
  60. alberta_buck/sim/experiments/rebalancing-eq-5yr-debtors-mature-basketeers-endog.toml +73 -0
  61. alberta_buck/sim/experiments/rebalancing-eq-5yr-debtors-mature-basketeers-originate.toml +74 -0
  62. alberta_buck/sim/experiments/rebalancing-eq-5yr-debtors-mature-basketeers.toml +57 -0
  63. alberta_buck/sim/experiments/rebalancing-eq-5yr-debtors-mature.toml +51 -0
  64. alberta_buck/sim/experiments/rebalancing-eq-5yr-debtors.toml +46 -0
  65. alberta_buck/sim/experiments/rebalancing-eq-5yr-deep.toml +22 -0
  66. alberta_buck/sim/experiments/rebalancing-eq-5yr-endow-deep.toml +34 -0
  67. alberta_buck/sim/experiments/rebalancing-eq-5yr-endow.toml +34 -0
  68. alberta_buck/sim/experiments/rebalancing-eq-5yr-portcast.toml +70 -0
  69. alberta_buck/sim/experiments/rebalancing-eq-5yr.toml +61 -0
  70. alberta_buck/sim/experiments/retune-mid.toml +17 -0
  71. alberta_buck/sim/experiments/shock-demand.toml +27 -0
  72. alberta_buck/sim/experiments/shock-price.toml +25 -0
  73. alberta_buck/sim/experiments/shock-uptake.toml +25 -0
  74. alberta_buck/sim/experiments/template.toml +155 -0
  75. alberta_buck/sim/gen_historical.py +163 -0
  76. alberta_buck/sim/gen_prices.py +148 -0
  77. alberta_buck/sim/identity.py +382 -0
  78. alberta_buck/sim/inspect.py +549 -0
  79. alberta_buck/sim/loop.py +366 -0
  80. alberta_buck/sim/mini.py +143 -0
  81. alberta_buck/sim/monetary_ops.py +468 -0
  82. alberta_buck/sim/notes_stack.py +674 -0
  83. alberta_buck/sim/observer.py +191 -0
  84. alberta_buck/sim/plot_basket_flow.py +123 -0
  85. alberta_buck/sim/plot_basket_model.py +112 -0
  86. alberta_buck/sim/plot_basket_split.py +257 -0
  87. alberta_buck/sim/plot_catalogue.py +129 -0
  88. alberta_buck/sim/plot_equilibrium.py +307 -0
  89. alberta_buck/sim/plot_octl.py +141 -0
  90. alberta_buck/sim/plot_rebalance_article.py +244 -0
  91. alberta_buck/sim/plot_rebalance_policy.py +193 -0
  92. alberta_buck/sim/plot_rebalancing.py +555 -0
  93. alberta_buck/sim/plot_routing.py +231 -0
  94. alberta_buck/sim/prices/aoil-rev.csv +731 -0
  95. alberta_buck/sim/prices/aoil.csv +731 -0
  96. alberta_buck/sim/prices/cbbtc-rev.csv +731 -0
  97. alberta_buck/sim/prices/cbbtc.csv +731 -0
  98. alberta_buck/sim/prices/hist-cbbtc.csv +1828 -0
  99. alberta_buck/sim/prices/hist-cnst.csv +1828 -0
  100. alberta_buck/sim/prices/hist-food.csv +1828 -0
  101. alberta_buck/sim/prices/hist-labr.csv +1828 -0
  102. alberta_buck/sim/prices/hist-nrgc.csv +1828 -0
  103. alberta_buck/sim/prices/hist-paxg.csv +1828 -0
  104. alberta_buck/sim/prices/paxg-rev.csv +731 -0
  105. alberta_buck/sim/prices/paxg.csv +731 -0
  106. alberta_buck/sim/prices.py +40 -0
  107. alberta_buck/sim/privacy_world.py +480 -0
  108. alberta_buck/sim/proxy.py +231 -0
  109. alberta_buck/sim/pyrevm_backend.py +372 -0
  110. alberta_buck/sim/quotes/__init__.py +28 -0
  111. alberta_buck/sim/quotes/data/AU-USD.csv +2322 -0
  112. alberta_buck/sim/quotes/data/BCPI_MONTHLY-sd-1972-01-01.csv +666 -0
  113. alberta_buck/sim/quotes/data/BTC-USD.csv +5765 -0
  114. alberta_buck/sim/quotes/data/CAD_Wages_1997.csv +535 -0
  115. alberta_buck/sim/quotes/data/CPI-USD.csv +1360 -0
  116. alberta_buck/sim/quotes/data/M2-USD.csv +810 -0
  117. alberta_buck/sim/quotes/data/STATIC_INFLATIONCALC.csv +1352 -0
  118. alberta_buck/sim/quotes/data/USD_CAD_1972.csv +647 -0
  119. alberta_buck/sim/quotes/data/us/cpi.csv +953 -0
  120. alberta_buck/sim/quotes/data/us/energy_electricity.csv +570 -0
  121. alberta_buck/sim/quotes/data/us/energy_gasoline.csv +606 -0
  122. alberta_buck/sim/quotes/data/us/energy_natgas.csv +570 -0
  123. alberta_buck/sim/quotes/data/us/food_bananas.csv +555 -0
  124. alberta_buck/sim/quotes/data/us/food_beef.csv +508 -0
  125. alberta_buck/sim/quotes/data/us/food_bread.csv +557 -0
  126. alberta_buck/sim/quotes/data/us/m2.csv +810 -0
  127. alberta_buck/sim/quotes/data/us/ppi_cement.csv +666 -0
  128. alberta_buck/sim/quotes/data/us/ppi_gravel.csv +954 -0
  129. alberta_buck/sim/quotes/data/us/ppi_lumber.csv +1206 -0
  130. alberta_buck/sim/quotes/data/us/ppi_steel.csv +1206 -0
  131. alberta_buck/sim/quotes/data/us/wage_ahetpi.csv +750 -0
  132. alberta_buck/sim/quotes/fetch_feedstock.py +198 -0
  133. alberta_buck/sim/quotes/ingest.py +237 -0
  134. alberta_buck/sim/quotes/labour.py +101 -0
  135. alberta_buck/sim/quotes/metrics.py +47 -0
  136. alberta_buck/sim/quotes/plot_quotes.py +95 -0
  137. alberta_buck/sim/quotes/source.py +180 -0
  138. alberta_buck/sim/quotes/units.py +108 -0
  139. alberta_buck/sim/quotes/walks.py +119 -0
  140. alberta_buck/sim/rebalance_policy.py +1283 -0
  141. alberta_buck/sim/rebalancer.py +148 -0
  142. alberta_buck/sim/rng.py +117 -0
  143. alberta_buck/sim/router.py +94 -0
  144. alberta_buck/sim/scenario.py +314 -0
  145. alberta_buck/sim/server.py +328 -0
  146. alberta_buck/sim/snapshot.py +596 -0
  147. alberta_buck/sim/sweep.py +131 -0
  148. alberta_buck/sim/telemetry.py +56 -0
  149. alberta_buck/sim/tui.py +338 -0
  150. alberta_buck/test/__init__.py +0 -0
  151. alberta_buck/test/gen_truthstake_plots.py +412 -0
  152. alberta_buck/test/review/conftest.py +47 -0
  153. alberta_buck/test/review/test_wallet_failures.py +290 -0
  154. alberta_buck/test/test_accumulator.py +368 -0
  155. alberta_buck/test/test_b1_binding.py +193 -0
  156. alberta_buck/test/test_cards.py +37 -0
  157. alberta_buck/test/test_contract_binding.py +38 -0
  158. alberta_buck/test/test_debtor_ledger.py +275 -0
  159. alberta_buck/test/test_delivery.py +139 -0
  160. alberta_buck/test/test_domains.py +114 -0
  161. alberta_buck/test/test_e2e_payload.py +211 -0
  162. alberta_buck/test/test_escrow_oracle.py +1514 -0
  163. alberta_buck/test/test_identity_cache_regen.py +166 -0
  164. alberta_buck/test/test_issuer.py +233 -0
  165. alberta_buck/test/test_issuer_reenc.py +210 -0
  166. alberta_buck/test/test_js_join_web3.py +91 -0
  167. alberta_buck/test/test_kernel_backend.py +110 -0
  168. alberta_buck/test/test_mini_parity.py +122 -0
  169. alberta_buck/test/test_notes.py +182 -0
  170. alberta_buck/test/test_oracle_history.py +159 -0
  171. alberta_buck/test/test_particulars.py +65 -0
  172. alberta_buck/test/test_poseidon.py +82 -0
  173. alberta_buck/test/test_privacy_world.py +177 -0
  174. alberta_buck/test/test_quotes.py +205 -0
  175. alberta_buck/test/test_rebalancing_sim_invariants.py +313 -0
  176. alberta_buck/test/test_receipt.py +288 -0
  177. alberta_buck/test/test_receipt_core.py +504 -0
  178. alberta_buck/test/test_receipt_e2e.py +172 -0
  179. alberta_buck/test/test_receiving_key.py +493 -0
  180. alberta_buck/test/test_registry.py +1095 -0
  181. alberta_buck/test/test_render.py +238 -0
  182. alberta_buck/test/test_routing_sim_web3.py +51 -0
  183. alberta_buck/test/test_schnorr.py +72 -0
  184. alberta_buck/test/test_timeseries_oracle.py +930 -0
  185. alberta_buck/test/test_truthstake.py +1718 -0
  186. alberta_buck/test/test_unilateral_a1.py +291 -0
  187. alberta_buck/test/test_unilateral_a2.py +445 -0
  188. alberta_buck/test/test_vectors.py +175 -0
  189. alberta_buck/test/test_verifiable_decrypt.py +79 -0
  190. alberta_buck/test/test_wallet.py +305 -0
  191. alberta_buck/test/test_wallet_kernel_backend.py +147 -0
  192. alberta_buck/test/vectors/e2e/a1.json +401 -0
  193. alberta_buck/test/vectors/e2e/a2.json +464 -0
  194. alberta_buck/test/vectors/e2e/b1.json +295 -0
  195. alberta_buck/test/vectors/privacy/world.json +1109 -0
  196. alberta_buck/test/vectors/receipt-eoa_priv.golden.txt +32 -0
  197. alberta_buck/test/vectors/receipt-eoa_pub.golden.txt +32 -0
  198. alberta_buck/test/vectors/receipt-eoa_pub_unicode.golden.txt +32 -0
  199. alberta_buck/test/vectors/receipt-note_a1.golden.txt +33 -0
  200. alberta_buck/test/vectors/receipt-note_a1_issuer.golden.txt +33 -0
  201. alberta_buck/test/vectors/receipt-note_a2.golden.txt +33 -0
  202. alberta_buck/test/vectors/receipt-note_a2_issuer.golden.txt +33 -0
  203. alberta_buck/test/vectors/receipt-note_b1.golden.txt +33 -0
  204. alberta_buck/test/vectors/receipt-note_b1_issuer.golden.txt +33 -0
  205. alberta_buck/truthstake.py +1685 -0
  206. alberta_buck/wallet/__init__.py +129 -0
  207. alberta_buck/wallet/_kernel.py +112 -0
  208. alberta_buck/wallet/attributes.py +107 -0
  209. alberta_buck/wallet/b1_binding.py +275 -0
  210. alberta_buck/wallet/bn254.py +144 -0
  211. alberta_buck/wallet/build_receipt.py +679 -0
  212. alberta_buck/wallet/cards.py +284 -0
  213. alberta_buck/wallet/chaum_pedersen.py +183 -0
  214. alberta_buck/wallet/cli.py +173 -0
  215. alberta_buck/wallet/contract_binding.py +86 -0
  216. alberta_buck/wallet/delivery.py +236 -0
  217. alberta_buck/wallet/deposit_fold.py +497 -0
  218. alberta_buck/wallet/domains.py +147 -0
  219. alberta_buck/wallet/elgamal.py +43 -0
  220. alberta_buck/wallet/envelope.py +631 -0
  221. alberta_buck/wallet/identity.py +51 -0
  222. alberta_buck/wallet/issuer.py +218 -0
  223. alberta_buck/wallet/issuer_reenc.py +233 -0
  224. alberta_buck/wallet/kernel_vectors.py +450 -0
  225. alberta_buck/wallet/nizk.py +292 -0
  226. alberta_buck/wallet/notes.py +195 -0
  227. alberta_buck/wallet/nums.py +94 -0
  228. alberta_buck/wallet/poseidon.py +143 -0
  229. alberta_buck/wallet/poseidon_constants.json +206 -0
  230. alberta_buck/wallet/ps.py +167 -0
  231. alberta_buck/wallet/receipt.py +257 -0
  232. alberta_buck/wallet/recvkey.py +316 -0
  233. alberta_buck/wallet/render.py +425 -0
  234. alberta_buck/wallet/salt.py +90 -0
  235. alberta_buck/wallet/schnorr.py +103 -0
  236. alberta_buck/wallet/transcript.py +48 -0
  237. alberta_buck/wallet/unilateral_a1.py +218 -0
  238. alberta_buck/wallet/unilateral_a2.py +370 -0
  239. alberta_buck/wallet/vectors.py +675 -0
  240. alberta_buck/wallet/verifiable_decrypt.py +175 -0
  241. alberta_buck/wallet/verify_receipt.py +455 -0
  242. alberta_buck/wallet/wallet_kernel_vectors.py +804 -0
  243. alberta_buck-0.2.0.dist-info/METADATA +129 -0
  244. alberta_buck-0.2.0.dist-info/RECORD +249 -0
  245. alberta_buck-0.2.0.dist-info/WHEEL +5 -0
  246. alberta_buck-0.2.0.dist-info/licenses/LICENSE +674 -0
  247. alberta_buck-0.2.0.dist-info/licenses/NOTICE +74 -0
  248. alberta_buck-0.2.0.dist-info/top_level.txt +1 -0
  249. alberta_buck-0.2.0.dist-info/zip-safe +1 -0
@@ -0,0 +1,8 @@
1
+ # Alberta Buck -- commodity-basket stablecoin tools
2
+ #
3
+ # buck_core was reached from here by a sys.path shim appending
4
+ # ../core/python, because there was no wheel to depend on. There is now:
5
+ # alberta-buck-core is a hard dependency of this package. A checkout still
6
+ # gets its own copy first, because `make venv` installs core/python
7
+ # editable -- which is the same mechanism, declared rather than improvised.
8
+ # (alberta-buck-platform.org Phase 2; alberta-buck-deployment.org P5.)
alberta_buck/oracle.py ADDED
@@ -0,0 +1,286 @@
1
+ """Chainlink oracle historical price reading utilities.
2
+
3
+ Two query strategies:
4
+
5
+ get_round_history() -- walks backwards through getRoundData(roundId) at the current
6
+ (fork) block. All calls hit one block height, so Anvil's storage cache is maximally
7
+ effective. Preferred when running against a local Anvil fork.
8
+
9
+ get_daily_prices() -- calls latestRoundData() at estimated block numbers for each day.
10
+ Each call targets a different block height. Works against any RPC but is not
11
+ cache-friendly across blocks.
12
+ """
13
+
14
+ import logging
15
+ import os
16
+ from datetime import datetime, timezone
17
+
18
+ from web3 import Web3
19
+
20
+ log = logging.getLogger(__name__)
21
+
22
+ # Chainlink AggregatorV3Interface -- minimal ABI for price reading
23
+ AGGREGATOR_V3_ABI = [
24
+ {
25
+ "inputs": [],
26
+ "name": "latestRoundData",
27
+ "outputs": [
28
+ {"name": "roundId", "type": "uint80"},
29
+ {"name": "answer", "type": "int256"},
30
+ {"name": "startedAt", "type": "uint256"},
31
+ {"name": "updatedAt", "type": "uint256"},
32
+ {"name": "answeredInRound", "type": "uint80"},
33
+ ],
34
+ "stateMutability": "view",
35
+ "type": "function",
36
+ },
37
+ {
38
+ "inputs": [{"name": "_roundId", "type": "uint80"}],
39
+ "name": "getRoundData",
40
+ "outputs": [
41
+ {"name": "roundId", "type": "uint80"},
42
+ {"name": "answer", "type": "int256"},
43
+ {"name": "startedAt", "type": "uint256"},
44
+ {"name": "updatedAt", "type": "uint256"},
45
+ {"name": "answeredInRound", "type": "uint80"},
46
+ ],
47
+ "stateMutability": "view",
48
+ "type": "function",
49
+ },
50
+ {
51
+ "inputs": [],
52
+ "name": "decimals",
53
+ "outputs": [{"name": "", "type": "uint8"}],
54
+ "stateMutability": "view",
55
+ "type": "function",
56
+ },
57
+ {
58
+ "inputs": [],
59
+ "name": "description",
60
+ "outputs": [{"name": "", "type": "string"}],
61
+ "stateMutability": "view",
62
+ "type": "function",
63
+ },
64
+ ]
65
+
66
+ # Chainlink mainnet feed addresses (from BuckKController.t.sol)
67
+ FEEDS = {
68
+ "XAU/USD": "0x214eD9Da11D2fbe465a6fc601a91E62EbEc1a0D6",
69
+ "XAG/USD": "0x379589227b15F1a12195D3f2d90bBc9F31f95235",
70
+ }
71
+
72
+ # Average Ethereum block time post-merge (seconds)
73
+ AVG_BLOCK_TIME = 12.05
74
+ SECONDS_PER_DAY = 86400
75
+
76
+
77
+ def get_web3(rpc_url=None):
78
+ """Connect to Ethereum mainnet via the provided or environment RPC URL."""
79
+ if rpc_url is None:
80
+ rpc_url = os.environ.get("ETH_RPC_URL") or os.environ.get("MAINNET_RPC_URL")
81
+ if not rpc_url:
82
+ raise RuntimeError(
83
+ "No RPC URL: set MAINNET_RPC_URL or ETH_RPC_URL environment variable"
84
+ )
85
+ log.info(
86
+ "Connecting to %s (ETH_RPC_URL=%s, MAINNET_RPC_URL=%s)",
87
+ rpc_url,
88
+ os.environ.get("ETH_RPC_URL", "(unset)"),
89
+ os.environ.get("MAINNET_RPC_URL", "(unset)"),
90
+ )
91
+ w3 = Web3(Web3.HTTPProvider(rpc_url))
92
+ if not w3.is_connected():
93
+ raise RuntimeError(f"Cannot connect to {rpc_url}")
94
+
95
+ # Patch the provider to count actual RPC calls and cache eth_chainId.
96
+ # web3.py's fill_transaction_defaults calls eth_chainId on every
97
+ # contract.functions.foo().call() -- ~2x per eth_call. Caching at
98
+ # the provider level intercepts all code paths (middleware,
99
+ # fill_transaction_defaults, etc.).
100
+ rpc_counts = {} # actual calls that hit the node
101
+ cached_counts = {} # calls served from cache
102
+ _cached = {}
103
+ _original = w3.provider.make_request
104
+
105
+ def _counting_provider(method, params):
106
+ if method == "eth_chainId" and method in _cached:
107
+ cached_counts[method] = cached_counts.get(method, 0) + 1
108
+ return _cached[method]
109
+ rpc_counts[method] = rpc_counts.get(method, 0) + 1
110
+ result = _original(method, params)
111
+ if method == "eth_chainId":
112
+ _cached[method] = result
113
+ return result
114
+
115
+ w3.provider.make_request = _counting_provider
116
+ w3.rpc_counts = rpc_counts
117
+ w3.cached_counts = cached_counts
118
+
119
+ return w3
120
+
121
+
122
+ def rpc_call_summary(w3, label=""):
123
+ """Return a human-readable summary of RPC call counts."""
124
+ if not hasattr(w3, "rpc_counts") or not w3.rpc_counts:
125
+ return ""
126
+ total = sum(w3.rpc_counts.values())
127
+ parts = ", ".join(
128
+ f"{method}: {count}" for method, count in sorted(w3.rpc_counts.items())
129
+ )
130
+ prefix = f"RPC calls [{label}]" if label else "RPC calls"
131
+ summary = f"{prefix}: {total} total ({parts})"
132
+ if hasattr(w3, "cached_counts") and w3.cached_counts:
133
+ cached_total = sum(w3.cached_counts.values())
134
+ summary += f" + {cached_total} cached"
135
+ return summary
136
+
137
+
138
+ def log_call_counts(w3, label=""):
139
+ """Log a summary of actual (non-cached) RPC call counts."""
140
+ summary = rpc_call_summary(w3, label)
141
+ if summary:
142
+ log.info("%s", summary)
143
+
144
+
145
+ def get_daily_prices(w3, feed_address, days=365):
146
+ """Query a Chainlink price feed at daily block intervals.
147
+
148
+ Returns (description, decimals, records) where each record is a dict with
149
+ keys: date, price, block, round_id, updated_at.
150
+ """
151
+ contract = w3.eth.contract(
152
+ address=Web3.to_checksum_address(feed_address),
153
+ abi=AGGREGATOR_V3_ABI,
154
+ )
155
+ decimals = contract.functions.decimals().call()
156
+ description = contract.functions.description().call()
157
+
158
+ latest_block = w3.eth.block_number
159
+ latest = w3.eth.get_block(latest_block)
160
+ now_ts = latest.timestamp
161
+
162
+ log.info(
163
+ "Querying %s (%d decimals) over %d days from block %d",
164
+ description, decimals, days, latest_block,
165
+ )
166
+
167
+ records = []
168
+ for day_offset in range(days, -1, -1):
169
+ blocks_ago = int(day_offset * SECONDS_PER_DAY / AVG_BLOCK_TIME)
170
+ block_num = max(1, latest_block - blocks_ago)
171
+
172
+ try:
173
+ data = contract.functions.latestRoundData().call(
174
+ block_identifier=block_num
175
+ )
176
+ round_id, answer, _started, updated_at, _answered = data
177
+ price = answer / (10**decimals)
178
+ dt = datetime.fromtimestamp(updated_at, tz=timezone.utc)
179
+
180
+ records.append({
181
+ "date": dt,
182
+ "price": price,
183
+ "block": block_num,
184
+ "round_id": round_id,
185
+ "updated_at": updated_at,
186
+ })
187
+ except Exception as exc:
188
+ log.warning(" block %d (day -%d): %s", block_num, day_offset, exc)
189
+
190
+ if day_offset % 30 == 0:
191
+ log.info(" ... %d days remaining", day_offset)
192
+
193
+ return description, decimals, records
194
+
195
+
196
+ # ---------------------------------------------------------------------------
197
+ # Round-based history -- cache-friendly (all calls at one block height)
198
+ # ---------------------------------------------------------------------------
199
+
200
+ def get_round_history(w3, feed_address, days=365):
201
+ """Query a Chainlink feed by walking backwards through getRoundData().
202
+
203
+ All calls execute at the current (fork) block, reading historical round
204
+ storage. This makes every call cacheable by Anvil at a single block height.
205
+
206
+ Walks backwards one round at a time, collecting one record per calendar day.
207
+
208
+ Returns (description, decimals, records) -- same shape as get_daily_prices().
209
+ """
210
+ contract = w3.eth.contract(
211
+ address=Web3.to_checksum_address(feed_address),
212
+ abi=AGGREGATOR_V3_ABI,
213
+ )
214
+ decimals = contract.functions.decimals().call()
215
+ description = contract.functions.description().call()
216
+
217
+ round_id, _, _, latest_ts, _ = contract.functions.latestRoundData().call()
218
+ phase_id = round_id >> 64
219
+ agg_round = round_id & ((1 << 64) - 1)
220
+ cutoff_ts = latest_ts - days * SECONDS_PER_DAY
221
+
222
+ log.info(
223
+ "Querying %s via getRoundData: phase=%d, latest round=%d",
224
+ description, phase_id, agg_round,
225
+ )
226
+
227
+ records = {} # date -> record (one per calendar day)
228
+ cursor = agg_round
229
+ consecutive_errors = 0
230
+
231
+ while cursor >= 1 and phase_id >= 1:
232
+ rid = (phase_id << 64) | cursor
233
+ try:
234
+ _, answer, _, updated_at, _ = contract.functions.getRoundData(rid).call()
235
+ consecutive_errors = 0
236
+ except Exception:
237
+ consecutive_errors += 1
238
+ if consecutive_errors > 5:
239
+ # Likely hit phase boundary -- try previous phase
240
+ phase_id -= 1
241
+ if phase_id >= 1:
242
+ cursor = _find_phase_last_round(contract, phase_id)
243
+ consecutive_errors = 0
244
+ continue
245
+ break
246
+ cursor -= 1
247
+ continue
248
+
249
+ if updated_at <= cutoff_ts:
250
+ break
251
+
252
+ dt = datetime.fromtimestamp(updated_at, tz=timezone.utc)
253
+ date_key = dt.date()
254
+ if date_key not in records:
255
+ records[date_key] = {
256
+ "date": dt,
257
+ "price": answer / (10**decimals),
258
+ "block": None,
259
+ "round_id": rid,
260
+ "updated_at": updated_at,
261
+ }
262
+
263
+ cursor -= 1
264
+
265
+ if len(records) % 30 == 0 and cursor == agg_round - 1 or len(records) % 60 == 0:
266
+ log.info(" ... %d days collected, round %d", len(records), cursor)
267
+
268
+ result = sorted(records.values(), key=lambda r: r["date"])
269
+ log.info("%s: %d daily records via round walk", description, len(result))
270
+ return description, decimals, result
271
+
272
+
273
+ def _find_phase_last_round(contract, phase_id, hi=100_000):
274
+ """Binary search for the highest valid aggregator round in a phase."""
275
+ lo = 1
276
+ last_valid = 0
277
+ while lo <= hi:
278
+ mid = (lo + hi) // 2
279
+ rid = (phase_id << 64) | mid
280
+ try:
281
+ contract.functions.getRoundData(rid).call()
282
+ last_valid = mid
283
+ lo = mid + 1
284
+ except Exception:
285
+ hi = mid - 1
286
+ return last_valid
@@ -0,0 +1,96 @@
1
+ """Alberta Buck Identity Registry -- government identity certification.
2
+
3
+ A federated identity-certification layer for the Identity-axis Notes system.
4
+ Each registry (provincial, state, federal) independently verifies KYC data and
5
+ issues signed, ElGamal-encrypted identity certificates. Feature authorities
6
+ attest that identities possess specific attributes (age>18, has-license, region).
7
+ A central Merkle service aggregates sub-tree roots from all registries and
8
+ feature authorities; the single combined root is posted on chain and consumed
9
+ by Notes spend-path identity-membership proofs.
10
+
11
+ Reference: alberta-buck-notes.org ("Mutual Decryptability", "one gadget" / Identity-M model); see also alberta-buck-notes-flow.org "The Identity-M Spend Path".
12
+ """
13
+
14
+ from alberta_buck.registry.certificate import (
15
+ IdentityCertificate,
16
+ SignedCertificate,
17
+ SealedCertificate,
18
+ RegistryKeyPair,
19
+ RegistrySchnorrProof,
20
+ registry_keygen,
21
+ registry_schnorr_sign,
22
+ registry_schnorr_verify,
23
+ registry_sign_certificate,
24
+ registry_verify_certificate,
25
+ seal_certificate,
26
+ unseal_certificate,
27
+ )
28
+ from alberta_buck.registry.tree import (
29
+ IdentityMerkleTree,
30
+ MembershipProof,
31
+ identity_leaf,
32
+ identity_leaf_salted,
33
+ EMPTY_LEAF,
34
+ )
35
+ from alberta_buck.registry.merkle_service import (
36
+ CentralMerkleService,
37
+ SubTreeKind,
38
+ SubTreeRecord,
39
+ AggregatorMembershipProof,
40
+ FullMembershipProof,
41
+ ComposedMembershipProof,
42
+ )
43
+ from alberta_buck.registry.registry import (
44
+ RegistryAgent,
45
+ RegistrationRecord,
46
+ FullRegistrationRecord,
47
+ RegistryState,
48
+ )
49
+ from alberta_buck.registry.feature_authority import (
50
+ FeatureAuthority,
51
+ FeatureRecord,
52
+ )
53
+ from alberta_buck.registry.client import (
54
+ ClientAgent,
55
+ VerifiedIdentity,
56
+ )
57
+
58
+ __all__ = [
59
+ # certificate
60
+ "IdentityCertificate",
61
+ "SignedCertificate",
62
+ "SealedCertificate",
63
+ "RegistryKeyPair",
64
+ "RegistrySchnorrProof",
65
+ "registry_keygen",
66
+ "registry_schnorr_sign",
67
+ "registry_schnorr_verify",
68
+ "registry_sign_certificate",
69
+ "registry_verify_certificate",
70
+ "seal_certificate",
71
+ "unseal_certificate",
72
+ # tree
73
+ "IdentityMerkleTree",
74
+ "MembershipProof",
75
+ "identity_leaf",
76
+ "identity_leaf_salted",
77
+ "EMPTY_LEAF",
78
+ # merkle_service
79
+ "CentralMerkleService",
80
+ "SubTreeKind",
81
+ "SubTreeRecord",
82
+ "AggregatorMembershipProof",
83
+ "FullMembershipProof",
84
+ "ComposedMembershipProof",
85
+ # registry
86
+ "RegistryAgent",
87
+ "RegistrationRecord",
88
+ "FullRegistrationRecord",
89
+ "RegistryState",
90
+ # feature_authority
91
+ "FeatureAuthority",
92
+ "FeatureRecord",
93
+ # client
94
+ "ClientAgent",
95
+ "VerifiedIdentity",
96
+ ]