Skip to content

feat: dca@6 — exact token + chain, tokenized stocks, symbol titles - #16

Merged
kahwaipd merged 3 commits into
mainfrom
feat/v6-confirm-token-chain
Sep 22, 2026
Merged

kahwaipd merged 3 commits into
mainfrom
feat/v6-confirm-token-chain

Conversation

@kahwaipd

@kahwaipd kahwaipd commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

What

Every DCA buy is now one exact shape, so the rail never picks the token. When the owner hasn't named the chain, the butler confirms it with them.

The owner named TOKEN ADDRESS CHAIN_ID What duty.py sends
a crypto token by ticker the ticker its contract its chain --token-out <address> --chain-out <id>
a crypto token by address the address — its chain (a mint implies Solana) same
a chain's own coin ETH / BNB / SOL — a chain whose coin it is (BNB and SOL imply theirs) --token-out ETH --chain-out <id>
a tokenized stock the ticker — — --token TSLA --amount-usdc <usd> (the rail routes a stock buy to the venue with the most shares)
  • New optional ADDRESS holds the exact contract behind a crypto ticker. TOKEN stays what the owner named, so the title and notes show the symbol, or the address when the owner gave one.
  • Anything looser buys nothing. That includes a ticker with a chain but no address, a mint on an EVM chain, and ETH on BSC. The owner gets one note when the program starts, and each fire logs the reason.
  • Confirmation card. TOKEN's description says: if the owner named no chain and gave no address, ask them to pick on a card listing each token-search match, with a tile to paste their own address, even when there is only one match. When they named the chain or gave an address, no card is shown.
  • recipe.json goes to version 6 and supersedes dca@5. description and keywords now mention stocks.

Why an address, not just a chain

A ticker with no chain lets the trade bot pick the deployment. It can pick a wrapper or a lookalike, and it re-picks on every fire. Pinning only the chain is worse: the server's alias rewrite (BTC → cbBTC), its UNVERIFIED_TICKER refusal and its stock reroute all skip a leg that carries an explicit chain (bevo-server docs/trading.md §3). So a crypto ticker needs its contract.

A stock buy can't be pinned to a venue at all. tradePlan drops protocol / stockChain on buys and auto-routes, so a stock takes no chain.

Live test (real DeepSeek V4 Flash, real staging reads, writes blocked)

The butler was virtuals-agent at origin/main (660ca7e), with a local registry serving this branch's dca@6. A guard in front of BEVO_API_URL forwarded only GET /api/butler-read/* to staging and answered the duty list as empty. It blocked every write: 19 duty_create POSTs, none of which reached staging. acp was a fake binary that ran only --version.

  • "Buy $5 SHIT every 2 minutes": token-search on the ticker returns only the Base SHIT (0xc005…1c8f, unverified, marked relevant). The Solana SHIT appears only when searched by its mint. With the first wording ("when several matches fit"), the butler filed the Base token without asking. That led to the last commit. With the final wording it asked: "Which token do you mean by SHIT?" offering SHIT on Base or "paste a different contract address". With the Solana mint pasted, it filed {TOKEN: "SHIT", ADDRESS: "ShitJu…gETRH", SIZE_USD: 5} with no CHAIN_ID. The title renders as "Buy $5 of SHIT every 2 minutes".
  • "Buy $5 of VIRTUAL on Base every day at 9am": no token card, only a timezone question. It filed {TOKEN: "VIRTUAL", ADDRESS: "0x0b3e…7e1b", CHAIN_ID: 8453, SIZE_USD: 5}.
  • "DCA $10 into TSLA every Monday at 9am": {TOKEN: "TSLA", SIZE_USD: 10} with no address or chain, bought in the stock shape. It forked the template to add the Monday check, because a timer trigger has no weekly cadence.
  • Side note: in the SHIT run the butler also added an optional "$5 every 2 minutes — correct?" question the owner had already answered. That is the model's own choice, not something the template asks for.

Checks

  • validate.py --standalone --maintainer (butler-skills origin/main): OK. README is 3,502 bytes.
  • replay.py against trade-activity-page and trade-activity-mixed: both pass. Each records only the startup note, because CI runs with TOKEN unset.
  • The buy path was run in a local harness over 18 cases, covering every shape above and every refusal. Each sent the expected argv with the key unchanged, or bought nothing and left a note plus a log line.

Merging

The registry follows this repo's main, so merging publishes dca@6 to every butler on the next hourly build. Afterwards, check the live index:

curl -s https://virtual-protocol.github.io/butler-skills/index.json | jq '.templates[]|{name,version}'

🤖 Generated with Claude Code

kahwaipd and others added 3 commits September 22, 2026 15:09
CHAIN_ID is now required, so recipe_show lists it under `decide` beside
TOKEN and SIZE_USD. Both settings' descriptions say what the owner
confirms: the match they picked from a card showing its name, chain and
address. TOKEN is that match's contract address, and every buy carries
--chain-out.

A bare ticker used to go to the rail on every fire, and the rail bought
whichever deployment it ranked first — possibly a wrapper or a lookalike
on another chain, and not necessarily the same one each time. duty.py now
buys only an address whose kind matches CHAIN_ID, or ETH / BNB / SOL on
their own chains. Anything else buys nothing: one note at start, one log
line per fire.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TOKEN goes back to what the owner named, a ticker or an address, so the
title shows the symbol, or the address when they gave one. A new ADDRESS
setting pins a crypto ticker to its exact contract on CHAIN_ID.

A tokenized stock is its ticker alone, with no ADDRESS and no CHAIN_ID.
It is bought in the stock shape, and the rail routes a stock buy to the
venue with the most shares. CHAIN_ID is optional again: a mint implies
Solana, and BNB and SOL imply their chains. A ticker with a chain but no
address still buys nothing.

The card is shown only when several matches fit; one clear match needs no
card.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A live run of "Buy $5 SHIT every 2 minutes" showed that token-search on a
ticker returns only the Base SHIT (unverified but marked relevant). The
Solana SHIT shows up only when searched by its mint. The earlier wording
("when several matches fit") let the butler take that one hit as the
answer and file the Base token.

TOKEN's description now says: if the owner named no chain and gave no
address, ask them to pick on a card that lists each match plus a tile to
paste their own address, even when there is only one match. Re-run with
this wording: the butler asked, and after the mint was pasted it filed
{TOKEN: SHIT, ADDRESS: <mint>} with no CHAIN_ID.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kahwaipd kahwaipd changed the title feat: dca@6 — confirm the token and chain with the owner first feat: dca@6 — exact token + chain, tokenized stocks, symbol titles Sep 22, 2026
@kahwaipd
kahwaipd merged commit 4acf3f4 into main Sep 22, 2026
2 checks passed
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.

1 participant