Skip to content

Commit 12e1f4b

Browse files
committed
feat: fold ATNToken into RPB as inherited ERC20, deploy to shadownet
RPB now inherits ERC20/ERC20Votes/ERC20Burnable/ERC20Permit — deploying an RPB gives you the jurisdiction token, agent registry, training economics, inference accounting, and share system in one contract. All contracts that referenced ATNToken externally (TaskContract, ParticipantStaking, InferenceProviderBridge, ForcedErrorRegistry, GuildRegistry, EvolutionProposal) updated to use RPB directly. RPBFactory now supports registerRPB() for pre-deployed contracts. RPB deployed to etherlink-shadownet at: 0x57F68991572D639D0A1faD43aE86163334368Bf2 Registry: 0xA2Ec6A1Aa7bd2bfF4f7AFF8d40247F302cFBBb2F 298 tests passing (includes new economic-loop.test.js with full e2e coverage of alignment-weighted training rewards, share purchases, dividends, inference pricing independence, and revenue splits).
1 parent 6010e67 commit 12e1f4b

18 files changed

Lines changed: 1088 additions & 394 deletions

atn/providers/bridge.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,16 @@
3636

3737
log = logging.getLogger(__name__)
3838

39-
# Path to the bridge script relative to this file
40-
_BRIDGE_DIR = Path(__file__).resolve().parent.parent.parent / "bridge"
39+
# Locate the bridge directory: try importlib first (pip install), then relative path (dev)
40+
def _find_bridge_dir() -> Path:
41+
try:
42+
import bridge as _bridge_pkg
43+
return Path(_bridge_pkg.__file__).resolve().parent
44+
except ImportError:
45+
pass
46+
return Path(__file__).resolve().parent.parent.parent / "bridge"
47+
48+
_BRIDGE_DIR = _find_bridge_dir()
4149
_BRIDGE_SCRIPT = _BRIDGE_DIR / "claude-bridge.ts"
4250

4351

@@ -777,14 +785,38 @@ async def _ensure_process(self) -> None:
777785
node_modules = self._bridge_script.parent / "node_modules"
778786
if not node_modules.exists():
779787
raise ProviderError(
780-
f"Bridge dependencies not installed. Run: cd {self._bridge_script.parent} && bun install",
788+
f"Bridge dependencies not installed. "
789+
f"Run: cd {self._bridge_script.parent} && bun install (or npm install)",
781790
provider="claude_max",
782791
)
783792

784793
# Fresh queue for new process
785794
self._event_queue = asyncio.Queue()
786795

787-
log.info("Spawning bridge: bun run %s", self._bridge_script)
796+
# Resolve JS runtime: prefer bun, fall back to node
797+
import shutil
798+
runtime = shutil.which("bun")
799+
runtime_args = ["run", str(self._bridge_script)]
800+
if not runtime:
801+
runtime = shutil.which("node")
802+
if runtime:
803+
# node needs the compiled .ts → use tsx or run .ts directly
804+
# bun handles .ts natively; for node we need tsx or pre-compiled JS
805+
tsx = shutil.which("tsx") or shutil.which("npx")
806+
if tsx and "npx" in str(tsx):
807+
runtime_args = ["tsx", str(self._bridge_script)]
808+
elif tsx:
809+
runtime = tsx
810+
runtime_args = [str(self._bridge_script)]
811+
else:
812+
runtime_args = ["--import", "tsx", str(self._bridge_script)]
813+
if not runtime:
814+
raise ProviderError(
815+
"Neither bun nor node found. Install bun: https://bun.sh/docs/installation",
816+
provider="claude_max",
817+
)
818+
819+
log.info("Spawning bridge: %s %s", runtime, " ".join(runtime_args))
788820

789821
kwargs: dict[str, Any] = {}
790822
if sys.platform == "win32":
@@ -795,7 +827,7 @@ async def _ensure_process(self) -> None:
795827
env.pop("CLAUDECODE", None)
796828

797829
self._process = await asyncio.create_subprocess_exec(
798-
"bun", "run", str(self._bridge_script),
830+
runtime, *runtime_args,
799831
stdin=asyncio.subprocess.PIPE,
800832
stdout=asyncio.subprocess.PIPE,
801833
stderr=asyncio.subprocess.PIPE,

atn/runtime/provider_manager.py

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -856,7 +856,11 @@ async def _validate_api_key(self, provider_id: str, api_key: str) -> None:
856856

857857
async def _ensure_bridge_deps(self) -> None:
858858
"""Auto-install bridge dependencies (bun install) if missing."""
859-
bridge_dir = Path(__file__).resolve().parent.parent.parent / "bridge"
859+
try:
860+
import bridge as _bridge_pkg
861+
bridge_dir = Path(_bridge_pkg.__file__).resolve().parent
862+
except ImportError:
863+
bridge_dir = Path(__file__).resolve().parent.parent.parent / "bridge"
860864
pkg_json = bridge_dir / "package.json"
861865
node_modules = bridge_dir / "node_modules"
862866
cli_js = node_modules / "@anthropic-ai" / "claude-agent-sdk" / "cli.js"
@@ -868,27 +872,31 @@ async def _ensure_bridge_deps(self) -> None:
868872
log.warning("Bridge package.json not found at %s", bridge_dir)
869873
return
870874

871-
# Check bun is available
875+
# Check bun is available; auto-install if missing
872876
import shutil
873877
bun = shutil.which("bun")
874878
if not bun:
875-
# Try installing bun via npm
879+
bun = await self._auto_install_bun()
880+
if not bun:
881+
# Fall back to npm if available
876882
npm = shutil.which("npm")
877883
if npm:
878-
log.info("Installing bun via npm...")
884+
log.info("bun not available, falling back to npm install...")
879885
try:
880886
proc = await asyncio.create_subprocess_exec(
881-
npm, "install", "-g", "bun",
887+
npm, "install",
888+
cwd=str(bridge_dir),
882889
stdout=asyncio.subprocess.PIPE,
883890
stderr=asyncio.subprocess.PIPE,
884891
)
885-
await asyncio.wait_for(proc.communicate(), timeout=120)
886-
bun = shutil.which("bun")
892+
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=120)
893+
if proc.returncode == 0:
894+
log.info("Bridge dependencies installed via npm")
895+
return
887896
except Exception as e:
888-
log.warning("Failed to install bun: %s", e)
889-
890-
if not bun:
891-
log.warning("bun not found — cannot auto-install bridge dependencies")
897+
log.warning("npm install failed: %s", e)
898+
log.warning("Neither bun nor npm found — cannot auto-install bridge dependencies. "
899+
"Install bun: https://bun.sh/docs/installation")
892900
return
893901

894902
# Run bun install in the bridge directory
@@ -911,8 +919,51 @@ async def _ensure_bridge_deps(self) -> None:
911919
except Exception as e:
912920
log.warning("Failed to install bridge dependencies: %s", e)
913921

922+
async def _auto_install_bun(self) -> str | None:
923+
"""Download and install bun without requiring npm/node."""
924+
import shutil
925+
import sys
926+
927+
log.info("bun not found — attempting automatic install...")
928+
try:
929+
if sys.platform == "win32":
930+
# PowerShell one-liner from bun.sh
931+
proc = await asyncio.create_subprocess_exec(
932+
"powershell", "-Command",
933+
"irm bun.sh/install.ps1 | iex",
934+
stdout=asyncio.subprocess.PIPE,
935+
stderr=asyncio.subprocess.PIPE,
936+
)
937+
else:
938+
# Unix: curl | bash
939+
proc = await asyncio.create_subprocess_shell(
940+
"curl -fsSL https://bun.sh/install | bash",
941+
stdout=asyncio.subprocess.PIPE,
942+
stderr=asyncio.subprocess.PIPE,
943+
)
944+
await asyncio.wait_for(proc.communicate(), timeout=120)
945+
if proc.returncode == 0:
946+
# Bun installs to ~/.bun/bin — refresh PATH
947+
bun_bin = Path.home() / ".bun" / "bin"
948+
if bun_bin.exists():
949+
os.environ["PATH"] = str(bun_bin) + os.pathsep + os.environ.get("PATH", "")
950+
bun = shutil.which("bun")
951+
if bun:
952+
log.info("bun installed successfully: %s", bun)
953+
return bun
954+
log.warning("bun install script exited with code %s", proc.returncode)
955+
except asyncio.TimeoutError:
956+
log.warning("bun install timed out")
957+
except Exception as e:
958+
log.warning("Failed to auto-install bun: %s", e)
959+
return None
960+
914961
def _resolve_sdk_cli(self) -> Path | None:
915-
bridge_dir = Path(__file__).resolve().parent.parent.parent / "bridge"
962+
try:
963+
import bridge as _bridge_pkg
964+
bridge_dir = Path(_bridge_pkg.__file__).resolve().parent
965+
except ImportError:
966+
bridge_dir = Path(__file__).resolve().parent.parent.parent / "bridge"
916967
cli_js = bridge_dir / "node_modules" / "@anthropic-ai" / "claude-agent-sdk" / "cli.js"
917968
if cli_js.exists():
918969
return cli_js

bridge/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Bridge package — TypeScript source shipped as package data.

contracts/bridge/InferenceProviderBridge.sol

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ pragma solidity ^0.8.20;
44
import "../interfaces/IInferenceProvider.sol";
55
import "../core/RPB.sol";
66
import "../core/ParticipantStaking.sol";
7-
import "../tokens/ATNToken.sol";
87
import "../utils/AutonetLib.sol";
98
import "@openzeppelin/contracts/access/Ownable.sol";
109
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
@@ -23,7 +22,7 @@ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
2322
* - Spot-check triggered: payment staged, verifier reviews output
2423
* - No spot-check (93%): immediate provider payout
2524
* 6. On completion, bridge mints ATN to provider from burn pool
26-
* (requires this contract is an authorized minter on ATNToken)
25+
* (requires this contract is an authorized minter on RPB)
2726
* 7. PROTOCOL_FEE_BPS is retained as jackpot pool for verification rewards
2827
*
2928
* Key design decisions:
@@ -34,7 +33,6 @@ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
3433
*/
3534
contract InferenceProviderBridge is IInferenceProvider, Ownable, ReentrancyGuard {
3635
RPB public immutable rpb;
37-
ATNToken public immutable atnToken;
3836
ParticipantStaking public immutable staking;
3937

4038
// ============ BME Economics Parameters ============
@@ -115,7 +113,6 @@ contract InferenceProviderBridge is IInferenceProvider, Ownable, ReentrancyGuard
115113
) Ownable(_owner) {
116114
rpb = RPB(_rpb);
117115
staking = ParticipantStaking(_staking);
118-
atnToken = rpb.atnToken();
119116
}
120117

121118
// ============ Admin Functions ============
@@ -172,7 +169,7 @@ contract InferenceProviderBridge is IInferenceProvider, Ownable, ReentrancyGuard
172169

173170
// BME Step 1: Burn ATN from user immediately
174171
// No token holds — tokens are destroyed, provider gets fresh minted tokens
175-
atnToken.burnFrom(msg.sender, fee);
172+
rpb.burnFrom(msg.sender, fee);
176173
totalBurned += fee;
177174

178175
// Split: provider amount vs protocol fee (goes to jackpot pool)
@@ -341,7 +338,7 @@ contract InferenceProviderBridge is IInferenceProvider, Ownable, ReentrancyGuard
341338
// Award jackpot to the verifier (msg.sender)
342339
if (jackpotPool >= verificationJackpotAmount) {
343340
jackpotPool -= verificationJackpotAmount;
344-
atnToken.mint(msg.sender, verificationJackpotAmount);
341+
rpb.mint(msg.sender, verificationJackpotAmount);
345342
emit JackpotAwarded(msg.sender, verificationJackpotAmount);
346343
}
347344

@@ -382,7 +379,7 @@ contract InferenceProviderBridge is IInferenceProvider, Ownable, ReentrancyGuard
382379
* Then calls rpb.recordInferenceBME() to update RPB accounting so
383380
* shareholders can claim dividends.
384381
*
385-
* Requires this contract is an authorized minter on ATNToken and an
382+
* Requires this contract is an authorized minter on RPB and an
386383
* authorized disburser on RPB.
387384
*/
388385
function _settleInference(
@@ -402,13 +399,13 @@ contract InferenceProviderBridge is IInferenceProvider, Ownable, ReentrancyGuard
402399

403400
// Mint to each recipient
404401
if (providerPayment > 0) {
405-
atnToken.mint(provider, providerPayment);
402+
rpb.mint(provider, providerPayment);
406403
}
407404
if (treasuryPayment > 0) {
408-
atnToken.mint(rpb.dao(), treasuryPayment);
405+
rpb.mint(rpb.dao(), treasuryPayment);
409406
}
410407
if (shareholderPayment > 0) {
411-
atnToken.mint(address(rpb), shareholderPayment);
408+
rpb.mint(address(rpb), shareholderPayment);
412409
}
413410

414411
// Update RPB accounting for dividend tracking

contracts/core/ForcedErrorRegistry.sol

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pragma solidity ^0.8.20;
33

44
import "../utils/AutonetLib.sol";
55
import "./ParticipantStaking.sol";
6-
import "../tokens/ATNToken.sol";
6+
import "./RPB.sol";
77

88
/**
99
* @title ForcedErrorRegistry
@@ -19,7 +19,7 @@ import "../tokens/ATNToken.sol";
1919
* might rubber-stamp everything since real errors are rare.
2020
*/
2121
contract ForcedErrorRegistry {
22-
ATNToken public immutable atnToken;
22+
RPB public immutable rpb;
2323
ParticipantStaking public immutable staking;
2424
address public governance;
2525

@@ -61,8 +61,8 @@ contract ForcedErrorRegistry {
6161

6262
// ============ Constructor ============
6363

64-
constructor(address _atnToken, address _staking, address _governance) {
65-
atnToken = ATNToken(_atnToken);
64+
constructor(address _rpb, address _staking, address _governance) {
65+
rpb = RPB(_rpb);
6666
staking = ParticipantStaking(_staking);
6767
governance = _governance;
6868
}
@@ -73,7 +73,7 @@ contract ForcedErrorRegistry {
7373
* @dev Fund the jackpot pool. Anyone can contribute.
7474
*/
7575
function fundJackpotPool(uint256 _amount) external {
76-
require(atnToken.transferFrom(msg.sender, address(this), _amount), "Transfer failed");
76+
require(rpb.transferFrom(msg.sender, address(this), _amount), "Transfer failed");
7777
jackpotPool += _amount;
7878
emit JackpotFunded(msg.sender, _amount);
7979
}
@@ -146,7 +146,7 @@ contract ForcedErrorRegistry {
146146

147147
// Award jackpot (already reserved from pool during injection)
148148
uint256 award = fe.jackpotAmount;
149-
require(atnToken.transfer(msg.sender, award), "Jackpot transfer failed");
149+
require(rpb.transfer(msg.sender, award), "Jackpot transfer failed");
150150

151151
emit ForcedErrorCaught(_taskId, msg.sender, award);
152152
}

contracts/core/ParticipantStaking.sol

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.20;
33

4-
import "../tokens/ATNToken.sol";
4+
import "./RPB.sol";
55
import "../utils/AutonetLib.sol";
66

77
/**
@@ -12,7 +12,7 @@ import "../utils/AutonetLib.sol";
1212
contract ParticipantStaking {
1313
using AutonetLib for AutonetLib.ParticipantRole;
1414

15-
ATNToken public immutable atnToken;
15+
RPB public immutable rpb;
1616
address public governance;
1717

1818
mapping(address => AutonetLib.StakeInfo) public stakes;
@@ -39,12 +39,12 @@ contract ParticipantStaking {
3939
_;
4040
}
4141

42-
constructor(address _atnToken, address _governance) {
43-
atnToken = ATNToken(_atnToken);
42+
constructor(address _rpb, address _governance) {
43+
rpb = RPB(_rpb);
4444
governance = _governance;
4545

4646
// Default minimum stakes (in wei)
47-
uint8 decimals = atnToken.decimals();
47+
uint8 decimals = rpb.decimals();
4848
minStakeAmount[AutonetLib.ParticipantRole.PROPOSER] = 100 * (10**decimals);
4949
minStakeAmount[AutonetLib.ParticipantRole.SOLVER] = 50 * (10**decimals);
5050
minStakeAmount[AutonetLib.ParticipantRole.COORDINATOR] = 500 * (10**decimals);
@@ -93,7 +93,7 @@ contract ParticipantStaking {
9393
active: true
9494
});
9595

96-
require(atnToken.transferFrom(msg.sender, address(this), _amount), "Transfer failed");
96+
require(rpb.transferFrom(msg.sender, address(this), _amount), "Transfer failed");
9797
emit Staked(msg.sender, _role, _amount);
9898
}
9999

@@ -103,7 +103,7 @@ contract ParticipantStaking {
103103
require(userStake.lockupUntil == 0, "Unstake in progress");
104104

105105
userStake.amount += _amount;
106-
require(atnToken.transferFrom(msg.sender, address(this), _amount), "Transfer failed");
106+
require(rpb.transferFrom(msg.sender, address(this), _amount), "Transfer failed");
107107
emit Staked(msg.sender, userStake.role, _amount);
108108
}
109109

@@ -130,7 +130,7 @@ contract ParticipantStaking {
130130
userStake.role = AutonetLib.ParticipantRole.NONE;
131131
userStake.lockupUntil = 0;
132132

133-
require(atnToken.transfer(msg.sender, amount), "Transfer failed");
133+
require(rpb.transfer(msg.sender, amount), "Transfer failed");
134134
emit Unstaked(msg.sender, role, amount);
135135
}
136136

@@ -142,7 +142,7 @@ contract ParticipantStaking {
142142
userStake.amount -= _amount;
143143

144144
// Burn slashed tokens so they don't stay locked in the contract
145-
atnToken.burn(_amount);
145+
rpb.burn(_amount);
146146

147147
emit Slashed(_participant, userStake.role, _amount, _reason);
148148

0 commit comments

Comments
 (0)