diff --git a/CHANGELOG.md b/CHANGELOG.md index 6aa82a1e..02fb04c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased (dev) + +### Fixes + +* **`/v2/state/get_tokens` missing balances — token-contract detection in `sync accounts`**: `./hyp-control sync accounts` (and `sync all`) resolved a contract's transfer parameter struct by the hard-coded struct name `"transfer"`. Per the ABI spec the struct backing an action is named by the action's `type` field, which is frequently *not* the action name — e.g. several contracts declare a fully standard transfer (`from:name, to:name, quantity:asset, memo:string`) under the struct name `transfer_token`. Those contracts were silently skipped, so their balances were never backfilled into the MongoDB `accounts` collection and `get_tokens` returned only the symbols the live indexer happened to capture — producing a "same contract, some symbols present and some missing" result on upgraded nodes. The struct is now resolved through the transfer action's declared `type`. **After upgrading, re-run `./hyp-control sync accounts ` (or `sync all`) to backfill the previously skipped contracts.** + ## 4.0.7 (2026-05-16) > **4.0.6 was skipped.** The 4.0.6 version sat on `main` untagged for a period and some operators deployed it directly from `main` before any release tag existed. To avoid ambiguity between those pre-tag production deployments and the official artifact, this work is released as **4.0.7**. There is no separate 4.0.6 entry. Going forward, `main` only advances to tagged releases and active development happens on `dev`. diff --git a/src/cli/sync-modules/sync-accounts.ts b/src/cli/sync-modules/sync-accounts.ts index fa4c8b65..fb63bed8 100644 --- a/src/cli/sync-modules/sync-accounts.ts +++ b/src/cli/sync-modules/sync-accounts.ts @@ -68,18 +68,25 @@ export class AccountSynchronizer extends Synchronizer { const abi = await this.client.v1.chain.get_abi(contract); const tables = new Set(abi.abi?.tables?.map(value => value.name)); if (tables.has("accounts") && tables.has("stat")) { - const actions = new Set(abi.abi?.actions?.map(value => value.name)); - if (actions.has("transfer")) { - const transferType = abi.abi?.structs?.find(s => s.name === 'transfer'); + // Resolve the transfer parameter struct through the transfer action's + // declared `type`, not the literal name "transfer". Per the ABI spec the + // struct backing an action is named by the action's `type` field, which is + // frequently NOT the action name — e.g. the paycash contracts (token.pcash, + // cashescashes, swap.pcash, ...) declare a standard transfer under the struct + // name "transfer_token". Looking the struct up by name === "transfer" returned + // undefined and silently dropped these valid token contracts from the backfill. + const transferAction = abi.abi?.actions?.find(a => a.name === "transfer"); + if (transferAction) { + const transferType = abi.abi?.structs?.find(s => s.name === transferAction.type); if (transferType && transferType.fields) { const fields = transferType.fields; let valid = true; for (let i = 0; i < transferFields.length; i++) { - if ((fields[i].name === "from" || fields[i].name === "to") && fields[i].type === 'account_name') { + if (fields[i] && fields[i].name === transferFields[i].name && fields[i].type === 'account_name') { valid = true; continue; } - if (fields[i].name !== transferFields[i].name || fields[i].type !== transferFields[i].type) { + if (!fields[i] || fields[i].name !== transferFields[i].name || fields[i].type !== transferFields[i].type) { console.error(`Invalid token contract ${contract} ->> ${fields.map(f => (f.name + "(" + f.type + ")").padEnd(24, " ")).join(' ')}`); valid = false; break;