Skip to content

Feat/yield plan cards - #34

Open
Wieedze wants to merge 185 commits into
mainfrom
feat/yield-plan-cards
Open

Feat/yield plan cards#34
Wieedze wants to merge 185 commits into
mainfrom
feat/yield-plan-cards

Conversation

@Wieedze

@Wieedze Wieedze commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

Wieedze and others added 30 commits July 25, 2026 02:06
Fetches the Uniswap Labs Default token list (tokenlists.org schema), validates
and checksums entries, filters by chainId, and memoizes the ~630KB payload.
An uncovered chain (Sepolia / Base Sepolia are barely listed) returns an empty
array, not an error — callers fall back to the custom-address path. A failed
fetch clears the cache so the next call retries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a balance reader (one multicall, fail-soft per token) and a useSafeTokens
hook that intersects the Uniswap whitelist for the chain with the tokens the
Safe holds a non-zero balance of. This is the picker's data source: it excludes
both non-whitelisted shitcoins and zero-balance tokens. Empty on uncovered
chains, where the custom-address path takes over.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the USDC/custom toggle with a 'Safe tokens' picker driven by
useSafeTokens: only Uniswap-vetted tokens the Safe actually holds are offered,
each with its balance. Custom ERC-20 stays as the escape hatch (and the only
option on testnets, where the whitelist is empty). Token-derived values keep
their shape, so the sign flow is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add useSafeBalance (reads the chosen token's balance on demand for the custom
path; whitelist mode reuses the balance already read). Under the Charge box,
show 'Safe holds X TOKEN — covers N periods', or a warning when it can't cover
one period. Advisory only: it never blocks signing, since the Safe can be topped
up later.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the CreateDelegation changes onto CreateStream: the 'Safe tokens' picker
(Uniswap-vetted holdings) with custom ERC-20 as the escape hatch, and a solvency
readout under the Flow box ('Safe holds X — covers N months'), advisory only.
Token-derived values keep their shape; the stream sign flow is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'USDC — 1 held' read oddly; show 'USDC — 1 USDC' so the amount carries its unit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Migrate the app's hosted domain from ourglass.intuition.box to the new
apex domain hourglass.box across the repo:

- Safe App manifest link + public/manifest.json
- Publisher CORS allow-list (apex + *.hourglass.box preview wildcard) and its unit test
- Intuition providedBy URL, pitch/verify pages, env examples
- README, CLAUDE.md, docs and .mdx content

Intuition protocol references (intuition.box) left unchanged.
Add a History section to the README documenting the migration.
Clarify the token picker: label 'Available tokens' and a hint explaining the
list is whitelisted by Uniswap for security, with a pointer to Custom ERC-20 for
anything not listed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rebrand the user-facing product name from OurGlass to HourGlass across
all rendered surfaces:

- Logo wordmark (Safe App, website header, redeem console): Our → Hour
- Browser title + app/website manifest names
- App pages: Home module banner, Pitch, Verify copy
- Website: appName, docs (.mdx), blog, analytics dashboard/page text

Intentionally unchanged:
- GitHub repo URLs (intuition-box/OurGlass) — the repo name is unchanged
- On-chain identity (Intuition org atoms, self-deployed enforcer
  instances, DELEGATION_DOCUMENT_NAME) — pending the team's decision
- Code comments referencing the old design-system name
Deploys the remaining 34 Delegation Framework caveat enforcers to Ethereum
mainnet and all 37 to Base, under the existing CREATE2 salt, and records them.
Unmodified @metamask/delegation-framework bytecode — the only deviation is the
deployment address. All 74 carry verified source on Etherscan / Basescan, as do
both DeleGatorModuleFactory instances.

The addresses are a registry, not a routing change: getEnvironment() still
overrides only the three enforcers used since June, so nothing about what gets
signed changes and no in-flight delegation is affected. Because CREATE2 derives
the address from salt + bytecode alone, one shared HOURGLASS_ENFORCERS constant
serves both chains.

The salt string stays the literal "OURGLASS" despite the rebrand: it is an input
to every deployed address and cannot be renamed without orphaning all 74
deployments. New identifiers and prose say Hourglass; the salt, the OWS deployer
wallet name and the June ADR filename are historical facts and stay as they are.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Scans both chains in parallel and merges the results, tagging every charge with
its chainId. The enforcer addresses are identical on both chains, so one address
filter serves both.

Amounts are grouped by chain+token, never summed across chains: the same symbol
on two chains is a different asset, and an address-only key would silently merge
them. KNOWN_TOKENS is keyed the same way and gains Base USDC.

A chain whose RPC fails is named in the UI rather than contributing zero — a
partial total that looks complete is worse than a visible gap.

Scan windows are per chain and tuned by time rather than block count (~3 weeks
mainnet, ~6 weeks Base). Defaults to Tenderly's public gateways: publicnode
rejects archive log queries and mainnet.base.org caps ranges at 10k blocks.

The analytics page now links to the per-chain contract list in /docs/deployments.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Align the off-chain IPFS/Intuition layer with the HourGlass rebrand,
per the team's decision on the on-chain identity.

Renamed to hourglass:
- DELEGATION_DOCUMENT_NAME → "Hourglass delegation" (Intuition atom title)
- Subscription doc schema → hourglass/subscription-agreement@1 (was
  safe-subscriptions/agreement@1)
- Stream doc schema → hourglass/stream-agreement@1
- Pinata pin label → hourglass-delegation
- Code identifier: the `ourglass` field in addresses.ts and its accessors
  (environment.ts, discover.ts app + redeem)

Intentionally unchanged (team decision):
- Deployed enforcer instance addresses and the CREATE2 deploy salt stay
  "ourglass" — the contracts hold >$10k volume; keeping them preserves
  analytics attribution by emitter address
- Wallet name `ourglass-deployer` and spec/ourglass-enforcer-instances.md

Notes:
- None of the schema/name strings enter the delegation salt, so existing
  signatures are unaffected; only newly pinned documents carry the new
  labels. The Intuition indexer must recognize both old and new schema
  strings so already-published documents keep resolving.

Verified: app typecheck clean, website `next build` green.
feat: implement Uniswap pool discovery, position management, and secu…
   Rebrand OurGlass → HourGlass (domain, visible name, Intuition identifiers)
Resolves the rebrand collision. Upstream renamed the `ourglass` field to
`hourglass` and settled on HourGlass as the visible product name; this branch had
made the same field rename independently, so the two converge.

Resolution, per file:
- addresses.ts (both copies): kept this branch's registry — it is a superset of
  main's three-enforcer block, with the same three addresses unchanged.
- environment.ts: kept the added note that the registry is not a routing change.
- security.mdx: kept "routes" over main's "may route" — the override ships — and
  took main's HourGlass casing.
- analytics config + dashboard: kept the multi-chain versions, which supersede
  main's mainnet-only copy.

All prose in this branch's files is normalised to HourGlass to match main. Code
identifiers follow: HourGlassEnforcers, and the lowercase `hourglass` field as
main named it. The CREATE2 salt stays the literal "OURGLASS" — it is an input to
every deployed address.

Files outside this branch's scope were left at main's version, including
delegation-document.ts and docs/index.mdx.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
feat(web): full enforcer suite on mainnet + Base, and Base analytics
Add the verified enforcer research (HOURGLASS_STRATEGIES.md) and the build map
(STRATEGY_IMPLEMENTATION_PLAN.md): DCA/range/index rail via functionCall +
erc20BalanceChange, alongside the colleague's exactExecution yield rail. Register
the ERC20BalanceChangeEnforcer address (deterministic 0xcdF6…5437) in config — the
one prerequisite for the strategy rail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ss enforcer suite

The colleague's enforcer-suite-base PR registered the full audited enforcer set
(HourGlassEnforcers), including erc20BalanceChangeEnforcer. Our earlier
SHARED_ENFORCERS entry was redundant — remove it and rely on the merged suite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What the uniswap-ai skills actually offer (DCA, copy-trade, index, swap/liquidity
planners), which fit our BalanceChange rail (DCA/copy/index — spot swaps) vs which
don't (LP mint = colleague's Yield/exactExecution rail). Records the critical
CLASSIC-routing requirement (BEST_PRICE returns gasless UniswapX orders our caveat
can't bound) and the per-chain Universal Router target + execute() selector to
whitelist in the functionCall scope.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ordered plan grounded in the swap-integration skill, .claude/rules, and repo
state: config (Universal Router), buildDcaMandate service, storage, Strategy tab,
runtime agent runner, then a range variant. Explicit that we reuse only the
generic signing/redeem primitives — NOT the colleague's yield shape: DCA signs ONE
reusable delegation (not a 3-call exactExecution bundle) and the agent builds swap
calldata at run time via the Trading API (CLASSIC routing), the opposite of
yield-agent's fixed-calldata replay.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…easury Safe

The goal is Safes with big treasuries that can't automate on-chain: the multisig
approves one bounded mandate (quorum signs once), and the agent trades inside it
without re-quoruming owners or holding funds. Records the technical constraints
this imposes (verified): delegator is always the Safe module, signature is
multisig EIP-1271, the agent only triggers redeemDelegations (executes as the
Safe, never custodies), and non-custodial is non-negotiable — hence BalanceChange
over a funding-transfer rail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add decodeBalanceChangeTerms + findBalanceChangeCaveat to discover.ts, mirroring
the existing period/streaming decoders. Terms layout enforceDecrease(1) +
token(20) + recipient(20) + amount(32) = 73 bytes, matched against the
HourGlass erc20BalanceChangeEnforcer. First piece of making a DCA/range mandate
recognizable by the existing Intuition publish+discover flow (same system as
subscription/stream — one more caveat type).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire the erc20BalanceChange decoder into detailsFromDelegation and
tokenFromDelegation so the publisher no longer throws 'no known caveat' for a
strategy mandate — it now recognizes the DCA kind (per-swap cap) alongside
subscription/stream. Add the 'dca' DelegationKind with its label/enforcer and a
cap-phrased description. Fix the delegation-document test's stale OurGlass name
(rebrand fallout).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the strategyMandate branch to toStoredDelegation so a published DCA mandate is
returned (not dropped at return null) when an agent discovers delegations made to
it. New scopeType 'strategyMandate' with a strategyKind field (dca/range) so the
catalogue grows without new scopeTypes, plus capPerSwap/enforceDecrease meta. Only
src/ is extended (not the website standalone) — a strategy mandate is consumed by
the agent, not a human payee; documented as a deliberate scope choice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Register the per-chain Uniswap Universal Router (Ethereum + Base mainnet, our prod
targets) in config/uniswap.ts. This is the swap target a strategy mandate
whitelists in its functionCall scope (with the execute(bytes,bytes[],uint256)
selector), returned as swap.to by the Trading API under CLASSIC routing. Testnet
routers to be added once their addresses are confirmed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… enforcer

Override ERC20BalanceChangeEnforcer to the HourGlass instance in getEnvironment
(alongside period/stream/timestamp), so strategy mandates carry the HourGlass
address at create time — and match only that address in findBalanceChangeCaveat.
Write and read now agree on one address; a published DCA mandate is always
rediscoverable, with HourGlass emitter attribution. Recorded as ADR 0006.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wieedze and others added 26 commits July 26, 2026 01:42
feat: run a limit order from the Safe App, driven by a 0G agent
Starting the agent right after signing failed on "not found on Intuition yet": the
publish only ran on app open, so the operator had to reload before the agent could
discover its own mandate. Reloading also left the page, losing the run panel.

Start now runs the same finalize pass first. finalizePending is extracted out of the
mount effect into a callable function - same references-only poke, same EIP-1271
verification on the backend, same dedupe via the poked set - and the effect delegates
to it, so the on-open path is unchanged.

Publishing an already-published mandate is a no-op, so the extra pass costs nothing on
a restart. A genuine publish failure is swallowed rather than blocking: the agent not
finding the mandate is a clearer symptom than a poke error would be.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Firing the next signTypedMessage automatically (even after a delay) races
Safe's own confirmation screen — clicking its "Continue" could make the app
look like it dropped out of the flow, only for the next request to resurface
once Safe's UI settled (the reported workaround: wait it out).

Replaced the fixed-delay auto-advance with an explicit per-step pause: after
each signature, the flow stops at a "ready-for-next" state and shows its own
Continue button. Nothing fires until the operator clicks it, so there's no
timing to race — pacing is entirely human-driven now. Refactored handleDelegate
into prepare (build the plan + queue every delegation to sign, including the
optional compound mandate) + signStep(index) (sign one, then either pause or
assemble the finished plan on the last step).
feat(web): publish the mandate to Intuition when starting the agent
feat(brand): the mark becomes the die, in mono-mint
…w + register design-only

Scopes the "agent" (unattended bot) path as documented design rather than a
build/prove target this phase, per team decision — the manual (operator,
Safe App) path is what we're proving end-to-end instead. Records what's
already real code (yield-agent.ts, run-compound.ts) versus what's design-only
(withdrawDelegation.ts + run-withdraw.ts, Intuition register discovery), so
picking either up later doesn't require re-deriving the shape.

Also flags a discrepancy in IMPLEMENTATION_PLAN.md: it lists
references/execution-compound.md as [live]; that file doesn't exist yet.
…e agent

Starting still failed with a bare "exited with code 1". Two causes, both mine.

The publish error was swallowed. I wrote that catch thinking the agent not finding the
mandate would say more, which was wrong - it produced an unexplained exit code and hid
whether the pin had even happened. Publish failures now surface with their message.

Nothing waited for indexing. The poke returns as soon as the backend has written; the
graph indexes after. An agent started in that window finds nothing and exits, correctly,
looking broken.

The pin is deterministic (ADR 0005), so finalizePending now returns the atom id per
delegationHash instead of a count. The caller knows its own hash, so it can tell a
successful pin from a silent skip. Then it polls discoverIncomingDelegations - the same
path the runner uses, so "visible here" means "visible to the agent" - for up to two
minutes, and only then starts.

An absent hash is only treated as a failure on a first start: the poked set skips
already-published mandates, so a restart legitimately publishes nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(web): confirm the pin, then wait for indexing, before starting th…
…publishing

The previous guard read a skipped publish as a failed one. Click start twice and the
poked set correctly skips the already-published mandate, finalizePending returns
nothing for that hash, and the guard told the operator to reload an app that had in
fact published fine - the mandate was visible on Intuition the whole time.

The restart escape hatch keyed off agentSvc.run, which is null until an agent has
actually started. So it never covered the real case: published, indexed, never run.

The guard is the wrong question. What matters is not "did this call write" but "can
the agent find it", and the indexing poll already answers that on the exact path the
runner uses. Publish, then poll, then start.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(web): let discoverability decide, not whether this click did the …
… the mandates

Two halves of the same gap: a yield plan could be published to Intuition but never
read back.

getEnvironment overrode eight enforcers to the HourGlass instances but not
ExactExecutionEnforcer or RedeemerEnforcer, the two the yield rail uses. So yield
mandates were signed against the canonical SDK addresses - unattributable, and
invisible to a discovery side that matches on the HourGlass ones. Both are now
routed, per ADR 0006.

toStoredDelegation recognised periodTransfer, streaming and balanceChange, then
returned null. A yield mandate carries none of those, so it was published, traversed,
its IPFS document fetched - and dropped on the last line. A fourth branch decodes
exactExecution into targetAddress + calldataArgs, which is exactly what the runner
redeems with: the pinned calldata IS the mandate, there is no amount or token to
recover.

Discovery matches the canonical address as well as the HourGlass one. Mandates signed
before this change carry the canonical enforcer, and dropping it would strand them.

Verified: tsc clean; unit tests unchanged (stream-amounts had one pre-existing failure
on main, untouched here).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same shape as run-limit-order.ts, different mandate. Discovers the steps addressed to
the agent, orders them, redeems each as the Safe. No price to watch and no trigger:
every step pins its exact execution, so the agent decides nothing about target, amount
or recipient - it resubmits what the Safe already approved.

Ordering comes from the pinned calldata, not from the graph. Both approves then the
mint is the only sequence that works, and the selector in each step says which is
which (0x095ea7b3 / 0x88316456, verified against the plan builder). The graph does not
guarantee return order, so inferring it from that would be a coin flip.

Each step's on-chain state is read before submitting. limitedCalls(1) makes a replay
revert - that is the cap working - but a bare revert reads as breakage, and an agent
that retried a step it already landed would abandon a plan that had in fact succeeded.
callCounts on the LimitedCallsEnforcer (probed live on Base) distinguishes consumed
from ready, and disabledDelegations distinguishes revoked from both. A resumed run
finishes a half-done plan instead of restarting it.

Stops at the first revert rather than minting against a half-approved position, same
as scripts/yield-agent.ts.

Matches the canonical exactExecution enforcer as well as the HourGlass one, so plans
signed before the routing change stay redeemable.

Also adds src/lib/yieldPlanState.ts, the same state read for app-side use.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same two modes the Limit order tab has, and the same flow: provision an agent, sign,
fund it, start it. The manual agent path is gone - pasting an address you manage
yourself was the old way in, and "Run it for me" replaces it. Manual mode (the Safe
redeems its own plan) stays.

What differs is only the mandate. A yield plan is three single-use delegations pinned
to exact executions, so there is no price to watch and no trigger - discover, redeem
approve, approve, mint, done. Gas is 0.0015 ETH rather than 0.0006 because three
redeems instead of one.

The service and harness now route on hourglassStrategy: limitOrder keeps its
delegationHash, yield carries none because the plan is discovered whole. run-yield.ts
joins the per-run directory alongside the limit-order runner.

Publishing waits for the plan to be discoverable before starting, and for a plan that
means all three steps indexed - a partially indexed plan would have the agent stop
halfway and look broken.

Verified: tsc clean on both configs, vite build succeeds, unit tests unchanged (the
one stream-amounts failure predates this branch).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same affordance the Limit order tab has. The address is needed outside the app to fund
or inspect the agent, and reading it off a truncated Mono is not that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two failures from the same live run, both blocking a signed plan.

"no known caveat in delegation": the publisher derived a token from the delegation
and refused anything without one, because it needed the symbol and decimals for the
description. A yield step pins an exact execution - no token, no amount, no period -
so it was rejected at publish time, which is why only two of three steps reached
Intuition. The token is now optional; only failing to describe the delegation at all
is fatal. Adds kind 'yield' end to end so the pinned document says what the step
actually authorises.

The indexing wait published once, up front. The three steps are three separate Safe
messages and the tx-service assembles preparedSignature asynchronously, so on a
multisig the last one is routinely unfinalized when Start is clicked - skipped by that
single pass and never retried. Both rails now re-publish on every poll round.

The yield wait also reports progress (2/3 steps) and says how many are missing when it
gives up, instead of spinning silently on a plan that could never complete.

Verified: tsc clean on both configs, vite build succeeds, unit tests unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A live run reported: steps 1 and 2 succeeded, step 3 blocked by persistent
rate-limiting on mainnet.base.org. The mandate was fine - checked on-chain, the mint
still reads consumed:0, revoked:false - so a valid plan sat halfway done with both
approvals spent.

Reading each step's state before submitting costs two extra calls per step, which is
enough to get refused by Base's canonical public endpoint inside a single run. Defaults
to base-rpc.publicnode.com (which answered when mainnet.base.org would not, verified
while diagnosing this), still honouring RPC_URL.

More importantly, a failed read now retries with backoff rather than ending the run. A
node refusing to answer is not a broken step, and treating them alike is what turned a
network hiccup into a stalled deposit.

Recovery needs no resigning: the two consumed approvals are skipped on the next run and
the mint goes through.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The mirror of discoverIncomingDelegations: same graph, walked from the delegator's
side instead of the recipient's. Long noted as needed in FUTURE.md; the yield rail is
what made it blocking.

Nothing survives a reload today. Overview reads localStorage, and the Yield tab holds
the provisioned agent address in React state - so a refresh loses the way back to a
half-finished plan even though the mandate is still perfectly redeemable on-chain. The
graph knows, and every mandate names its own delegate, so the agent address comes back
with the plan and no client-side store is needed.

Traverses by the module address (delegation.delegator), not the Safe address: the
module is what signs and what the ontology records.

Verified against the live mainnet graph: eight granted delegations recovered from the
module, including the stalled yield plan's agent and this morning's filled limit order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… one

A reload lost the provisioned agent address, and with it any way back to a plan that
had stopped partway - even though its remaining steps were still perfectly redeemable.
The only way out was re-signing a plan that did not need it.

useSafeYieldPlans rebuilds the list from Intuition and the chain: discoverBySafe for
the mandates, callCounts for which steps are spent. Nothing is stored client-side, so
there is no local state to fall out of sync with what the Safe actually signed.

Plans group by delegate. An agent serves one plan at a time, and the mandates carry no
other link between their steps - each salt is keccak256 of its own calldata.

useAgentRun gains adopt(): the run directory is keyed on the address, so an agent
recovered from the graph can be addressed without the run id the app no longer has.

The panel sits outside the pool conditional - looking up your own plans should not
require picking a pool first - and shows only unfinished ones, with a Resume that skips
what already landed.

Verified: tsc clean, vite build succeeds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Read from the PositionManager, not from a store: a position minted by an agent, by the
Safe itself, or before this app existed all appear the same way, and a reload changes
nothing. Verified against the position the yield agent just minted (tokenId 5655792,
WETH/USDC 0.3%, full range).

One component, two surfaces - Overview gets an Investments section linking through to
Yield, Yield gets the detail under the deposit form - so they cannot disagree about
what the Safe holds.

Fees shown are tokensOwed, which the contract only credits on a collect or a liquidity
change: a freshly minted position reads zero while it is in fact already earning.
Deriving live fees from feeGrowthInside is deliberately left out of this pass; the
label says "none collected yet" rather than implying zero earnings.

Closed positions (zero liquidity) are filtered out.

Also realigns Home's local Page union with App's, which had drifted behind the routes
added since and blocked linking to them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two nav entries for what is one idea - sign a mandate, an agent redeems it, the chain
caps it - so they become a single tab keeping Yield's icon. Inside, two cards:
"Provide liquidity" and "Buy the dip".

Cards rather than tabs. Tabs suggest two views of one object; these are different
strategies answering different questions, and the operator already knows which they
came for, so the choice should be explicit rather than landing on whichever is first.
Same pattern as Create, which the operator has already met.

Yield.tsx and LimitOrder.tsx are untouched - they become children of the picker instead
of routes of their own. The old 'yield' and 'limit' routes still resolve, opening their
strategy directly, so Overview's Manage link keeps working without a picker detour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rview

Revoke and clear are separate actions because their consequences are. Revoke disables
all three steps in ONE Safe transaction - revoking two of three leaves an agent that
can still spend the rest, and three signing rounds to undo one decision is not a choice
worth offering. Clear only hides an abandoned attempt locally; the mandate stays signed
and redeemable, and the label says so.

Overview now separates what an agent runs on the treasury from what the treasury pays
out: "Agentic DeFi strategies" above, a rule, then "Operations" (subscriptions, streams,
orders) below.

Consumed steps are left in the revoke batch on purpose: disableDelegation on a spent
delegation is a no-op, and filtering them would need on-chain state the helper does not
have.

Verified: tsc clean, vite build succeeds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A yield step is an ordinary delegation with a hash of its own, so it gets the ordinary
treatment: SubscriptionDetail, and the revoke every other mandate on this Safe already
uses. No new detail page, no new revoke path.

Overview renders each plan's steps as cards under "Agentic DeFi strategies", reusing
SubCard and the module address the page already resolves. In the Yield tab each step is
a chip on its plan, marked when consumed, opening the same detail.

The batched plan revoke stays for undoing a whole plan at once; this adds the
per-delegation route alongside it.

Verified: tsc clean, vite build succeeds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ations

Three cards for one deposit buried the treasury's actual operations under their
implementation. A plan is now one folder card - open it and the three mandates are
there, each with Details and Revoke.

Revoke is offered only on steps not yet consumed: disableDelegation on a spent
delegation changes nothing, so showing it would suggest an effect it does not have.

The dialog warns plainly that revoking any single step ends the whole deposit - the
mint reverts without its approvals. The earlier wording said the others "stay valid",
which was true and misleading.

The Yield tab leads with what the Safe already holds: open positions, then the folded
plans, then a rule, then the deposit form. Overview shows the same folders under
Agentic DeFi strategies.

buildRevokePlanTxs stays in lib for revoking a plan wholesale; the UI now goes step by
step, which is what was asked for.

Verified: tsc clean, vite build succeeds, unit tests unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…sting styling

The card now says what the deposit is: pair, fee tier, both committed amounts and the
pool's APY. All of it decoded from the mint's pinned calldata, which is the deposit the
Safe signed - so it needs no separate record and cannot drift from what was authorised.
Everything else on the card goes, leaving "Open to inspect or revoke".

Styling was invented rather than reused, and it showed. Fixed against what already
exists: the card head mirrors SubCard/Payee (40px tile, same gap, same name weight) with
the address-derived tint every other card uses instead of a fixed green; Overview's
strategies heading now matches Operations (text-2xl/extrabold, not text-lg) so the two
sections read as siblings; the AgentDefi selector cards follow the same geometry.

The inner page still titled itself "Yield" after the tab was renamed - it is "Provide
liquidity" now.

APY is matched to the plan by pair and fee against useUniswapPools, and simply omitted
when the subgraph has no data, rather than showing a zero.

Verified: tsc clean, vite build succeeds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@host-intuition-box

host-intuition-box Bot commented Jul 26, 2026

Copy link
Copy Markdown

The preview deployment for OurGlass is ready. 🟢

Open Preview | Open Build Logs | Open Application Logs

Last updated at: 2026-07-26 04:35:52 CET

@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

@Wieedze is attempting to deploy a commit to the Maxime's projects Team on Vercel.

A member of the Team first needs to authorize it.

Wieedze and others added 2 commits July 26, 2026 05:32
The plan card already carries the pair, the fee tier, both amounts and the APY. The
strip above it repeated the pair and added a "Fees earned" line that reads zero on
every fresh position, so it said the same thing twice and one of them badly.

The section now hinges on the plans alone; useSafePositions stays for the Yield tab,
where the position detail belongs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same redundancy as Overview: the plan card carries the pair, the amounts and the yield,
so the strip above it only repeated them and added a fees line that reads zero on a
fresh position.

useSafePositions and the Positions component stay in the tree - nothing else reads them
today, but they are what a real fee view would be built on, and deleting them would
throw away the on-chain read that makes it possible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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