Repository navigation
fix(#821): agent matches balances by issuer, never by code - #939
Anambraboi-1 wants to merge 1 commit into
Conversation
getBalances() keyed trustlines as CODE:ISSUER but nothing told the model
which were genuine, so a counterfeit USDT0 read the same as the real one.
The agent had no asset registry, so the isRegisteredIssuer gate the issue
describes did not exist yet.
- src/assetRegistry.ts: issuers pinned per network (mainnet USDC, USDY,
USDT0; testnet USDC), mirroring frontend/mobile/lib/assets.ts, with
getRegisteredAsset, isRegisteredIssuer (code AND issuer) and
classifyHolding.
- getBalances() returns { balances, holdings }. Balances stay keyed
CODE:ISSUER, never by bare code; each trustline is classified by issuer.
A verified holding names its issuer; anything else is UNVERIFIED with its
issuer shown (and, for a fake of a registered code, the real issuer).
Liquidity-pool shares are skipped instead of emitting undefined:undefined.
- The balance tool description and a system-prompt rule tell the model to
relay those notes and never call an unverified holding by a registered
name.
Closes Miracle656#821
|
@Anambraboi-1 is attempting to deploy a commit to the miracle656's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@Anambraboi-1 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Miracle656
left a comment
There was a problem hiding this comment.
Good work — the rule this enforces is the right one, and getBalances classifying at source is a better place for it than the call site. But #935 landed an hour ago against the same issue family and it took the other half of this ground, so this needs a rebase and a scope trim before it can go in.
What's on main now (from #935, packages/agent/src/assets.ts): an agent-side registry keyed by network, classifyHolding / classifyBalances returning status: 'verified' | 'unverified' | 'unlisted' with a message, a get_asset_info tool, and agent.ts already returning { ...balances, holdings: classifyBalances(balances) }. The conflict is real: git merge stops on packages/agent/src/agent.ts in both the get_wallet_balance tool description and the numbered rule at the end of buildSystemPrompt.
Please do not land a second registry. packages/agent/src/assetRegistry.ts and packages/agent/src/assets.ts would be two hand-maintained copies of the same pinned issuers inside one package — which is exactly the drift #822 exists to catch, one directory deeper. Fold your work into assets.ts.
Three things here are worth keeping, and I'd like all three:
-
The liquidity-pool guard in
txBuilder.ts. This is a genuine bug you found and nobody else did.mainstill does:const key = `${(balance as any).asset_code}:${(balance as any).asset_issuer}` result[key] = balance.balance
An LP-share balance has neither field, so the model is handed a key literally spelled
undefined:undefined. #935'sclassifyBalancesskips it when buildingholdings(itsStrKey.isValidEd25519PublicKeycheck catches it), but the flatbalancesobject still carries it. Yourtypeof code !== 'string' || typeof issuer !== 'string'continue fixes it at the source. Keep it, and keep the test for it. -
Classifying inside
getBalancesrather than inagent.ts. Moving it there means every future caller gets the issuer check for free instead of having to remember. If you do this,getBalancesreturns{ balances, holdings }andagent.tsline 411–414 has to change with it — your current diff changes the return type but leavesJSON.stringifyat the call site untouched, so the shape silently changes under the model. Please update that call site in the same commit, and grep for any other caller. -
USDY. #935's agent registry has only USDC and USDT0; yours has USDY too, which the wallet and mobile registries both carry. Adding it to
assets.tscloses a real gap.
What to drop: the whole of assetRegistry.ts and __tests__/assetRegistry.test.ts, and the getRegisteredAsset / isRegisteredIssuer / classifyHolding duplicates inside them. Re-point txBuilder.ts at ./assets.js. Note that #935's classifyHolding returns { status, message }, not { verified, note }, so your txBuilder.test.ts assertions will need updating to match — keep the assertions themselves, they're testing the right thing.
On the system prompt: main's rule 8 already covers naming the issuer and reporting unverified holdings. Your wording about relaying each holding's note is close enough that a second rule would just repeat it — please fold anything it adds into the existing rule 8 rather than appending a rule 10.
I validated every hard-coded address in this diff with StrKey.isValidEd25519PublicKey; all four constants in assetRegistry.ts check out, including the counterfeit fixtures. No problems there.
Summary
The agent's balance tool now checks every trustline against a registry by code and issuer. A counterfeit is never reported under a registered asset's name.
A note on the issue text: the code #821 describes isn't on
main.txBuilder.ts:166-174has nogetRegisteredAsset(code)call and no bare-code alias, and the agent has noisRegisteredIssuerhelper. Whatmainactually does is key trustlines asCODE:ISSUERand hand them to the model with no verification. So a fakeUSDT0:G-FAKE…looked the same as the real one, and the model reported it as USDT0. This PR adds the missing registry and the issuer gate.Changes (
packages/agent)src/assetRegistry.ts(new):frontend/mobile/lib/assets.ts: mainnet USDC, USDY and USDT0; testnet USDC only (no testnet USDT0 or USDY).getRegisteredAsset,isRegisteredIssuer(code and issuer must match) andclassifyHolding.getBalances()now returns{ balances, holdings }:balancesstays keyedCODE:ISSUERand never has a bare-code alias.holdingsclassifies each trustline. A verified holding names the asset, the issuer name and the issuer address. Anything else isverified: false, with a note startingUNVERIFIEDthat names its issuer; a fake of a registered code also names the real issuer.undefined:undefinedkeys.agent.ts: theget_wallet_balancedescription and a new system-prompt rule (9) tell the model to relay each holding's note, always name the issuer, and never call an unverified holding by a registered name.getBalancesis internal (not exported fromindex.ts), andagent.tsis its only caller.Overlap with #935: that PR adds a similar registry at
packages/agent/src/assets.ts. This one uses a separate file (assetRegistry.ts) so the two don't collide as files, but whichever lands second should be folded into the other.Related issue
Closes #821
Type of change
Component
Checklist
npm run typecheckpasses (agent:tsc --noEmitclean)npm run buildpasses (agent)Screenshots / test output
txBuilder.test.tscovers a wallet holding both the real USDT0 (25) and a fake (5000):USDT0key;issuerName: 'Tether'and its issuer in the note;UNVERIFIED, shows its own issuer, and has no registry name;Against the old
txBuilder.ts, 4 of these fail.assetRegistry.test.tschecks that:StrKey.isValidEd25519PublicKey;Agent suite: 7 suites / 64 tests passing.