Skip to content

fxi: permissionless curator sdk - #565

Merged
gregorymel merged 23 commits into
feat/permissionless-curatorfrom
fix/permissionless-curator-e2e-tests
Nov 3, 2026
Merged

gregorymel merged 23 commits into
feat/permissionless-curatorfrom
fix/permissionless-curator-e2e-tests

Conversation

@gregorymel

Copy link
Copy Markdown
Collaborator

No description provided.

57Ark and others added 23 commits October 26, 2026 12:55
Add `CuratorSession.merge(builders)`, which compiles several market
builders into one GIP with a shared `preview()`, `build()` and
`simulate()`. Markets execute one after another and see the periphery
earlier markets deployed or registered; a market waits for the markets
providing periphery it requires, and an identical periphery call listed
by several markets runs once.

Expose what the permissionless interface needs: action definitions and
parameter types, `MarketActionOf<T>`, concrete adapter, IRM, loss policy
and rate keeper types, the RWA periphery planning and repair helpers,
compiled `txs` on market and configurator previews, and a
`checkActions: false` executor option for inspecting historical calls.
Midas and ERC-4626 adapter deploy types now list only supported versions.

Share the ETA check and executor options through the runtime and the
fork-simulation target through `simulateOnFork`, so single-scope
builders and the merged GIP do not duplicate them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`permissionless.configurators` reads the backend's index of market
configurators and what their TimeLocks did:

- `list({ chainId? })`: listed configurators with their market count
  (`GET /curators/list`)
- `details({ chainId, address })`: one configurator, listed or not
  (`GET /curators/:chainId/:address`)
- `markets({ chainId, address })`: the configurator's markets with the
  pool token and lifecycle (`GET /curators/:chainId/:address/markets`)
- `timelockEvents({ chainId, txHashes })`: the latest Queue / Execute /
  Cancel event per transaction hash, in chunks of 50
  (`GET /curators/timelock-transactions`)

and two reads off the chain, since neither is indexed:

- `roles({ chainId, address })`: admin, emergency admin, treasury and
  the ACL role holders
- `timelock({ chainId, address })`: the TimeLock with its `delay` and
  `GRACE_PERIOD`

The single-configurator read is `details`, as `get` is the base class's
protected reader.

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

A new market's preview has no `before`; `creationState` is its state right
after creation, before the later actions. `inputActions` keeps the merged
executable inputs before credit-suite references resolve, aligned with txs
and actionDiagnostics, so an editor can persist or revert a preview without
freezing predicted addresses.

Already shipped to the interface in the 18.2.0-curator.7 integration build.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings 18.2.0-next.10 (the version the interface's staging pins): the
/pfs/stores and /pfs/assets oracle routes, assets methods, testnets SDK and
treehouse integration.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
next dropped the shared `hexSchema` from api/schemas.ts; the configurators
namespace still validates TimeLock transaction hashes with it, so it keeps
its own copy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
parseMarketScopedAction and parseConfiguratorScopedAction threw the raw
ZodError of the action's params schema, which names the field but not the
action. They now throw GipScopeError with code INVALID_ACTION_PARAMS, the
action type in the message and every rejected param in invalidParams.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Previews are frozen data and report a failing check through blockedAt and
actionDiagnostics only, so every caller that refuses a blocked preview
rebuilt the GipActionError itself. assertNotBlocked(preview) throws the
error build() and simulate() throw, for market, configurator and merged
previews alike; MergedGip uses it too. The README also documents the
INVALID_ACTION_PARAMS error added in the previous commit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A merged GIP runs a periphery call listed by several markets once, and
dropped it from every later market's action list before executing it. The
drop leaked out of execution:

- inputActions, documented as safe to persist, lost the call, so saving an
  edited preview removed it from that market's draft;
- getGipDiagnostics saw the shortened list and reported the market's Degen
  NFT as unregistered, which re-adding the registration could not fix.

Each market preview now keeps its whole action list. Calls carry the
actionIndex of their action, and blockedAt and action diagnostics index
the whole list. The Degen NFT check counts a registration queued by any
market of the GIP, as the RWA periphery check already did.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Callers planned a Securitize market's Degen NFT registration and zapper
deployment and registration themselves, wiring the TimeLock, Bytecode
Repository and periphery registry reads by hand, and repaired drafts with
a second, separate path.

market.addRwaPeriphery() records that intent; it resolves when the builder
compiles, after the actions recorded before it, through the reads the
session already wires for its diagnostics (now shared in
rwaPeripheryLookups). It adds nothing for an ordinary underlying or a
complete setup and replaces a legacy author-derived zapper, so the same
call sets up a new market and repairs an existing draft.
completeRwaPeripheryActions is its stateless counterpart.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A suite recorded without an id could only be named by its predicted
address, which changes with its params, so editing a suite meant
predicting its new address and retargeting every action that configures it.

A market builder now gives such a suite an id derived from its name
(suffixed on collision) and, when it compiles, rewrites the actions that
name one of the market's suites by its predicted address to suite:<id>,
merging the list again so the two spellings of one action count once.
inputActions, the list callers persist, therefore follows its suites; the
payload does not change. With distinct ids, the same suite recorded twice
is two deployments and the second reports CREDIT_MANAGER_EXISTS instead
of merging silently.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Replacing or copying a credit suite took its predicted address: callers
predicted the replacement's or copy's address themselves, found the suite
among the actions by predicting every suite's address, and retargeted the
actions configuring it.

market.replaceCreditSuite(creditManager, suite),
market.copyCreditSuite(creditManager, name) and
market.removeAdapter(creditManager, target, options) record the edit and
resolve it when the builder compiles, once the market's suites are named
by reference: replacing keeps the suite's id, a copy gets <id>#<name>, and
none of them predicts an address. creditManager is what a preview shows,
the predicted address, or the suite's reference.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A market's suites are named by reference from the addresses a projection
predicts, but that projection ran the action checks: a suite a check
blocks, say until another market of a merged GIP registers its Degen
NFT, never got an address, so the actions configuring it kept naming it
by address and replacing or copying it failed.

Predicted addresses now come from a projection without checks, as do
the credit suite edits and a new market's pool for addRwaPeriphery.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gregorymel
gregorymel merged commit 0c4d0d3 into feat/permissionless-curator Nov 3, 2026
0 of 2 checks passed
@gregorymel
gregorymel deleted the fix/permissionless-curator-e2e-tests branch November 3, 2026 11:00
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.

3 participants