Created
March 24, 2026 15:30
-
-
Save denniswon/fffb80a5fb4647abcfbdf1ecbe3dd7ad to your computer and use it in GitHub Desktop.
Newton CAB
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Now I have the full picture. Here's the deep analysis: | |
| The Architecture Problem | |
| The commenter is pointing at a fundamental tension in the PRD's design. Let me map it out: | |
| Current PRD Architecture (per-account policy storage) | |
| Developer deploys PolicyClient contract (once) | |
| βββ Per user account: | |
| βββ Install ownable validator (2-of-2) | |
| βββ Install Policy Client fallback module | |
| βββ Call setPolicy() on the account's fallback module β NΓM transactions | |
| The NewtonPolicyClient mixin (line 44-48) stores policy state per-contract in ERC-7201 namespaced storage: | |
| struct NewtonPolicyClientStorage { | |
| INewtonProverTaskManager policyTaskManager; | |
| address policy; // pointer to NewtonPolicy contract | |
| bytes32 policyId; // which policy config within that contract | |
| address policyClientOwner; | |
| } | |
| If the fallback module is per-account, each account has its own copy of this storage. Changing policyId or policy address = one transaction per account per chain. | |
| What Newton Already Has (singleton pattern) | |
| Newton's existing architecture already solves this for the current AVS use case: | |
| 1. NewtonPolicy contract β Singleton deployed by the developer via NewtonPolicyFactory. Stores policy configs keyed by policyId. One contract, many configs. | |
| 2. NewtonPolicyClient mixin β Each developer deploys ONE policy client contract. It stores a pointer to the NewtonPolicy contract and a policyId. When the developer calls setPolicy(policyConfig), it updates the config on the NewtonPolicy | |
| contract and gets back a new policyId β one transaction, affects all tasks that reference this policy client. | |
| 3. PolicyClientRegistry β Directory of active policy clients, keyed by address. Used by IdentityRegistry to validate linkages. | |
| 4. TaskManager validation β _getPolicyState() reads the policy from the policy client address at validation time, not from per-account storage. The policy client is passed as task.policyClient in the Task struct. | |
| The Key Insight | |
| The commenter's concern is valid only if the ERC-7579 fallback module duplicates policy storage per-account. But it doesn't have to. | |
| There are three architectures worth considering: | |
| --- | |
| Option A: Shared Policy Client Contract (simplest, Newton-native) | |
| The developer deploys one NewtonPolicyClient contract (already the existing pattern). The ERC-7579 fallback module on each account just stores a pointer to this shared contract β not the policy config itself. | |
| Developer's PolicyClient (singleton, on-chain) | |
| βββ policy: 0xNewtonPolicy | |
| βββ policyId: bytes32 | |
| βββ policyClientOwner: developer | |
| User Account A βββ fallback module stores: policyClient = 0xDevPolicyClient | |
| User Account B βββ fallback module stores: policyClient = 0xDevPolicyClient | |
| User Account C βββ (not yet created, will inherit on first use) | |
| Policy change: Developer calls setPolicy() on their single PolicyClient contract. One transaction. All accounts immediately reflect the new policy because the ownable validator calls isValidSignature() on the PolicyClient, which reads the | |
| current policy at validation time. | |
| Accounts not yet created: No problem β the policyClient address is a constructor/init parameter of the SDK. When the account is eventually created, it points to the already-configured PolicyClient. | |
| Cross-chain: The developer deploys one PolicyClient per chain (or uses a deterministic deploy like CREATE2 for the same address). Policy changes = M transactions (one per chain), not NΓM. | |
| Option B: Registry Lookup (no per-account module state) | |
| The fallback module doesn't store policyClient at all. Instead, it resolves the policy client from PolicyClientRegistry at validation time, keyed by the developer's address (the account creator). | |
| PolicyClientRegistry (singleton) | |
| βββ developer address β PolicyClient address | |
| User Account A βββ fallback module stores: developer = 0xDev (or nothing, inferred from account creator) | |
| Pro: Zero per-account configuration. | |
| Con: Extra indirection at validation time (one more SLOAD). Also requires a reliable mapping from "account" to "developer" β which might not exist on-chain unless the SDK stores it. | |
| Option C: Hybrid (per-account override + default) | |
| Each account defaults to the developer's shared PolicyClient but can optionally override with a per-account policy. This supports tiered accounts (premium users get different policies). | |
| User Account A βββ fallback module: policyClientOverride = address(0) β uses developer default | |
| User Account B βββ fallback module: policyClientOverride = 0xCustomPolicy β custom | |
| --- | |
| My Recommendation | |
| Option A is the right answer for the PRD. It's the existing Newton pattern β the commenter's concern dissolves when you realize the fallback module only stores a pointer, not the policy config. The PRD should clarify: | |
| 1. The fallback module stores policyClientAddress (the developer's shared contract), not inline policy config | |
| 2. setPolicy() is called on the developer's PolicyClient contract, not on each user account | |
| 3. Policy changes are 1 transaction (same chain) or M transactions (M chains) β never NΓM | |
| 4. The SDK's NewtonAccount.init({ policyClientAddress }) already reflects this β it's a per-developer init, not per-user | |
| The PRD's setPolicy section was confusing because it described the fallback module as "exposes setPolicy() and policy configuration storage" β implying per-account config storage. It should say "exposes INewtonPolicyClient interface, | |
| delegating to the developer's shared PolicyClient contract." | |
| --- | |
| Want me to reply to the comment with this analysis and update both the PRD doc and Notion page accordingly? |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Now I have comprehensive understanding of the Rhinestone SDK. Let me synthesize the findings against our PRD.
β Insight βββββββββββββββββββββββββββββββββββββ
The Rhinestone SDK is much more sophisticated than what our PRD's Newton Account SDK suggests. Key architectural differences:
that EIP-712/Permit2 requires.
βββββββββββββββββββββββββββββββββββββββββββββββββ
PRD Gap Analysis: Newton Account SDK vs Rhinestone Reality
Critical Gaps
The Rhinestone SDK uses a three-step pattern: prepareTransaction() β signTransaction() β submitTransaction(). Our PRD's account.transfer() merges all three, which won't work because:
Our SDK needs either:
Rhinestone supports 6 account types with version configs:
type AccountType = 'safe' | 'nexus' | 'kernel' | 'startale' | 'passport' | 'eoa'
Our PRD has createAccount() with no account type selection. The user needs to choose which ERC-7579 account implementation to use. Different account types have different module installation mechanics, signature formats, and EIP-7702
compatibility.
Rhinestone's SDK has explicit setup() and deploy() methods per chain that handle module installation. Our PRD's createAccount() implicitly installs the ownable validator + intent executor + policy client fallback module, but doesn't expose:
The Rhinestone docs state: "The Intent Executor module must be installed on ERC-7579 accounts before using Warp." This is the on-chain permission layer enabling the relayer to execute on the user's behalf. Our PRD mentions it in the
architecture diagram but doesn't address the installation UX β do we install it automatically during createAccount()? What if the user already has a smart account without it?
Rhinestone intents require Permit2 approvals on the origin chain before signing. The SDK has dedicated helpers:
signPermit2Batch()
signPermit2Sequential()
checkERC20AllowanceDirect()
getPermit2Address()
Our PRD's convert() and transfer() don't mention Permit2 at all. Without it, cross-chain intents can't access the user's tokens for the solver to settle.
Rhinestone's /intents/route response includes tokenRequirements β a per-chain map of approvals and wraps needed before the intent can be signed. Types:
Our PRD skips this entirely. The SDK needs to handle or surface these requirements.
Rhinestone has explicit signature modes including SIG_MODE_ERC1271 (value 1). This is how the ownable validator knows that the second signer is a contract (EIP-1271) rather than an EOA. Our PRD doesn't mention signature mode configuration at
all. Newton's Policy Client as EIP-1271 co-signer requires signatureMode: SIG_MODE_ERC1271 when configuring the intent.
The Rhinestone Account type includes setupOps β operations to execute during account creation (module installations). Our PRD needs to define what setupOps Newton generates (ownable validator init, policy client module install, intent executor
install).
Medium Gaps
Rhinestone's portfolio is per-account via GET /accounts/{address}/portfolio with chain/token filters. Our PRD's account.balance() doesn't show chain filtering or locked/unlocked breakdown (Rhinestone tracks locked balances in resource locks).
Rhinestone supports sponsorSettings: { gasSponsored, bridgeFeesSponsored, swapFeesSponsored }. Our PRD mentions "Gas abstraction: Users should not need native gas tokens" but doesn't map this to the Rhinestone sponsor settings or paymaster
configuration.
Rhinestone has signEip7702InitData() which returns the signed authorization tuple. Our PRD's upgradeEOA() needs to use this β it's not a simple API call, it requires the user to sign the authorization.
Rhinestone has waitForExecution(result, acceptsPreconfirmations) that polls for on-chain finality. Our PRD returns { status: 'completed' } synchronously from transfer(), which isn't realistic for cross-chain intents that take 2-5 seconds.
Rhinestone's intent model supports destinationExecutions: Execution[] where each execution has { to, value, data }. This is how contract interactions on the destination chain work. Our PRD added data: '0x' to transfer() but doesn't model
multi-execution intents (e.g., swap + deposit into vault in one intent).
Architecture Corrections
Looking at Rhinestone's getOwnableValidator():
function getOwnableValidator(threshold: number, owners: Address[]): Module {
return {
address: OWNABLE_VALIDATOR_ADDRESS, // 0x000000000013fdb5234e4e3162a810f54d9f7e98
initData: encodeAbiParameters(
[{ name: 'threshold', type: 'uint256' }, { name: 'owners', type: 'address[]' }],
[BigInt(threshold), owners.sort()],
),
...
}
}
The owners array takes addresses (both EOA and contract). For Newton's 2-of-2, it would be:
owners: [userEOAAddress, newtonPolicyClientAddress].sort()
threshold: 2
The ownable validator at 0x000000000013fdb5234e4e3162a810f54d9f7e98 already supports EIP-1271 for contract owners. No custom module is needed β this confirms the PRD's approach. But the PRD should reference this exact contract address and the
SDK's getOwnableValidator() helper.
Looking at the Rhinestone SDK structure:
Our PRD mentions "Same-chain transfers skip Rhinestone entirely" in lessons.md, but the SDK interface doesn't distinguish between these paths. Rhinestone's SDK handles this internally based on routing β our SDK should too.
Summary: What Needs to Change in the PRD
ββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Priority β Gap β Action β
ββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β P0 β Prepare/sign/submit pipeline missing β Add three-step flow or document that sendTransaction wraps it β
ββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β P0 β Permit2 approval flow not addressed β Add token requirements step before intent submission β
ββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β P0 β Account type selection missing β Add account provider config (safe/nexus/kernel/eoa) β
ββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β P0 β SignatureMode.ERC1271 not configured β Document signature mode for Newton co-signer β
ββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β P1 β Module installation UX missing β Add setup(), isDeployed(), getValidators() β
ββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β P1 β Intent Executor installation not addressed β Document auto-install during account creation β
ββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β P1 β waitForExecution() missing β Add polling/callback for async intent completion β
ββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β P1 β EIP-7702 needs user signature β Fix upgradeEOA() to include signer param β
ββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β P2 β Portfolio locked/unlocked breakdown β Align with Rhinestone's portfolio model β
ββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β P2 β Gas sponsorship config missing β Map to Rhinestone sponsor settings β
ββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β P2 β Multi-execution intents not modeled β Support destinationExecutions[] β
ββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β P2 β setupOps for account init not defined β Define Newton's module installation operations β
ββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Want me to draft an updated SDK interface that addresses all P0 and P1 gaps?
β» Baked for 4m 1s
βΊ Agent "Research Rhinestone contracts repos" completed
βΊ Agent "Research Rhinestone SDK repo" completed
βΊ Agent "Research Rhinestone intent docs" completed
βΊ All three research agents completed. The findings confirm and strengthen my analysis. Let me add the critical new insights from the agent results that I missed earlier.
Additional Critical Findings from Deep Research
From the Contracts Agent (On-Chain Architecture)
Newton's EIP-1271 co-signer approach is confirmed viable, but with an important nuance:
The OwnableValidator's CheckSignatures.recoverNSignatures uses Solady's SignatureCheckerLib, which automatically falls back to ERC-1271 for contract addresses. This means:
The Intent Executor has permanent execution privileges β once installed, it can call executeFromExecutor on the smart account indefinitely. There's no time-bound or per-operation approval. All authorization happens at the signature level. This
is fine for Newton's use case but worth noting in the PRD's security section.
The atomic fill signer is centralized β $atomicFillSigner is a single Rhinestone-controlled ECDSA key. Newton cannot influence which fills are authorized unless integrated into Rhinestone's orchestrator pipeline. This confirms our Model C
approach (Newton wraps Rhinestone) is correct β we control policy evaluation before Rhinestone even sees the intent.
From the SDK Agent (Full API Surface)
Three signing flows exist, not one:
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Flow β Use Case β Method β
ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Intent (cross-chain) β Cross-chain via orchestrator β prepareTransaction β signTransaction β submitTransaction β
ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β UserOp (same-chain) β Same-chain via 4337 bundler β prepareUserOperation β signUserOperation β submitUserOperation β
ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β All-in-one β Convenience wrapper β sendTransaction / sendUserOperation β
ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Our PRD only has the all-in-one pattern. For a developer SDK, exposing the three-step flow is important for applications that need custom UI (e.g., showing the user what they're signing before they sign).
Session keys (Smart Sessions) have an intent-execution policy type at address 0xa09b47de6e510cbdc18b97e9239bedcb44fb4901. This is specifically designed for authorizing intent execution via session keys β relevant for our use case where
Turnkey-managed keys sign on behalf of users.
sendTransaction has a sponsored field that maps to Rhinestone's sponsor settings:
type Sponsorship = boolean | { gas: boolean, bridging: boolean, swaps: boolean }
From the Docs Agent (API Reference)
Never modify intentOp after receiving it from /intents/route β it contains an HMAC (serverSignature). Only append signatures. This is a critical integration constraint our PRD doesn't mention. The Newton gateway wrapping Rhinestone must pass
the intentOp through unmodified.
The quote must be refreshed immediately before signing β stale quotes cause expiry errors. Our SDK flow needs to handle this (no caching of quotes).
EOAs must handle Permit2 approvals manually; smart accounts get them via preClaimOps. Since our ERC-7579 accounts are smart accounts, Permit2 approvals are handled automatically β but we need to document this.
Updated Gap Summary (incorporating all three agents)
βββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ¬βββββββββββ¬ββββββββββββββββββ
β # β Gap β Severity β Source β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 1 β Prepare/sign/submit pipeline β SDK needs 3-step flow, not just sendTransaction β P0 β SDK agent β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 2 β UserOp path for same-chain β separate from intent path β P0 β SDK agent β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 3 β Account type selection (safe/nexus/kernel/eoa) with version configs β P0 β SDK agent β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 4 β SignatureMode.ERC1271 must be configured for Newton co-signer β P0 β Contracts agent β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 5 β Signature format: Newton's portion must be valid for SignatureCheckerLib ERC-1271 fallback β P0 β Contracts agent β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 6 β intentOp must not be modified after quote (HMAC) β gateway must pass through β P0 β Docs agent β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 7 β Quote refresh before signing β no caching β P1 β Docs agent β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 8 β waitForExecution() with preconfirmation support β P1 β SDK agent β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 9 β Token requirements (Permit2 approvals, ETH wraps) surfaced to developer β P1 β Docs agent β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 10 β Module installation: setup(), isDeployed(), getValidators() β P1 β SDK agent β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 11 β Intent Executor auto-installation during account creation β P1 β Contracts agent β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 12 β Session key support with intent-execution policy for Turnkey keys β P2 β SDK agent β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 13 β Gas sponsorship mapped to Rhinestone's sponsorSettings β P2 β Docs agent β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 14 β Intent Executor has permanent privileges β note in security section β P2 β Contracts agent β
βββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββΌββββββββββββββββββ€
β 15 β Atomic fill signer is centralized Rhinestone key β note in risk section β P2 β Contracts agent β
βββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ΄βββββββββββ΄ββββββββββββββββββ
Want me to draft the updated SDK interface that addresses all P0 and P1 gaps?