cetus-yield-agent: extract range strategy, harden tx flow, migrate to SDK v2#33
Open
RandyPen wants to merge 5 commits into
Open
cetus-yield-agent: extract range strategy, harden tx flow, migrate to SDK v2#33RandyPen wants to merge 5 commits into
RandyPen wants to merge 5 commits into
Conversation
…ion, BigInt price math Harden the standalone template's swap path and document the 2PC signing boundary so the project is more usable as a third-party reference. - README: add "Anatomy of send-tx" section explaining the agent / waap-cli / WaaP-server / fullnode data flow, why the subprocess boundary keeps key material out of the agent address space, and a pseudocode equivalent for developers integrating their own 2PC backend. - agent.ts.tpl: replace the string-sniffing guessDecimals with a Map-backed cache populated via sui.getCoinMetadata. Decimals are preloaded in main() for TARGET and QUOTE; getPoolState additionally warms whichever coins the pool itself uses. - agent.ts.tpl: after waap-cli send-tx, call sui.waitForTransaction with showEffects + showBalanceChanges. On failure, throw so the tick handler records rebalance_failed instead of pretending the swap succeeded. On success, extract the agent's positive balance change for the output coin and log both the raw and human-decimals amount received. - agent.ts.tpl: rewrite calculatePrice so the (sqrtPriceX64)^2 / 2^128 reduction stays in BigInt until a scaled Number conversion at the end, avoiding precision loss for pools where the intermediate exceeds 2^53. Replace the brittle .includes() coin-type comparison with a normalized full-string equality check (addresses padded to 0x + 64 hex chars). Signed-off-by: Randy Pen <pzm16@tsinghua.org.cn>
Pulls the volatility-adaptive half-width calculation and the open/reopen sizing rule into a pure, side-effect-free strategy module exposing a single decideRange() entry point. Both the initial open and the rebalance reopen now consume one RangeDecision (tickLower, tickUpper, sizingFraction, reason, signals) instead of duplicating the tick math and bookkeeping. This is the place to plug in new range signals (fee tier, depth, yield-scan APY delta, directional bias) without touching the IO loop, and makes the math unit-testable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Randy Pen <pzm16@tsinghua.org.cn>
…e on startup Two related safety changes for the active-mode rebalance flow: 1. waitForFinality(): after every signAndSendTx, block on sui.waitForTransaction and verify effects.status === 'success'. The previous flow replaced this with a blind setTimeout(5000) after the remove tx, which let the agent proceed to the reopen step even when the remove had silently reverted (or had not finalized at all, leaving the reopen to build against pre-remove balances). On any non-success status the rebalance now aborts and logs the failure for triage. 2. Rebalance intent file: written before each on-chain step (remove_pending → open_pending → cleared on success) and reconciled at startup. The dangerous case is a crash after remove but before open succeeds — without intent tracking the next cycle sees "no positions" and triggers the initial-open path, depositing (full post-remove balance × open fraction) instead of the planned sizing. The reconciler refuses to auto-recover from open_pending and exits non-zero with a Matrix alert so an operator inspects the file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Randy Pen <pzm16@tsinghua.org.cn>
When DRY_RUN=true, signAndSendTx routes through the Sui gRPC client's dryRunTransaction endpoint instead of submitting via waap-cli. Effects, gas, and balance changes are logged so an operator can validate a planned remove or open against current chain state before flipping back to live submission. The submission returns a DRY_RUN sentinel that waitForFinality recognizes, so the rest of the flow proceeds end-to-end in simulation. Method-name note: this calls core.dryRunTransaction (v1.x SDK name). The v2 SDK renamed it to core.simulateTransaction — same wire-level gRPC service. The agent stays on @mysten/sui ^1.14.x because @cetusprotocol/cetus-sui-clmm-sdk@^5.4.0 still imports the legacy SuiClient export that v2 dropped. Rename once Cetus ships v2-compatible. Also documents DRY_RUN, INTENT_FILE, SUI_GRPC_URL, and MIN_SAMPLES_FOR_ADAPTIVE in dot-env.example and the activity manifest. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Randy Pen <pzm16@tsinghua.org.cn>
…/sui-clmm-sdk v1.4
Drops the legacy @cetusprotocol/cetus-sui-clmm-sdk@5.4 (which still pinned
the agent to @mysten/sui v1 because it imported the removed SuiClient
symbol) and switches to the v2-native @cetusprotocol/sui-clmm-sdk@^1.4.5
plus @cetusprotocol/common-sdk@^1.3.7.
Concrete changes:
- One client: sdk.FullClient is a SuiGrpcClient & ExtendedSuiClient, so
getBalance, getPositionList, waitForTransaction, and simulateTransaction
all flow through a single gRPC connection. No more parallel JSON-RPC
SuiClient + separate gRPC client for dry-run.
- Position discovery via sdk.Position.getPositionList(owner, [POOL_ID]) —
server-side pool filter replaces the StructType-filtered getOwnedObjects
scan + manual fields.bits decoding for tick indices.
- removeLiquidityPayload now passes the pool's actual rewarder_coin_types
so rewards are collected on remove (was hardcoded to [], which left
rewards on the table for pools with active emissions).
- createAddLiquidityFixTokenPayload replaces the createAddLiquidityPayload
+ `as unknown as ...` typecast escape hatch. is_open and pos_id are
documented public params in v2, no longer undeclared runtime hacks.
Detects whether USDC is coin_a or coin_b on the pool instead of
assuming, so the agent works on any USDC pair, not just SUI/USDC.
- Dry-run uses chain.simulateTransaction({ transaction, checksEnabled,
include: { effects, balanceChanges, events } }) — the proper v2 gRPC
method, accepting the Transaction object directly instead of base64
bytes. waitForTransaction migrated to the same v2 gRPC shape.
- SUI_RPC and SUI_GRPC_URL env vars collapse to one configurable gRPC
endpoint; both names accepted for back-compat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Randy Pen <pzm16@tsinghua.org.cn>
|
@RandyPen is attempting to deploy a commit to the Holonym Team on Vercel. A member of the Team first needs to authorize it. |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four focused commits that improve the cetus-yield-agent template along two
axes: (1) safer rebalance flow on the live agent, and (2) bring the codebase
forward to the latest @mysten/sui and Cetus CLMM SDKs so it reads cleanly
as a reference for Sui developers.
Summary
Range decisions live in one place. A new
strategy.tsmodule exposes apure
decideRange({pool, tickHistory, balances, trigger, config})thatreturns
{tickLower, tickUpper, sizingFraction, reason, signals}. Both theinitial open and the rebalance reopen consume the same decision, so future
range signals (fee tier, depth, yield-scan APY delta, directional bias)
plug into one well-typed function instead of being threaded through the IO
loop. The function is side-effect-free and unit-testable.
Transaction finality is verified. A new
waitForFinality(digest, label)wraps the gRPC client's
waitForTransactionand checkseffects.status.successbefore the flow proceeds. The old code waitedsetTimeout(5000)after the remove tx and barreled into the reopen step —if the remove silently reverted (or just hadn't finalized yet), the agent
would build the reopen against pre-remove balances. On any non-success
status the rebalance now aborts and logs the failure for triage.
Crash recovery refuses to compound a partial rebalance. A rebalance
intent file (
remove_pending → open_pending → cleared) is written beforeeach on-chain step. On startup,
reconcileIntent()checks the intentagainst live position state. The dangerous case — a crash between remove
and open — used to look like "no positions, time to do a fresh open" and
would deploy
(full post-remove balance × open fraction)instead of theplanned sizing. The reconciler now refuses to auto-recover from
open_pendingwith no live position, exits non-zero, and surfaces aMatrix alert so an operator inspects the intent file by hand.
DRY_RUN simulates via the gRPC client. Setting
DRY_RUN=trueroutesevery submission through
chain.simulateTransaction({ transaction, include: { effects, balanceChanges, events } })instead ofwaap-cli send-tx. Thehelper logs effects, gas, and balance changes. A sentinel digest threads
through
waitForFinalityso the whole flow runs end-to-end in simulation,which lets an operator validate a planned action against current chain
state before flipping
DRY_RUN=falsefor live submission.SDK migration: latest @mysten/sui + Cetus CLMM v2 SDK. Drops the
legacy
@cetusprotocol/cetus-sui-clmm-sdk@5.4(which kept the agentpinned to
@mysten/suiv1 because it imported the removedSuiClientsymbol). Now uses:
@mysten/sui ^2.17.0@cetusprotocol/sui-clmm-sdk ^1.4.5@cetusprotocol/common-sdk ^1.3.7All chain access — reads, finality waits, and simulation — flows through
a single
cetus.FullClient(aSuiGrpcClient & ExtendedSuiClient), sothe agent is gRPC end-to-end. Position discovery uses
sdk.Position.getPositionList(owner, [POOL_ID])with a server-side poolfilter, replacing the
getOwnedObjectsscan +fields.bitsdecoding fori32 tick indices. The remove call now passes the pool's actual
rewarder_coin_types(was hardcoded to[], which left active emissionsuncollected). The open path uses
createAddLiquidityFixTokenPayload—is_openandpos_idare documented public params in v2, replacing theas unknown as ...typecast escape hatch the old code needed.USDC detection (is it
coin_type_aorcoin_type_bon this pool?) isnow dynamic via
pool.coinTypeA === USDC_TYPE, so the agent works onany USDC pair, not just SUI/USDC.
Why this matters for a reference template
This activity is what new Sui builders fork to get a working CLMM yield
agent on day one. Before this PR the template:
is_open,pos_id) via a typecast.rebalance could deploy multiples of the planned capital.
@mysten/suiv1 because the bundled Cetus SDK couldn't be bumped.After this PR it shows the canonical v2-SDK build-and-submit pattern, the
canonical gRPC
simulateTransactionflow, and the kind of crash-recoveryguard a 24/7 onchain agent actually needs.
Files
agents/cetus-yield-agent/templates/standalone/agent.ts.tpl— main rewriteagents/cetus-yield-agent/templates/standalone/strategy.ts— new pure moduleagents/cetus-yield-agent/templates/standalone/package.json.tpl— dep bumpsagents/cetus-yield-agent/templates/standalone/dot-env.example—DRY_RUN,INTENT_FILE, gRPC URL docagents/cetus-yield-agent/activity.json— manifest exposes the new env vars to the catalogTest plan
tsc --noEmiton the rendered template against@mysten/sui@^2.17.0+@cetusprotocol/sui-clmm-sdk@^1.4.5(verified locally — clean).npx @human.tech/create-agent-wallet --activity cetus-yield-agent --runtime standaloneagainst this branchand confirm
npm installresolves.AGENT_MODE=monitorcycle on testnet — verify the strategy previewshows up in
position_statusevents andvolatilitysamples populate.AGENT_MODE=active DRY_RUN=trueon mainnet against the SUI/USDC pool— confirm
dry_run_simulatedevents log effects + gas without anywaap-cli send-txinvocation.AGENT_MODE=active DRY_RUN=falseon a low-value mainnet pool — observeone full rebalance cycle:
intent_written→remove_liquidity_complete→intent_written(open_pending)→rebalance_complete.open submission (e.g. SIGKILL after the
remove_liquidity_completeevent), then restart and confirm
intent_reconcile_criticalfires andthe agent exits non-zero instead of auto-opening.