Skip to content

Commit 03db257

Browse files
committed
Ship all 4 gaps: reputation, subscriptions, cross-chain, escrow
Extensions built via 10 parallel subagents: Reputation (EAS): - AgentReputation.sol: EAS-wrapping contract, rateService/getAverageScore (21 tests) - SDK: rateService/getProviderRatings/getProviderScore via viem - Service: HTTP API for reputation scores with time-decay weighting Subscriptions (native): - SubscriptionManager.sol: auto-renewing channels, EIP-712 auth (36 tests) - SDK: SubscriptionSession class — request/renew/cancel - CLI: valuepacket subscribe/subscriptions list/cancel/renew Cross-Chain (Axelar): - CrossChainSettlement.sol: IAxelarExecutable, EIP-712 on foreign domain (28 tests) - Verifies ChannelClose sig against pre-stored SOURCE domain separator Full integration test: - extensions-integration.test.ts: proves all 3 work together - Reputation averages across subscription periods, cross-chain settles final period 149 total Solidity tests (was 92)
1 parent 9c9c308 commit 03db257

19 files changed

Lines changed: 8294 additions & 158 deletions

cli/src/index.ts

Lines changed: 226 additions & 158 deletions
Large diffs are not rendered by default.

cli/test/extensions-integration.test.ts

Lines changed: 1441 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
interface IEAS {
5+
function attest(AttestationRequest calldata request) external payable returns (bytes32);
6+
function getAttestation(bytes32 uid) external view returns (Attestation memory);
7+
8+
struct AttestationRequest {
9+
bytes32 schema;
10+
AttestationRequestData data;
11+
}
12+
13+
struct AttestationRequestData {
14+
address recipient;
15+
uint64 expirationTime;
16+
bool revocable;
17+
bytes32 refUID;
18+
bytes data;
19+
uint256 value;
20+
}
21+
22+
struct Attestation {
23+
bytes32 uid;
24+
bytes32 schema;
25+
uint64 time;
26+
uint64 expirationTime;
27+
uint64 revocationTime;
28+
bytes32 refUID;
29+
address recipient;
30+
address attester;
31+
bool revocable;
32+
bytes data;
33+
}
34+
}
35+
36+
/// @title AgentReputation
37+
/// @notice Wraps EAS attestations so payers can rate agent service quality
38+
/// after every ValuePacket transaction.
39+
contract AgentReputation {
40+
IEAS public immutable EAS;
41+
bytes32 public immutable SCHEMA_UID;
42+
43+
struct Rating {
44+
bytes32 uid;
45+
address provider;
46+
address payer;
47+
bytes32 channelId;
48+
uint8 score;
49+
string comment;
50+
uint64 timestamp;
51+
}
52+
53+
mapping(address => bytes32[]) private _providerRatings;
54+
mapping(bytes32 => uint8) private _scores;
55+
mapping(bytes32 => mapping(address => bool)) private _hasRated;
56+
57+
error InvalidScore();
58+
error AlreadyRated();
59+
error InvalidPagination();
60+
61+
event ServiceRated(
62+
address indexed provider,
63+
address indexed payer,
64+
bytes32 indexed channelId,
65+
uint8 score
66+
);
67+
68+
/// @param eas Address of the EAS contract
69+
/// @dev Computes the schema UID and boots the schema with a self-attestation.
70+
/// In production the schema must first be registered with the EAS
71+
/// SchemaRegistry contract. The constructor attestation will revert
72+
/// unless the schema is already registered.
73+
constructor(address eas) {
74+
EAS = IEAS(eas);
75+
76+
SCHEMA_UID = keccak256(
77+
abi.encodePacked(
78+
"address provider,address payer,bytes32 channelId,uint8 score,string comment",
79+
address(0),
80+
true
81+
)
82+
);
83+
84+
EAS.attest(
85+
IEAS.AttestationRequest({
86+
schema: SCHEMA_UID,
87+
data: IEAS.AttestationRequestData({
88+
recipient: address(0),
89+
expirationTime: 0,
90+
revocable: true,
91+
refUID: bytes32(0),
92+
data: abi.encode(address(0), address(0), bytes32(0), uint8(0), ""),
93+
value: 0
94+
})
95+
})
96+
);
97+
}
98+
99+
/// @notice Rate a service provider after a ValuePacket transaction.
100+
/// @param provider Address of the agent service provider being rated.
101+
/// @param channelId Unique identifier of the payment channel.
102+
/// @param score Rating from 0 (worst) to 10 (best).
103+
/// @param comment Optional human-readable feedback.
104+
/// @return uid The EAS attestation UID for this rating.
105+
function rateService(
106+
address provider,
107+
bytes32 channelId,
108+
uint8 score,
109+
string calldata comment
110+
) external returns (bytes32 uid) {
111+
if (score > 10) revert InvalidScore();
112+
if (_hasRated[channelId][msg.sender]) revert AlreadyRated();
113+
114+
_hasRated[channelId][msg.sender] = true;
115+
116+
bytes memory attestationData = abi.encode(
117+
provider,
118+
msg.sender,
119+
channelId,
120+
score,
121+
comment
122+
);
123+
124+
uid = EAS.attest(
125+
IEAS.AttestationRequest({
126+
schema: SCHEMA_UID,
127+
data: IEAS.AttestationRequestData({
128+
recipient: provider,
129+
expirationTime: 0,
130+
revocable: true,
131+
refUID: bytes32(0),
132+
data: attestationData,
133+
value: 0
134+
})
135+
})
136+
);
137+
138+
_providerRatings[provider].push(uid);
139+
_scores[uid] = score;
140+
141+
emit ServiceRated(provider, msg.sender, channelId, score);
142+
}
143+
144+
/// @notice Paginate through a provider's ratings (most recent last).
145+
/// @param provider Provider to fetch ratings for.
146+
/// @param offset Number of ratings to skip.
147+
/// @param limit Maximum ratings to return.
148+
/// @return results Array of Rating structs.
149+
function getRatings(
150+
address provider,
151+
uint256 offset,
152+
uint256 limit
153+
) external view returns (Rating[] memory results) {
154+
if (limit == 0) revert InvalidPagination();
155+
156+
bytes32[] storage uids = _providerRatings[provider];
157+
uint256 total = uids.length;
158+
if (offset >= total) return new Rating[](0);
159+
160+
uint256 end = offset + limit;
161+
if (end > total) end = total;
162+
uint256 resultLen = end - offset;
163+
164+
results = new Rating[](resultLen);
165+
for (uint256 i = 0; i < resultLen; i++) {
166+
bytes32 u = uids[offset + i];
167+
IEAS.Attestation memory att = EAS.getAttestation(u);
168+
(
169+
address decodedProvider,
170+
address decodedPayer,
171+
bytes32 decodedChannelId,
172+
uint8 decodedScore,
173+
string memory decodedComment
174+
) = abi.decode(att.data, (address, address, bytes32, uint8, string));
175+
176+
results[i] = Rating({
177+
uid: u,
178+
provider: decodedProvider,
179+
payer: decodedPayer,
180+
channelId: decodedChannelId,
181+
score: decodedScore,
182+
comment: decodedComment,
183+
timestamp: att.time
184+
});
185+
}
186+
}
187+
188+
/// @notice Returns the arithmetic mean of all scores for a provider.
189+
/// @param provider Provider address.
190+
/// @return average Average score (0 if no ratings exist). Truncated to integer.
191+
function getAverageScore(address provider) external view returns (uint256) {
192+
bytes32[] storage uids = _providerRatings[provider];
193+
uint256 count = uids.length;
194+
if (count == 0) return 0;
195+
196+
uint256 sum;
197+
for (uint256 i = 0; i < count; i++) {
198+
sum += _scores[uids[i]];
199+
}
200+
201+
return sum / count;
202+
}
203+
204+
/// @notice Number of ratings a provider has received.
205+
/// @param provider Provider address.
206+
/// @return count Total rating count.
207+
function getRatingCount(address provider) external view returns (uint256) {
208+
return _providerRatings[provider].length;
209+
}
210+
211+
/// @notice Look up the score for a specific attestation UID.
212+
/// @param uid EAS attestation UID.
213+
/// @return score The stored score (0-10).
214+
function getScore(bytes32 uid) external view returns (uint8) {
215+
return _scores[uid];
216+
}
217+
218+
/// @notice Check whether a payer has already rated a given channel.
219+
/// @param channelId Payment channel identifier.
220+
/// @param payer Payer address.
221+
/// @return rated True if the payer has already submitted a rating for this channel.
222+
function hasRated(bytes32 channelId, address payer) external view returns (bool) {
223+
return _hasRated[channelId][payer];
224+
}
225+
}

0 commit comments

Comments
 (0)