feat(morpho-sdk): add transaction plan#856
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7da4dd011c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Codex Review: Didn't find any major issues. Another round soon, please! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
71e53b9 to
93a73c1
Compare
0a07ac8 to
50c0fee
Compare
e63799a to
9d7fdef
Compare
9d7fdef to
faf9ffc
Compare
bec7c22 to
962e29d
Compare
0321d43 to
91d57a6
Compare
Adapt the latest Midnight flows and fork tests to TransactionPlan.
b5e42cc to
aa08791
Compare
…an-v1 # Conflicts: # .changeset/quiet-midnight-flows.md # packages/morpho-sdk/src/entities/blue/blue.refinance.test.ts # packages/morpho-sdk/src/entities/blue/blue.ts # packages/morpho-sdk/src/entities/midnight/midnight.test.ts # packages/morpho-sdk/src/entities/vaultV1/vaultV1.ts # packages/morpho-sdk/src/entities/vaultV2/vaultV2.ts # packages/morpho-sdk/test/actions/blue/reallocations.test.ts # packages/morpho-sdk/test/actions/wrap.test.ts
| }, | ||
| }, | ||
| }, | ||
| } as unknown as PermitRequirementSignature, |
There was a problem hiding this comment.
can we get rid of this casting?
There was a problem hiding this comment.
Not without making the test less explicit: expiration is required by PermitRequirementSignature, and this test intentionally constructs malformed runtime input to exercise the defensive guard. We could start from a valid typed signature and use Reflect.deleteProperty to avoid the cast, but that only hides the same type violation behind mutation. I would keep the narrow test-only cast; otherwise we should remove the runtime guard and this test if we consider the state unreachable.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6180966891
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a1a14cfcb
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Codex Review: Didn't find any major issues. Another round soon, please! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
…an-v1 # Conflicts: # packages/morpho-sdk/README.md
Rubilmax
left a comment
There was a problem hiding this comment.
Parallel PR Review (Codex)
Reviewed commit: 5a5c40b
| Severity | Count |
|---|---|
| Critical | 1 |
| High | 3 |
| Medium | 3 |
| Low | 2 |
This is an automated parallel review.
|
|
||
| ```ts | ||
| export type CallRequirementAction = | ||
| export type TxRequirementAction = |
There was a problem hiding this comment.
Medium: While this section is being updated for TxRequirement, the now-Accepted TIB's preceding “Requirement aliases” block still specifies the removed SignatureRequirementAction / RequirementSignatureArgs unions and obsolete two-parameter Requirement<TAction, TArgs> / RequirementSignature<TAction, TArgs> API. The implemented v6 surface correlates signatures through Requirement<TSignature> and the RequirementSignature discriminated union, so those snippets no longer compile. Rewrite that block to match the exported types.
| // then calls buildTx to get the final transaction: | ||
| const tx = buildTx([permitSignature]); | ||
| // Or convert the executable transactions for an EIP-5792 wallet batch. | ||
| await walletClient.sendCalls({ calls: executable.calls }); |
There was a problem hiding this comment.
High: The copy-pasteable usage block first submits every executable.txSteps transaction individually and then immediately submits the same calls again with sendCalls. Although the comment says “Or,” both statements execute sequentially, so copying the example can double-submit the primary financial action. Put the sequential and EIP-5792 paths in mutually exclusive branches or separate code blocks.
| export * from "./actions/index.js"; | ||
| export * from "./client/index.js"; | ||
| export * from "./helpers/index.js"; | ||
| export * from "./transactionPlan/index.js"; |
There was a problem hiding this comment.
High: This public migration replaces the entity { buildTx, getRequirements } API with TransactionPlan, but the canonical docs/jsdoc-style.md exemplar still destructures buildTx from market.borrow(...) and calls it. That example no longer compiles and is explicitly the template contributors are told to copy. Update the canonical exemplar to use plan.prepare(), collect signature requests, and call prepared.build(...).
| * @returns A prepared plan whose request types and primary action match this plan. | ||
| * @example | ||
| * ```ts | ||
| * const prepared = await vault.deposit(params).prepare({ |
There was a problem hiding this comment.
Low: The new public prepare() method's @example is only a fragment using undefined vault and params; it has no imports/client setup and does not show the return type. This fails the runnable-recipe requirements in docs/jsdoc-style.md. Replace it with a self-contained example using named addresses/constants, client setup, one call, and the expected PreparedTransactionPlan shape.
| * const calls = prepared.calls; | ||
| * ``` | ||
| */ | ||
| get calls(): readonly TransactionPlanBatchCall[] { |
There was a problem hiding this comment.
Medium: The new plan classes implement class-specific derivation directly instead of delegating to pure object-compatible utilities: calls, flowKind, hasIntent, and findIntent live on PreparedTransactionPlan, and the calls mapping is duplicated on ExecutableTransactionPlan. This violates the AGENTS.md §1 class/API convention and creates multiple sources of truth. Move these derivations into pure TransactionPlanUtils functions over readonly plan shapes and delegate the class methods/getters to them.
| ): ExecutableTransactionPlan<TPrimaryAction, TRequest> { | ||
| const expected = this.signatureRequests.length; | ||
| const received = signatures?.length ?? 0; | ||
| if (received !== expected) { |
There was a problem hiding this comment.
High: build() validates only the number of supplied signatures, not that each signature matches the prepared request. Combined Blue plans accept both permit and authorization types, so if prepare() requests only authorization but the caller supplies one permit (or vice versa), the count passes and the primary builder emits a bundle without the actually required setAuthorizationWithSig or permit action, causing the Morpho operation to revert. Bind each signature to the corresponding prepared request/action type and add a same-count wrong-kind regression test.
| * for (const step of executable.txSteps) { | ||
| * await walletClient.sendTransaction(step.tx); | ||
| * } | ||
| * await walletClient.sendCalls({ calls: executable.calls }); |
There was a problem hiding this comment.
Critical: This public executable-plan example submits every transaction twice: first individually via sendTransaction, then again as the full sendCalls batch. Copying it can duplicate deposits, borrows, or other fund-moving primary actions. Present these as mutually exclusive execution alternatives, as separate branches or code blocks.
| * }); | ||
| * ``` | ||
| */ | ||
| constructor( |
There was a problem hiding this comment.
Medium: The exported constructor accepts primaryStep and txSteps independently without ensuring the same primary step is last in txSteps. Consumers can construct an instance whose primaryStep / primaryTx disagree with txSteps / calls, breaking the advertised invariant. Keep construction internal/private or validate and centralize it behind a static factory.
| public readonly received: number, | ||
| ) { | ||
| super( | ||
| `Expected exactly ${expected} transaction-plan signatures but received ${received}. Pass one signature for each prepared signature request and no others.`, |
There was a problem hiding this comment.
Low: The new error message interpolates expected and received without quotes, contrary to AGENTS.md §3's required quoted formatting for interpolated values. Format the values as expected "${expected}", got "${received}" (while retaining the actionable guidance).
Adds a first-class
TransactionPlanAPI for app-side transaction review and execution flows, stacked onhermes/marketv2-action-flows-implementation.Summary:
TransactionPlan,PreparedTransactionPlan, andExecutableTransactionPlanundersrc/transactionPlan/.TransactionPlaninstances while preservingbuildTx(...)/getRequirements(...)destructuring.signatureRequestsandcallRequests.callRequestscontains viem-compatible calls in execution order and includes the final intent call when it can be previewed. After signatures,prepared.build(signatures).callRequestsalways includes the final intent call last.tokenApproval,operatorAuthorization,contractCall,midnightOfferRootSignature,finalTransaction), request-kind flow values (single_call,call_requests,signature_requests,mixed_requests), sequentialsignAll(), ordered viem-compatible calls, and missing-signature validation.prepared.finalTxbefore it is available throws a typed SDK error.Interface rationale:
SignatureRequest[]plus orderedCallRequest[]. PRsmorpho-org/morpho-apps#3567and#3568had to adapt the SDK output into that shape manually, including appending the final call after prerequisite calls.finalTxis still exposed as a convenience/safety handle for consumers that need the semantic final action (action.type, built tx metadata, guard when not previewable), but it is no longer the main way to submit calls. The main submission surface iscallRequests.Vaults-app transaction-context sketch:
Markets-app action-flow sketch:
Verification:
pnpm --filter @morpho-org/morpho-sdk buildpnpm lint:ciMAINNET_RPC_URL="https://rpc.morpho.dev/cache/evm/1?secret=$RPC_SECRET" pnpm exec vitest run --project morpho-sdk packages/morpho-sdk/src/transactionPlan/TransactionPlan.test.ts packages/morpho-sdk/src/entities/vaultV1/vaultV1.test.ts packages/morpho-sdk/src/entities/vaultV2/vaultV2.test.ts packages/morpho-sdk/src/entities/blue/blue.test.ts packages/morpho-sdk/src/entities/midnight/midnight.test.tsRequested by: <@U03L0SC1JUR>