Skip to content

Commit 288d03e

Browse files
authored
Merge pull request #119 from Virtual-Protocol/feat/yang-check-acp-agent-env-correctness
feat: add read contract check on session signer
2 parents 5a15038 + 2493438 commit 288d03e

5 files changed

Lines changed: 294 additions & 0 deletions

File tree

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
const SINGLE_SIGNER_VALIDATION_MODULE_ABI = [
2+
{ inputs: [], name: "InvalidSignatureType", type: "error" },
3+
{
4+
inputs: [],
5+
name: "NotAuthorized",
6+
type: "error",
7+
},
8+
{ inputs: [], name: "NotImplemented", type: "error" },
9+
{
10+
inputs: [],
11+
name: "UnexpectedDataPassed",
12+
type: "error",
13+
},
14+
{
15+
anonymous: true,
16+
inputs: [
17+
{
18+
indexed: true,
19+
internalType: "address",
20+
name: "account",
21+
type: "address",
22+
},
23+
{
24+
indexed: true,
25+
internalType: "uint32",
26+
name: "entityId",
27+
type: "uint32",
28+
},
29+
{
30+
indexed: true,
31+
internalType: "address",
32+
name: "newSigner",
33+
type: "address",
34+
},
35+
{
36+
indexed: false,
37+
internalType: "address",
38+
name: "previousSigner",
39+
type: "address",
40+
},
41+
],
42+
name: "SignerTransferred",
43+
type: "event",
44+
},
45+
{
46+
inputs: [],
47+
name: "moduleId",
48+
outputs: [{ internalType: "string", name: "", type: "string" }],
49+
stateMutability: "pure",
50+
type: "function",
51+
},
52+
{
53+
inputs: [{ internalType: "bytes", name: "data", type: "bytes" }],
54+
name: "onInstall",
55+
outputs: [],
56+
stateMutability: "nonpayable",
57+
type: "function",
58+
},
59+
{
60+
inputs: [{ internalType: "bytes", name: "data", type: "bytes" }],
61+
name: "onUninstall",
62+
outputs: [],
63+
stateMutability: "nonpayable",
64+
type: "function",
65+
},
66+
{
67+
inputs: [
68+
{ internalType: "address", name: "account", type: "address" },
69+
{
70+
internalType: "bytes32",
71+
name: "hash",
72+
type: "bytes32",
73+
},
74+
],
75+
name: "replaySafeHash",
76+
outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }],
77+
stateMutability: "view",
78+
type: "function",
79+
},
80+
{
81+
inputs: [
82+
{ internalType: "uint32", name: "entityId", type: "uint32" },
83+
{
84+
internalType: "address",
85+
name: "account",
86+
type: "address",
87+
},
88+
],
89+
name: "signers",
90+
outputs: [{ internalType: "address", name: "", type: "address" }],
91+
stateMutability: "view",
92+
type: "function",
93+
},
94+
{
95+
inputs: [{ internalType: "bytes4", name: "interfaceId", type: "bytes4" }],
96+
name: "supportsInterface",
97+
outputs: [{ internalType: "bool", name: "", type: "bool" }],
98+
stateMutability: "view",
99+
type: "function",
100+
},
101+
{
102+
inputs: [
103+
{ internalType: "uint32", name: "entityId", type: "uint32" },
104+
{
105+
internalType: "address",
106+
name: "newSigner",
107+
type: "address",
108+
},
109+
],
110+
name: "transferSigner",
111+
outputs: [],
112+
stateMutability: "nonpayable",
113+
type: "function",
114+
},
115+
{
116+
inputs: [
117+
{ internalType: "address", name: "account", type: "address" },
118+
{
119+
internalType: "uint32",
120+
name: "entityId",
121+
type: "uint32",
122+
},
123+
{ internalType: "address", name: "sender", type: "address" },
124+
{
125+
internalType: "uint256",
126+
name: "",
127+
type: "uint256",
128+
},
129+
{ internalType: "bytes", name: "", type: "bytes" },
130+
{
131+
internalType: "bytes",
132+
name: "",
133+
type: "bytes",
134+
},
135+
],
136+
name: "validateRuntime",
137+
outputs: [],
138+
stateMutability: "view",
139+
type: "function",
140+
},
141+
{
142+
inputs: [
143+
{ internalType: "address", name: "account", type: "address" },
144+
{
145+
internalType: "uint32",
146+
name: "entityId",
147+
type: "uint32",
148+
},
149+
{ internalType: "address", name: "", type: "address" },
150+
{
151+
internalType: "bytes32",
152+
name: "digest",
153+
type: "bytes32",
154+
},
155+
{ internalType: "bytes", name: "signature", type: "bytes" },
156+
],
157+
name: "validateSignature",
158+
outputs: [{ internalType: "bytes4", name: "", type: "bytes4" }],
159+
stateMutability: "view",
160+
type: "function",
161+
},
162+
{
163+
inputs: [
164+
{
165+
internalType: "uint32",
166+
name: "entityId",
167+
type: "uint32",
168+
},
169+
{
170+
components: [
171+
{ internalType: "address", name: "sender", type: "address" },
172+
{
173+
internalType: "uint256",
174+
name: "nonce",
175+
type: "uint256",
176+
},
177+
{ internalType: "bytes", name: "initCode", type: "bytes" },
178+
{
179+
internalType: "bytes",
180+
name: "callData",
181+
type: "bytes",
182+
},
183+
{
184+
internalType: "bytes32",
185+
name: "accountGasLimits",
186+
type: "bytes32",
187+
},
188+
{
189+
internalType: "uint256",
190+
name: "preVerificationGas",
191+
type: "uint256",
192+
},
193+
{ internalType: "bytes32", name: "gasFees", type: "bytes32" },
194+
{
195+
internalType: "bytes",
196+
name: "paymasterAndData",
197+
type: "bytes",
198+
},
199+
{ internalType: "bytes", name: "signature", type: "bytes" },
200+
],
201+
internalType: "struct PackedUserOperation",
202+
name: "userOp",
203+
type: "tuple",
204+
},
205+
{ internalType: "bytes32", name: "userOpHash", type: "bytes32" },
206+
],
207+
name: "validateUserOp",
208+
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
209+
stateMutability: "view",
210+
type: "function",
211+
},
212+
];
213+
214+
export default SINGLE_SIGNER_VALIDATION_MODULE_ABI;

src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ export const HTTP_STATUS_CODES = {
1919
OK: 200,
2020
PAYMENT_REQUIRED: 402,
2121
};
22+
23+
export const SINGLE_SIGNER_VALIDATION_MODULE_ADDRESS: Address = "0x00000000000099DE0BF6fA90dEB851E2A2df7d83";

src/contractClients/acpContractClient.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,23 @@ class AcpContractClient extends BaseAcpContractClient {
7474
this.sessionKeyClient,
7575
this.publicClient
7676
);
77+
78+
const account = this.sessionKeyClient.account;
79+
const sessionSignerAddress: Address = await account.getSigner().getAddress();
80+
81+
if (!await account.isAccountDeployed()) {
82+
throw new AcpError(
83+
`ACP Contract Client validation failed: agent account ${this.agentWalletAddress} is not deployed on-chain`
84+
);
85+
}
86+
87+
await this.validateSessionKeyOnChain(sessionSignerAddress, sessionEntityKeyId);
88+
89+
console.log("Connected to ACP:", {
90+
agentWalletAddress: this.agentWalletAddress,
91+
whitelistedWalletAddress: sessionSignerAddress,
92+
entityId: sessionEntityKeyId,
93+
});
7794
}
7895

7996
getRandomNonce(bits = 152) {

src/contractClients/acpContractClientV2.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,23 @@ class AcpContractClientV2 extends BaseAcpContractClient {
113113
this.sessionKeyClient,
114114
this.publicClient
115115
);
116+
117+
const account = this.sessionKeyClient.account;
118+
const sessionSignerAddress: Address = await account.getSigner().getAddress();
119+
120+
if (!await account.isAccountDeployed()) {
121+
throw new AcpError(
122+
`ACP Contract Client validation failed: agent account ${this.agentWalletAddress} is not deployed on-chain`
123+
);
124+
}
125+
126+
await this.validateSessionKeyOnChain(sessionSignerAddress, sessionEntityKeyId);
127+
128+
console.log("Connected to ACP:", {
129+
agentWalletAddress: this.agentWalletAddress,
130+
whitelistedWalletAddress: sessionSignerAddress,
131+
entityId: sessionEntityKeyId,
132+
});
116133
}
117134

118135
getRandomNonce(bits = 152) {

src/contractClients/baseAcpContractClient.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
keccak256,
1010
toEventSignature,
1111
toHex,
12+
zeroAddress,
1213
} from "viem";
1314
import { AcpContractConfig, baseAcpConfig } from "../configs/acpConfigs";
1415
import ACP_V2_ABI from "../abis/acpAbiV2";
@@ -25,6 +26,8 @@ import {
2526
X402PaymentResponse,
2627
} from "../interfaces";
2728
import FIAT_TOKEN_V2_ABI from "../abis/fiatTokenV2Abi";
29+
import SINGLE_SIGNER_VALIDATION_MODULE_ABI from "../abis/singleSignerValidationModuleAbi";
30+
import { SINGLE_SIGNER_VALIDATION_MODULE_ADDRESS } from "../constants";
2831

2932
export enum MemoType {
3033
MESSAGE, // 0 - Text message
@@ -89,6 +92,47 @@ abstract class BaseAcpContractClient {
8992
});
9093
}
9194

95+
protected async validateSessionKeyOnChain(
96+
sessionSignerAddress: Address,
97+
sessionEntityKeyId: number
98+
): Promise<void> {
99+
const onChainSignerAddress = ((await this.publicClient.readContract({
100+
address: SINGLE_SIGNER_VALIDATION_MODULE_ADDRESS,
101+
abi: SINGLE_SIGNER_VALIDATION_MODULE_ABI,
102+
functionName: "signers",
103+
args: [sessionEntityKeyId, this.agentWalletAddress],
104+
})) as Address).toLowerCase();
105+
106+
if (onChainSignerAddress === zeroAddress.toLowerCase()) {
107+
throw new AcpError(
108+
`ACP Contract Client validation failed:\n${JSON.stringify(
109+
{
110+
reason: "no whitelisted wallet registered on-chain for entity id",
111+
entityId: sessionEntityKeyId,
112+
agentWalletAddress: this.agentWalletAddress,
113+
},
114+
null,
115+
2
116+
)}`
117+
);
118+
}
119+
120+
if (onChainSignerAddress !== sessionSignerAddress.toLowerCase()) {
121+
throw new AcpError(
122+
`ACP Contract Client validation failed:\n${JSON.stringify(
123+
{
124+
agentWalletAddress: this.agentWalletAddress,
125+
entityId: sessionEntityKeyId,
126+
givenWhitelistedWalletAddress: sessionSignerAddress,
127+
expectedWhitelistedWalletAddress: onChainSignerAddress,
128+
},
129+
null,
130+
2
131+
)}`
132+
);
133+
}
134+
}
135+
92136
abstract handleOperation(operations: OperationPayload[]): Promise<{ userOpHash: Address , txnHash: Address }>;
93137

94138
abstract getJobId(

0 commit comments

Comments
 (0)