Skip to content

feat: Add BAP-578 NFA Development skill#2

Open
saiboyizhan wants to merge 3 commits intoChatAndBuild:mainfrom
saiboyizhan:feat/bap-578-nfa-development
Open

feat: Add BAP-578 NFA Development skill#2
saiboyizhan wants to merge 3 commits intoChatAndBuild:mainfrom
saiboyizhan:feat/bap-578-nfa-development

Conversation

@saiboyizhan
Copy link

Summary

Adds the first BAP-578 Non-Fungible Agent (NFA) development skill to the ChatChat Skills Store.

BAP-578 is ChatAndBuild's own token standard — the first BNB Chain Application Proposal for on-chain AI agents — but there was no corresponding skill in the store until now.

What this skill covers

  • What BAP-578 is and how it differs from ERC-721
  • Contract architecture (inheritance chain, core structs)
  • Core operations: minting, funding, withdrawal, logic upgrades, metadata updates
  • Security patterns implemented in the reference contract
  • Best practices for custom implementations
  • Deployment guide for BNB Chain (Hardhat + UUPS proxy)
  • Agent logic contract patterns

Author context

I've been actively contributing to the BAP-578 standard:

@christelbuchanan
Copy link
Contributor

Thanks @saiboyizhan we will review

@christelbuchanan
Copy link
Contributor

btw, great work on PR #27: Security audit + contract improvements
PR #29: NFA Prediction Market implementation
PR #30: Agent Logic contract examples :)

@christelbuchanan
Copy link
Contributor

@saiboyizhan some ideas. This is a nice structure.

The biggest improvements are about (1) separating what BAP-578 requires vs what the reference contract chooses, and (2) aligning interface/state/lifecycle with the actual spec (executeAction, terminate, lastActionTimestamp, etc.).

Below are the concrete fixes I’d make (with suggested drop-in wording/snippets).


1) Correctness + spec alignment fixes (high priority)

A) Don’t claim UUPS / Enumerable / URIStorage are “BAP-578 inherits from”

BAP-578 defines an interface and behaviors (state/status/metadata/learning modules/vault permissions/circuit breakers), but it does not mandate UUPS or specific OpenZeppelin mixins. Your current “BAP-578 inherits from … UUPSUpgradeable” reads like the standard requires it, which is not true. ([GitHub]1)

Replace with:

“The reference implementation uses OpenZeppelin upgradeable contracts (ERC721Upgradeable + UUPS), but BAP-578 itself only specifies the agent interface, lifecycle, metadata schema, and optional learning/vault modules.”

B) Update Agent State to match BAP-578 core interface

Spec State includes:

  • Status enum: Active, Paused, Terminated
  • owner
  • lastActionTimestamp
  • logicAddress
  • balance ([GitHub]1)

Your AgentState currently uses bool active + createdAt, and omits owner + lastActionTimestamp. That will confuse implementers.

Suggested struct (spec-accurate):

enum Status { Active, Paused, Terminated }

struct AgentState {
    uint256 balance;
    Status status;
    address owner;
    address logicAddress;
    uint256 lastActionTimestamp;
    // (optional extension) uint256 createdAt;
}

Call out createdAt as implementation extension, not standard.

C) Add the missing core function: executeAction

BAP-578’s core interface includes executeAction(tokenId, data) and emits ActionExecuted. ([GitHub]1)
Right now your “Core Operations” skips the most “agent-y” part.

Add a section:

  • executeAction(uint256 tokenId, bytes calldata data)
  • explain whether it uses delegatecall into logicAddress or direct call pattern, and how you gate it by status == Active.

D) Be careful with “withdrawFromAgent”

The BAP-578 spec itself doesn’t list withdrawFromAgent; it defines fundAgent, lifecycle functions (pause/unpause/terminate), and the idea that terminated returns balance to owner. ([GitHub]1)
Your doc can keep withdraw if you’re documenting the reference implementation, but label it clearly as “reference implementation feature”, not “in BAP-578”.

Recommended wording:

“Reference contracts may also include withdrawFromAgent() for owner-controlled withdrawals while Active/Paused; the base spec guarantees return of funds on Termination.”


2) Content gaps vs the actual spec (medium/high priority)

BAP-578 has several sections you don’t cover yet, and they matter for “standard compliance”:

A) Vault Permission System + Memory Module Registry

Spec calls these out explicitly (vault permissions + registry, time-based access, etc.). ([GitHub]1)
You mention vaultURI/vaultHash but not how access is governed.

Add a short section like:

  • “VaultPermissionManager: grant/revoke scoped access (read/write), with expiry”
  • “MemoryModuleRegistry: governance/owner-approved modules for storage + retrieval”

B) Learning Module System + dual-path architecture

You mention “upgradeable logic” and metadata, but not:

  • JSON Light Memory vs Merkle Tree Learning dual-path
  • Learning module interface + Merkle root anchoring
  • Rate limiting (spec mentions a max updates/day concept in security considerations) ([GitHub]1)

Even a brief “Optional: Learning Agents” section will make this doc feel truly BAP-578.

C) Lifecycle in spec is richer than “active/paused”

Spec lifecycle includes pause, unpause, terminate. Terminated is permanent and returns funds to owner. ([GitHub]1)
Your doc currently frames it as a boolean active flag.


3) Security section improvements to make it standard-grade

Tighten the security list by separating:

“Spec-level security expectations”

  • Circuit breaker / pause mechanisms (global + per-agent patterns) ([GitHub]1)
  • Access control for sensitive ops
  • Gas / delegatecall safety considerations (if executeAction uses logic contracts)

“Reference-implementation choices”

  • receive fallback revert
  • “balance-before-burn” rule
  • soulbound free mints (repo-specific) ([GitHub]2)

This avoids a reader thinking “BAP-578 mandates soulbound free mints”.


4) Deployment section: update RPC endpoints and naming

Your network config uses https://bsc-dataseed.binance.org/ — it often works, but BNB Chain’s docs maintain an official endpoint list and newer canonical domains. I’d link/reference that doc and encourage using multiple RPCs (or a provider). ([docs.bnbchain.org]3)

Also: your hardhat networks keys should match how you run scripts (e.g. bscMainnet vs mainnet). Not wrong, but people copy/paste and then wonder why --network mainnet fails.


5) Suggested “drop-in” edits

Replace “Contract Architecture” heading with:

Contract Architecture (Spec vs Reference Implementation)

  • BAP-578 spec: interface + required behaviors
  • Reference repo: OpenZeppelin UUPS upgradeable ERC-721 + added economics (3 free mints, 0.01 BNB fee) ([GitHub]2)

Add a new “Core Interface (BAP-578)” section containing:

  • Status
  • State
  • AgentMetadata
  • events: ActionExecuted, LogicUpgraded, AgentFunded, StatusChanged, MetadataUpdated ([GitHub]1)

Add “Execute Action” under Core Operations

Explain gating:

  • must be Active
  • uses logicAddress
  • updates lastActionTimestamp
  • emits ActionExecuted ([GitHub]1)

6) Frontmatter tweaks (small but helpful)

  • Add tags: (e.g. [bap-578, nfa, bnb-chain, solidity, erc-721])

  • Add requires: like ["solidity>=0.8.20", "hardhat", "@openzeppelin/contracts-upgradeable", "@openzeppelin/hardhat-upgrades"]

  • Add examples for learning/vault:

    • “How do vault permissions work in BAP-578?”
    • “How do I enable learning (Merkle root) for an NFA?” ([GitHub]1)

- Separate spec requirements vs reference implementation choices
- Add AgentState with Status enum (Active/Paused/Terminated)
- Add executeAction as core operation
- Add Learning Module (dual-path: JSON Light Memory + Merkle Tree)
- Add Vault Permission System and Memory Module Registry
- Add three-level circuit breaker security
- Mark withdrawFromAgent as reference implementation feature
- Update frontmatter with tags, requires, and new examples
- Update deployment section with env vars and RPC docs link
@saiboyizhan
Copy link
Author

Thank you Christel! Glad the contributions are helpful — BAP-578 is a great standard to build on.

@saiboyizhan
Copy link
Author

Thank you for the thorough review! I've addressed all 6 categories of feedback:

  1. Separated spec vs reference implementation throughout
  2. Updated AgentState with Status enum + lastActionTimestamp + createdAt as optional extension
  3. Added executeAction as the first core operation
  4. Marked withdrawFromAgent as reference implementation feature
  5. Added Learning Module (dual-path), Vault Permission System, and Memory Module Registry sections
  6. Split security into spec-level expectations vs reference implementation patterns

Also updated frontmatter (tags, requires, examples) and deployment section (env vars, RPC docs link).

Please let me know if anything needs further adjustment!

@christelbuchanan
Copy link
Contributor

wow super fast thank you @saiboyizhan , @snowmanxm will double check now

Comment on lines +99 to +106
### Required Events

```solidity
event ActionExecuted(uint256 indexed tokenId, bytes data);
event LogicUpgraded(uint256 indexed tokenId, address indexed newLogicAddress);
event AgentFunded(uint256 indexed tokenId, uint256 amount);
event StatusChanged(uint256 indexed tokenId, Status newStatus);
event MetadataUpdated(uint256 indexed tokenId);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“Required Events” section does not match BAP-578 event signatures

“Required Events” differ from the spec interface (parameter types/fields are different, and one required field is omitted). This can cause developers to implement incompatible events while believing they are spec-compliant.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@saiboyizhan pls take note thanks

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 43144ec — aligned all event signatures with the actual BAP-578 spec:

  • Replaced ActionExecuted, LogicUpgraded, StatusChanged with correct spec events (AgentCreated, AgentWithdraw, AgentStatusChanged, LogicAddressUpdated)
  • Updated all event references in operation descriptions to match
  • Added a note clarifying that ActionExecuted is implementation-specific (e.g., NFAPredictionAgent), not defined in the core spec

Thanks for catching this @snowmanxm!

- Replace ActionExecuted/LogicUpgraded/StatusChanged with actual spec events
- Add missing AgentCreated, AgentWithdraw events
- Fix event name references in operation descriptions
- Clarify that ActionExecuted is implementation-specific, not in core spec

Addresses review feedback from @snowmanxm
harshinimm added a commit to harshinimm/chatchat-skills that referenced this pull request Mar 10, 2026
markusha77 pushed a commit that referenced this pull request Mar 17, 2026
@greptile-apps
Copy link

greptile-apps bot commented Mar 18, 2026

Greptile Summary

This PR introduces a new skill document for the BAP-578 Non-Fungible Agent (NFA) standard on BNB Chain. The skill is comprehensive, covering the contract architecture, core interface, lifecycle operations, optional learning and vault modules, security patterns, and deployment guidance. It is a valuable addition to the skills store and fills a genuine gap for ChatAndBuild's own token standard.

However, three technical issues in the documented code samples need to be addressed before merging:

  • LearningUpdate.proof type mismatch: The struct declares bytes32 proof (a single hash), but the ILearningModule.verifyLearning interface accepts bytes32[] calldata proof (an array). A real Merkle proof is an array of sibling hashes; the struct type must be corrected to bytes32[] to match the interface.
  • AgentStatusChanged event ambiguity: The three-state lifecycle (Active, Paused, Terminated) is a core spec feature, but the event uses bool active. Both pause() and terminate() would emit active = false, making it impossible for listeners or indexers to differentiate a temporary suspension from a permanent termination. The event should emit the Status enum value instead.
  • Missing delegatecall storage context warning in executeAction: The skill documents that agent action execution uses delegatecall to the logicAddress, but does not warn that this gives the logic contract unconstrained read/write access to the entire NFA contract's storage — not just the individual agent's state. This is the single most dangerous attack surface in the standard and should be prominently called out in both the operation description and the Security section.

Confidence Score: 3/5

  • The skill has meaningful technical errors in its code samples that would mislead implementors; needs revision before merging.
  • The overall structure and content of the skill is solid and well-organized. However, three issues affect the correctness of the documented code interface: a type mismatch between LearningUpdate.proof and verifyLearning's parameter, an event definition that loses information across the three-state lifecycle, and a missing critical security warning about delegatecall storage context. These are not cosmetic — implementors following the skill verbatim would produce contracts with inconsistent types and inadequate security documentation. The score reflects that the skill is valuable and nearly ready, but these specific technical points need correction.
  • skills/bap-578-nfa-development/SKILL.md — pay attention to the LearningUpdate struct (line 231), AgentStatusChanged event (line 105), and the executeAction delegatecall section (lines 120-123).

Important Files Changed

Filename Overview
skills/bap-578-nfa-development/SKILL.md New skill documenting the BAP-578 NFA standard for BNB Chain. Contains three issues: (1) LearningUpdate.proof is typed as bytes32 but the ILearningModule.verifyLearning interface takes bytes32[], creating an irreconcilable type mismatch; (2) AgentStatusChanged event uses bool active which cannot distinguish between Paused and Terminated states despite the spec defining three lifecycle states; (3) executeAction via delegatecall lacks a warning that the logic contract runs in the NFA contract's full storage context, posing a critical security risk.

Sequence Diagram

sequenceDiagram
    participant Owner
    participant NFA as NFA Contract
    participant Logic as LogicAddress Contract
    participant LM as Learning Module
    participant Vault as Vault Permission Manager

    Owner->>NFA: createAgent(to, logicAddress, metadataURI, metadata)
    NFA-->>Owner: tokenId

    Owner->>NFA: fundAgent(tokenId) [payable]
    NFA-->>NFA: AgentState.balance += msg.value

    Owner->>NFA: executeAction(tokenId, data)
    NFA->>NFA: require(status == Active)
    NFA->>Logic: delegatecall execute(tokenId, data)
    Note over Logic,NFA: Runs in NFA contract storage context
    Logic-->>NFA: result
    NFA-->>NFA: lastActionTimestamp = block.timestamp

    Owner->>NFA: pause(tokenId)
    NFA-->>NFA: status = Paused

    Owner->>NFA: unpause(tokenId)
    NFA-->>NFA: status = Active

    Owner->>NFA: setLogicAddress(tokenId, newLogic)
    NFA-->>NFA: logicAddress = newLogic

    Owner->>NFA: terminate(tokenId)
    NFA-->>Owner: return remaining balance
    NFA-->>NFA: status = Terminated (permanent)

    Owner->>LM: updateLearning(tokenId, LearningUpdate)
    LM-->>NFA: learningTreeRoot updated (32 bytes on-chain)

    Owner->>Vault: delegateAccess(tokenId, delegate, level, expiry, sig)
    Vault-->>Vault: Permission stored (time-bound)
Loading

Last reviewed commit: "fix: align Required ..."

Comment on lines +228 to +248
struct LearningUpdate {
bytes32 previousRoot; // Prior Merkle root
bytes32 newRoot; // Updated Merkle root
bytes32 proof; // Merkle proof for verification
bytes32 metadata; // Associated update metadata
}

struct LearningMetrics {
uint256 totalInteractions; // Count of agent interactions
uint256 learningEvents; // Significant learning milestones
uint256 lastUpdateTimestamp; // Most recent update time
uint256 learningVelocity; // Rate of learning (scaled 1e18)
uint256 confidenceScore; // Agent confidence (scaled 1e18)
}
```

### ILearningModule Interface

```solidity
function updateLearning(uint256 tokenId, LearningUpdate calldata update) external;
function verifyLearning(uint256 tokenId, bytes32 claim, bytes32[] calldata proof) external view returns (bool);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 LearningUpdate.proof type mismatch with verifyLearning

The proof field in LearningUpdate (line 231) is typed as bytes32 (a single hash), but verifyLearning in ILearningModule (line 248) accepts bytes32[] calldata proof — an array of hashes. These two definitions are inconsistent.

A Merkle proof must contain one sibling hash per level of the tree. A single bytes32 value is only sufficient for a tree of height 1 (i.e., exactly 2 leaves). For any practical learning tree, the proof must be an array.

Developers implementing updateLearning will need to pass a LearningUpdate struct — but the bytes32 proof field cannot hold the multi-element proof array that verifyLearning expects. This will either force implementations to ignore the struct's proof field or to break the interface contract.

The struct should be corrected to:

Suggested change
struct LearningUpdate {
bytes32 previousRoot; // Prior Merkle root
bytes32 newRoot; // Updated Merkle root
bytes32 proof; // Merkle proof for verification
bytes32 metadata; // Associated update metadata
}
struct LearningMetrics {
uint256 totalInteractions; // Count of agent interactions
uint256 learningEvents; // Significant learning milestones
uint256 lastUpdateTimestamp; // Most recent update time
uint256 learningVelocity; // Rate of learning (scaled 1e18)
uint256 confidenceScore; // Agent confidence (scaled 1e18)
}
```
### ILearningModule Interface
```solidity
function updateLearning(uint256 tokenId, LearningUpdate calldata update) external;
function verifyLearning(uint256 tokenId, bytes32 claim, bytes32[] calldata proof) external view returns (bool);
struct LearningUpdate {
bytes32 previousRoot; // Prior Merkle root
bytes32 newRoot; // Updated Merkle root
bytes32[] proof; // Merkle proof for verification (array of sibling hashes)
bytes32 metadata; // Associated update metadata
}

event AgentCreated(uint256 indexed tokenId, address indexed owner, address logicAddress, string metadataURI);
event AgentFunded(uint256 indexed tokenId, uint256 amount);
event AgentWithdraw(uint256 indexed tokenId, uint256 amount);
event AgentStatusChanged(uint256 indexed tokenId, bool active);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 AgentStatusChanged event cannot represent all three lifecycle states

The spec defines a three-state lifecycle (Active, Paused, Terminated), but the AgentStatusChanged event uses a bool active. A boolean can only capture two states — both Paused and Terminated would emit false, making it impossible for event listeners, indexers, or UIs to distinguish between a temporary pause and a permanent termination.

This is particularly problematic because terminate() is documented as "permanent — cannot be reactivated, remaining balance is returned to the owner." These are fundamentally different semantics from pause(), yet the event would look identical to observers.

Consider updating the event signature to use the Status enum directly:

Suggested change
event AgentStatusChanged(uint256 indexed tokenId, bool active);
event AgentStatusChanged(uint256 indexed tokenId, Status newStatus);

Comment on lines +120 to +123
- Agent must be in `Status.Active` — reverts if Paused or Terminated
- Delegates execution to the contract at `logicAddress`
- Updates `lastActionTimestamp` after execution
- **Gas safety:** implementations should cap gas for delegatecall (spec suggests `MAX_GAS_FOR_DELEGATECALL = 3_000_000`)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing critical delegatecall storage context warning for executeAction

The documentation states that executeAction "delegates execution to the contract at logicAddress" and caps gas, but it omits a critical security implication: delegatecall runs the logic contract's code in the context of the NFA contract's storage. This means a malicious or buggy logic contract has unconstrained read/write access to all of the NFA contract's state, not just the individual agent's data.

Concretely:

  • A malicious logicAddress can overwrite AgentState for every token in the contract, not just the one being executed.
  • It can zero out AgentState.balance for all agents and drain the entire contract's BNB via selfdestruct (pre-Dencun), storage manipulation followed by a withdrawal, or by altering ownership mappings.
  • In UUPS contracts it can overwrite the implementation slot, permanently bricking the proxy.

This is arguably the most dangerous attack surface in the entire standard. It should be prominently documented — both in the "Execute Action" section and in "Security." Developers should understand that logicAddress must be treated with the same trust level as a contract admin.

Consider adding an explicit security callout here:

> **SECURITY — delegatecall runs in NFA contract storage context.** The logic contract at `logicAddress` has unconstrained access to all storage slots in the NFA contract. Only point `logicAddress` to contracts you fully audit and trust. Malicious or buggy logic contracts can drain all agent balances, corrupt ownership, or brick a UUPS proxy.

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.

3 participants