Skip to content

Instantly share code, notes, and snippets.

@denniswon
Created March 24, 2026 15:30
Show Gist options
  • Select an option

  • Save denniswon/fffb80a5fb4647abcfbdf1ecbe3dd7ad to your computer and use it in GitHub Desktop.

Select an option

Save denniswon/fffb80a5fb4647abcfbdf1ecbe3dd7ad to your computer and use it in GitHub Desktop.
Newton CAB
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?
@denniswon
Copy link
Copy Markdown
Author

Here's the full SDK interface review. I'll evaluate each method against Newton's actual on-chain architecture and the Rhinestone integration model.


SDK Interface Review

  1. NewtonAccount.init({ policyClientAddress, chains })

Purpose: Developer-level SDK initialization (one-time).

Issues:

  • Missing gatewayUrl — The SDK needs to know which Newton gateway to talk to. Without it, it's unclear how createAccount, transfer, etc. reach Newton's RPC.
  • Missing apiKey — Newton gateway authenticates RPC calls via API keys (existing RpcRead/RpcWrite tiers). The developer needs to pass their API key here.
  • chains is redundant — The gateway already knows which chains it serves via MultiChainConfig. The SDK shouldn't need a static chain list; it should query the gateway for supported chains. If it's a restriction on which chains this developer
    uses, it belongs on the PolicyClient config, not the SDK init.
  • policyClientAddress needs to be per-chain — The same developer may have different PolicyClient addresses on different chains (unless CREATE2 deterministic deploy is used). Should be policyClients: { base: '0x...', arbitrum: '0x...' } or the
    SDK should resolve it from PolicyClientRegistry by developer address.

Verdict: Needs gatewayUrl and apiKey. Consider making chains optional (auto-discover from gateway). policyClientAddress should support per-chain mapping.


  1. newton.createAccount({ owner, chains })

Purpose: Deploy a new ERC-7579 smart account with Newton modules installed.

Issues:

  • Who pays for deployment gas? — Not specified. Is this a UserOp via a bundler with paymaster? Direct EOA transaction? The developer's backend? This matters for UX.
  • owner type is ambiguous — Is this an ethers.Signer, a viem.WalletClient, an address string, or a passkey credential? The SDK needs to support multiple owner types (ECDSA key, passkey, Turnkey-managed key).
  • What gets installed? — The PRD says 2-of-2 ownable validator + intent executor + policy client fallback module + audit hook. The SDK should return some confirmation of what was installed.
  • Return type not shown — Should return the account address, deployment tx hash, and chain deployment status per chain.

Verdict: Needs gas payment strategy, clearer owner type, and return type definition.


  1. newton.upgradeEOA({ existingAddress, chains })

Purpose: ERC-7702 delegation for existing EOA wallets.

Issues:

  • Requires the user's signature — ERC-7702 delegation requires the EOA owner to sign an authorization tuple (chain_id, delegate_address, nonce). But the method only takes existingAddress — how does it get the user to sign? It needs a
    signer/wallet parameter.
  • Same chains redundancy as createAccount.
  • Phase 1 only — The PRD says this is Phase 1, but the example shows it alongside createAccount without noting this.

Verdict: Needs a signer parameter. Should be clearly gated behind a phase flag or separate import.


  1. account.convert({ amount, token, fromChain })

Purpose: Cross-chain asset movement (bridge + optional swap).

Issues:

  • Missing toChain — Where are the converted assets going? If it's "into the unified balance," say so explicitly. If the user wants to move USDC from Ethereum to Base specifically, they need a toChain param.
  • amount as string — Fine for UX but the SDK needs to handle decimals. Is '5000.00' 5000 USDC (6 decimals) or 5000 wei? Should document that amounts are in human-readable units, not wei.
  • Who signs the bridge transaction? — This goes through Rhinestone's solver market. The user needs to sign the Permit2 + Mandate EIP-712. But account.convert() implies a simple async call. The signing flow needs to be explicit or handled
    internally.
  • No slippage/deadline params — Cross-chain bridges have slippage. No way to set max slippage, deadline, or preferred settlement layer (Across vs Relay).

Verdict: Needs toChain (or explicit "unified balance" semantics), amount denomination docs, and slippage/deadline params.


  1. account.balance()

Purpose: Unified balance across chains.

Issues:

  • Where does this data come from? — Querying balances across 10 chains in real-time is expensive. Does the SDK query each chain's RPC individually, use Rhinestone's /portfolio endpoint, or use Newton's gateway indexer? This affects latency and
    accuracy.
  • No token filter — Should support account.balance('USDC') or account.balance({ tokens: ['USDC', 'ETH'] }).
  • No staleness indicator — Cross-chain balances can be stale. Should include lastUpdated per chain.

Verdict: Needs data source clarity, token filter, and staleness metadata.


  1. account.transfer({ to, amount, token, chain, data, memo })

Purpose: Policy-gated transfer (potentially cross-chain).

Issues:

  • Conflation of transfer and contract call — data: '0x' makes this a generic execution, not a "transfer." If data is non-empty, this is an arbitrary contract interaction. The method name transfer is misleading for that use case. David He's
    suggestion of executeIntent makes more sense as the underlying method, with transfer as sugar for the data: '0x' case.
  • chain is optional but semantics are unclear — If omitted, does it auto-route? What if the user has USDC on 3 chains — which one does it source from? "Cheapest route" needs to be explicit.
  • memo is off-chain only — Memos don't go on-chain in standard ERC-20 transfers. Is this stored in Newton's indexer? If so, document that.
  • Missing value for ETH transfers — The params only support ERC-20 tokens. For native ETH transfers, there's no value field separate from amount.
  • No return type for attestation — The comment says { txHash, attestationId, chain, status } but attestationId should be clarified — is this the Newton task ID? The on-chain task index? The BLS certificate hash?

Verdict: Consider exposing executeIntent as the general method with transfer as sugar. Clarify auto-routing, memo storage, ETH support, and attestation return type.


  1. account.submitCredential({ type, issuer, credential })

Purpose: Submit encrypted verifiable credential for KYC.

Issues:

  • Encryption is implicit — The comment says "HPKE-encrypted to Newton's public key" but the API doesn't show the encryption step. Does the SDK encrypt automatically? If so, it needs the gateway's HPKE public key (from
    newt_getPrivacyPublicKey).
  • Missing chain_id — Newton's newt_uploadEncryptedData requires chain_id for AAD binding. Without it, the credential isn't bound to a chain context.
  • Missing dual-signature auth — Privacy data uploads require dual Ed25519 signatures (user + app). Not reflected in this API.
  • type is vague — Is this a Newton identity domain (IdentityDomain::Kyc)? A VC type URI? Should align with Newton's identity domain registry.

Verdict: Needs explicit encryption handling, chain_id, dual-signature auth, and type alignment with Newton's identity domains.


  1. account.complianceStatus()

Purpose: Check account's compliance status.

Issues:

  • Where does this read from? — Is this on-chain (IdentityRegistry linkage check) or off-chain (Newton gateway query)? The answer affects latency and trust model.
  • sanctionsCleared: true is point-in-time — Sanctions status changes. This should return lastChecked timestamp and whether the check was live or cached.
  • No per-chain breakdown — Compliance status might differ per chain (different jurisdictions).

Verdict: Needs data source, timestamp, and per-chain granularity.


  1. account.transactions({ period }) and account.exportComplianceReport({ format, period })

Purpose: Audit trail and compliance reporting.

Issues:

  • Data source — This requires Newton's indexer DB (the PostgreSQL gateway_tasks table with optimistic writes). Not available without the indexer running.
  • period: 'last-30-days' — Should support ISO date ranges, not just relative strings. { from: '2026-01-01', to: '2026-03-31' } is more precise.
  • exportComplianceReport with format: 'pdf' — PDF generation is a heavyweight operation. Who renders it? The gateway? The SDK? A separate service? This is a Phase 2+ feature at best — shouldn't be in the Phase 0 SDK example without noting
    that.

Verdict: PDF export should be deferred. Transaction queries need ISO date ranges and pagination.


Missing Methods

┌─────────────────────────────────────┬───────────────────────────────────────────────────────────────────┐
│ Method │ Why It's Needed │
├─────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤
│ account.address / account.addresses │ Get the account address(es) per chain │
├─────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤
│ account.isDeployed(chain?) │ Check if account is deployed on a given chain │
├─────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤
│ newton.getPolicy() │ Read current policy config from the shared PolicyClient │
├─────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤
│ newton.setPolicy(config) │ Update policy on the shared PolicyClient (developer admin action) │
├─────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤
│ account.attestations({ taskId? }) │ Query attestation proofs for compliance evidence │
├─────────────────────────────────────┼───────────────────────────────────────────────────────────────────┤
│ account.revokeEOA() │ Revoke ERC-7702 delegation (set delegate to 0x0) │
└─────────────────────────────────────┴───────────────────────────────────────────────────────────────────┘


Summary

┌──────────────────────────┬─────────────────────────────────────────────────────────────────┬──────────┐
│ Method │ Issues │ Severity │
├──────────────────────────┼─────────────────────────────────────────────────────────────────┼──────────┤
│ init() │ Missing gatewayUrl, apiKey; policyClientAddress needs per-chain │ High │
├──────────────────────────┼─────────────────────────────────────────────────────────────────┼──────────┤
│ createAccount() │ Gas payment unclear, owner type ambiguous, no return type │ Medium │
├──────────────────────────┼─────────────────────────────────────────────────────────────────┼──────────┤
│ upgradeEOA() │ Missing signer param for authorization signing │ High │
├──────────────────────────┼─────────────────────────────────────────────────────────────────┼──────────┤
│ convert() │ Missing toChain, slippage, amount denomination │ Medium │
├──────────────────────────┼─────────────────────────────────────────────────────────────────┼──────────┤
│ balance() │ Missing data source, token filter, staleness │ Low │
├──────────────────────────┼─────────────────────────────────────────────────────────────────┼──────────┤
│ transfer() │ Conflates transfer + execution, unclear auto-routing │ Medium │
├──────────────────────────┼─────────────────────────────────────────────────────────────────┼──────────┤
│ submitCredential() │ Missing encryption, chain_id, dual-sig │ High │
├──────────────────────────┼─────────────────────────────────────────────────────────────────┼──────────┤
│ complianceStatus() │ Missing data source, timestamp │ Low │
├──────────────────────────┼─────────────────────────────────────────────────────────────────┼──────────┤
│ transactions() │ Needs ISO dates, pagination │ Low │
├──────────────────────────┼─────────────────────────────────────────────────────────────────┼──────────┤
│ exportComplianceReport() │ PDF generation is Phase 2+ │ Low │
└──────────────────────────┴─────────────────────────────────────────────────────────────────┴──────────┘

The highest-priority fixes are init() (missing gateway connection params), upgradeEOA() (missing signer), and submitCredential() (missing encryption flow that aligns with Newton's existing privacy API).

Want me to draft an updated SDK interface that addresses these issues?

@denniswon
Copy link
Copy Markdown
Author

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:

  1. Rhinestone separates prepare/sign/submit — our PRD merges them into single calls like transfer(). Rhinestone's prepareTransaction → signTransaction → submitTransaction three-step flow is deliberate: it enables the client-side signing step
    that EIP-712/Permit2 requires.
  2. Rhinestone has SignatureMode constants — including SIG_MODE_ERC1271 — which is exactly how Newton's Policy Client co-signer would integrate. The infrastructure already exists.
  3. Account types are first-class — Safe, Nexus, Kernel, Startale, Passport, EOA — each with version configs. Our PRD treats this as an implementation detail.
    ─────────────────────────────────────────────────

PRD Gap Analysis: Newton Account SDK vs Rhinestone Reality

Critical Gaps

  1. Missing the Prepare/Sign/Submit Pipeline

The Rhinestone SDK uses a three-step pattern: prepareTransaction() → signTransaction() → submitTransaction(). Our PRD's account.transfer() merges all three, which won't work because:

  • The user must sign EIP-712 typed data on the client side (Turnkey or wallet)
  • The Newton attestation (step 2 of 2-of-2) requires a completed BLS evaluation, which is server-side
  • These two signatures must be combined before submission

Our SDK needs either:

  • A similar three-step flow, or
  • An opinionated sendTransaction() that handles the orchestration internally (Rhinestone has this too — their sendTransaction wraps prepare+sign+submit)
  1. Account Type Configuration is Missing

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.

  1. Module Installation Flow Not Addressed

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:

  • isDeployed(chain) — check if account exists on a chain
  • setup(chain) — install missing modules on existing accounts
  • getValidators(chain) / getExecutors(chain) — introspect installed modules
  1. Intent Executor Module Must Be Installed

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?

  1. Permit2 Flow is Completely Missing

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.

  1. Token Requirements / Approval Step Missing

Rhinestone's /intents/route response includes tokenRequirements — a per-chain map of approvals and wraps needed before the intent can be signed. Types:

  • ApprovalRequired — ERC-20 approve to Permit2 contract
  • WrapRequired — ETH → WETH conversion

Our PRD skips this entirely. The SDK needs to handle or surface these requirements.

  1. SignatureMode for EIP-1271 Co-Signing

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.

  1. setupOps for Smart Account Initialization

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

  1. Portfolio Endpoint Mismatch

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).

  1. Gas Sponsorship Not Mapped to Newton

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.

  1. EIP-7702 Delegation Flow

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.

  1. Missing waitForExecution()

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.

  1. destinationExecutions for Arbitrary Calldata

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

  1. The PRD's Ownable Validator 2-of-2 Needs Refinement

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.

  1. Same-Chain vs Cross-Chain Are Different Rhinestone Paths

Looking at the Rhinestone SDK structure:

  • execution/utils.ts — cross-chain via orchestrator
  • execution/singleChainOps.ts — same-chain execution (UserOps via bundler)

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:

  • For a 2-of-2 setup with [userEOA, newtonPolicyClient], the validator concatenates 2 signatures
  • For the user's ECDSA signature: standard ecrecover
  • For Newton's contract signature: calls isValidSignature(hash, signature) on the Policy Client contract
  • The 130-byte concatenated format matters — Newton's "signature" portion must be ABI-encoded (Task, TaskResponse, SignatureData) that the Policy Client's isValidSignature can decode

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment