Skip to content

feat: Add ECDSA validator check when permitting an app version#460

Merged
Spacesai1or merged 3 commits into
mainfrom
feat/ecdsa-validator-contracts
Jan 31, 2026
Merged

feat: Add ECDSA validator check when permitting an app version#460
Spacesai1or merged 3 commits into
mainfrom
feat/ecdsa-validator-contracts

Conversation

@Spacesai1or

@Spacesai1or Spacesai1or commented Jan 14, 2026

Copy link
Copy Markdown
Contributor

Description

  • Inits new facet: packages/libs/contracts-sdk/contracts/facets/VincentZeroDevConfigFacet.sol with a getter/setter for setting the canonical ZeroDev ECDSA Validator address (source from https://github.com/zerodevapp/kernel?tab=readme-ov-file)
  • Updates Diamond constructor to require _ecdsaValidatorAddress param and handles registering newVincentZeroDevConfigFacet
  • Updates Makefile with new required ENV: VINCENT_ECDSA_VALIDATOR_ADDRESS and adds new cmd: set-ecdsa-validator to execute the set validator address script
  • Adds VincentZeroDevStorage to packages/libs/contracts-sdk/contracts/LibVincentDiamondStorage.sol to store ecdsaValidatorAddress
  • Updates packages/libs/contracts-sdk/contracts/facets/VincentUserFacet.sol:
    • Adds two new function modifiers:
      • onlySmartAccountOwner which is used by permitAppVersion to check if msg.sender is the registered ECDSA signer for the agentAddress
      • onlyAgentOwner which is used by unPermitAppVersion, rePermitApp, and setAbilityPolicyParameters to check if msg.sender is address registeredOwner = us_.registeredAgentAddressToUserAddress[agentAddress];
  • In packages/libs/contracts-sdk/contracts/libs/LibVincentUserFacet.sol AgentNotRegisteredToUser was replaced with NotAgentOwner which is used by both above function modifiers
  • Init new test helper script: packages/libs/contracts-sdk/script/FetchECDSAValidatorBytecode.sol which is used to fetch the ZeroDev ECDSA Validator bytecode from Base deployment and write it to a file so the tests can write the bytecode to the expected Base/Base sepolia address
  • Init new script: packages/libs/contracts-sdk/script/SetEcdsaValidatorAddress.sol for setting the ZeroDev ECDSA Validator address used by the onlySmartAccountOwner modifier in VincentUserFacet
  • Updates: packages/libs/contracts-sdk/script/UpdateFacet.sol to handle new VincentZeroDevConfigFacet

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

  • Existing tests were failing with NotAgentOwner, after updating them to use the test helper which sets the owner of the smart account to the test address, tests are passing
  • Added new test script for new facet

Checklist:

  • I created a release plan (nx release plan) describing my changes and the version bump
  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

@vercel

vercel Bot commented Jan 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vincent-app-dashboard Ready Ready Preview, Comment Jan 29, 2026 8:39pm

Request Review

@Spacesai1or Spacesai1or marked this pull request as ready for review January 14, 2026 22:46
Copilot AI review requested due to automatic review settings January 14, 2026 22:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds ECDSA validator integration to verify smart account ownership through ZeroDev's ECDSA validator contract when users permit app versions. The PR introduces a new access control mechanism that checks the smart account owner via an external validator contract before allowing operations.

Changes:

  • Adds VincentZeroDevConfigFacet for managing the ZeroDev ECDSA validator address configuration
  • Implements onlySmartAccountOwner modifier in VincentUserFacet to verify smart account ownership via the ECDSA validator for permitAppVersion
  • Updates onlyAgentOwner modifier to check internal state for other user functions (unPermitAppVersion, rePermitApp, setAbilityPolicyParameters)

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
contracts/facets/VincentZeroDevConfigFacet.sol New facet providing getter/setter for ECDSA validator address
contracts/facets/VincentUserFacet.sol Adds two new modifiers for ownership verification and applies them to user functions
contracts/libs/LibVincentUserFacet.sol Replaces AgentNotRegisteredToUser error with more descriptive NotAgentOwner error
contracts/VincentDiamond.sol Updates constructor to accept and store ECDSA validator address, registers new facet
contracts/LibVincentDiamondStorage.sol Adds VincentZeroDevStorage library for storing ECDSA validator address
test/TestHelpers.sol New helper contract for setting up ECDSA validator in tests using bytecode fixtures
test/fixtures/ECDSAValidatorBytecode.txt Contains ECDSA validator bytecode fetched from Base mainnet
script/FetchECDSAValidatorBytecode.sol Helper script to fetch and save ECDSA validator bytecode from Base
script/SetEcdsaValidatorAddress.sol Script for updating ECDSA validator address on deployed contracts
script/UpdateFacet.sol Adds support for updating VincentZeroDevConfigFacet
script/DeployVincentDiamond.sol Updates deployment to require ECDSA validator address parameter
Makefile Adds VINCENT_ECDSA_VALIDATOR_ADDRESS env var requirement and set-ecdsa-validator command
foundry.toml Adds filesystem permissions for test fixtures directory
abis/VincentZeroDevConfigFacet.abi.json ABI for new config facet
abis/VincentUserFacet.abi.json Updates ABI with new error signature
test/facets/*.t.sol Updates tests to register smart account owners and fix expected error types
Comments suppressed due to low confidence (1)

packages/libs/contracts-sdk/contracts/facets/VincentUserFacet.sol:127

  • The AgentRegisteredToDifferentUser check at lines 123-127 is now redundant since the onlySmartAccountOwner modifier already validates that the caller is the owner according to the ECDSA validator. If the ECDSA validator check passes, this internal state check should always pass as well (assuming state consistency). Consider removing this redundant check or adding a comment explaining why both checks are necessary.
        // Check if the agent is already registered to a different user
        address registeredUserAddress = us_.registeredAgentAddressToUserAddress[agentAddress];
        if (registeredUserAddress != address(0) && registeredUserAddress != sender) {
            revert LibVincentUserFacet.AgentRegisteredToDifferentUser(registeredUserAddress);
        }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/libs/contracts-sdk/contracts/facets/VincentUserFacet.sol
Comment thread packages/libs/contracts-sdk/contracts/facets/VincentUserFacet.sol
Comment thread packages/libs/contracts-sdk/contracts/VincentDiamond.sol
Comment thread packages/libs/contracts-sdk/test/TestHelpers.sol
Comment thread packages/libs/contracts-sdk/script/SetEcdsaValidatorAddress.sol Outdated

@DashKash54 DashKash54 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good but left some design questions

Comment thread packages/libs/contracts-sdk/Makefile
Comment thread packages/libs/contracts-sdk/contracts/facets/VincentUserFacet.sol
Comment thread packages/libs/contracts-sdk/contracts/facets/VincentUserFacet.sol
}

/**
* @notice Validates that the caller is the registered owner of the agent in Vincent's internal state

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the user's EOA the registered owner Agent smart account in registeredAgentAddressToUserAddress, can it ever be separate? Why have two separate modifiers onlyAgentOwner and onlySmartAccountOwner?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like onlySmartAccountOwner is only used on installation. we use it to set the owner, and then onlyAgentOwner checks that owner.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why have two if they're doing the same thing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the modifiers calls the ecdsa validator storage contract which is comparatively expensive, but required when the user permits the app for the smart account for the first time, then because we store the owner in the diamond storage, the second modifier can just read from storage which saves gas

Comment thread packages/libs/contracts-sdk/contracts/facets/VincentUserFacet.sol
/**
* @title FetchECDSAValidatorBytecode
* @notice Script to fetch the ECDSA validator bytecode from Base Mainnet and save it
* @dev Run with: forge script script/FetchECDSAValidatorBytecode.sol:FetchECDSAValidatorBytecode --rpc-url https://mainnet.base.org

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used only in our local tests but not used during the deployment or setup of ecdsaValidator?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this allows us to test against the actual ecdsa validator that's deployed on Base. The tests will etch the bytecode into the expected address to simulate deployment on Base mainnet/sepolia

Comment thread packages/libs/contracts-sdk/script/FetchECDSAValidatorBytecode.sol
Comment thread packages/libs/contracts-sdk/script/FetchECDSAValidatorBytecode.sol
* The ECDSA validator has: mapping(address => address) public ecdsaValidatorStorage;
* This mapping is at slot 0, so we calculate: keccak256(abi.encode(smartAccount, 0))
*/
function registerSmartAccountOwner(address smartAccount, address owner) internal {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will we set this up for the Agent smart account IRL?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the onlySmartAccountOwner takes care of this when permitting the app. like onlySmartAccountOwner validates the address matches the smart account owner. and then when permitting the app we store this owner. is this correct @Spacesai1or ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the EOA is set as the validator when the smart account is deployed, then we record the verified owner EOA in the diamond storage when an App is permitted


// Register smart account owners
// This simulates Frank and George owning their agents (smart accounts)
registerSmartAccountOwner(APP_USER_FRANK, USER_FRANK);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any tests to verify that permitApp doesn't work if the signer isn't ECDSA?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm can you clarify this question @DashKash54 ? why would the signer be anything else, and yes, if it was something else (not ecdsa) then it wouldn't work?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to validate that it won't work if the signer is anything else, confirming unhappy path doesn't work

@DashKash54 DashKash54 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving but we have 2 unresolved comments:

  1. Why have two separate modifier: #460 (comment)
  2. How does the ownerSmartAccount owner work for agent: #460 (comment)

@Spacesai1or Spacesai1or merged commit 1fa0a48 into main Jan 31, 2026
5 checks passed
@Spacesai1or Spacesai1or deleted the feat/ecdsa-validator-contracts branch January 31, 2026 07:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants