Skip to content

SDK + daemon PCA write surface is wired to archived V9 contract; V10 PCA NFT has no SDK path #519

@zsculac

Description

@zsculac

Summary

The DKG SDK's Publishing Conviction Account (PCA) write surface — chain-adapter.ts methods, DKGAgent facade methods, daemon HTTP routes /api/pca/*, the response schemas in daemon/routes/pca.ts, and every chain-adapter integration test that exercised PCA — was wired end-to-end to the V9 PublishingConvictionAccount.sol contract. The V10 DKGPublishingConvictionNFT.sol contract was never reachable from the SDK. After #500 archived V9, the SDK methods became return null stubs and all /api/pca/* writes return HTTP 503 FEATURE_UNAVAILABLE_503.

This is both a hypothesis confirmation (we were testing the wrong contract) and a feature gap (SDK has no path to the live V10 PCA contract). Publishing through V10 PCA NFT does work end-to-end on-chainKnowledgeAssetsV10.publish() directly invokes the V10 NFT — but nothing in the SDK or daemon can create, fund, or register an agent against a V10 PCA account.

Evidence

1. Pre-archive SDK call sites all resolved the V9 contract

packages/chain/src/archive/evm-adapter-v8-v9-methods.ts (verbatim source preserved from commit 4dc99e6e):

// L348
const tx = await pca.createAccount(amount, lockEpochs);   // V9: per-account lock
// L391
const tx = await pca.addFunds(accountId, amount);          // V9 method name
// L407
const tx = await this.contracts.publishingConvictionAccount.extendLock(...);
// L425
const tx = await this.contracts.publishingConvictionAccount.addAuthorizedKey(...);
// L447, L474
await this.contracts.publishingConvictionAccount.getAccountInfo(accountId);

this.contracts.publishingConvictionAccount was populated from Hub.getContractAddress("PublishingConvictionAccount") — the V9 contract key. See evm-adapter.ts:705-709:

try {
  this.contracts.publishingConvictionAccount = await this.resolveContract('PublishingConvictionAccount');
} catch {
  // PublishingConvictionAccount not deployed — conviction account operations unavailable
}

2. V9 vs V10 contract APIs are not just renamed — they are semantically different

Concern V9 PublishingConvictionAccount.sol V10 DKGPublishingConvictionNFT.sol
Account creation createAccount(uint96 amount, uint40 lockEpochs) — per-account lock duration createAccount(uint96 committedTRAC) — lock is global (parametersStorage.publishingConvictionEpochs())
Funding addFunds(accountId, amount) — admin-gated, raises raw balance topUp(accountId, amount) — populates persistent topUpBalance buffer separate from base commitment
Authorization addAuthorizedKey(accountId, key) — many keys per account, manual list registerAgent(accountId, agent) — bidirectional agentToAccountId reverse map, agent can only belong to one account (N28-protection)
Publish cost coverPublishingCost(accountId, baseCost, caller) — direct balance debit, single epoch coverPublishingCost(publishingAgent, baseCost, kcStartEpoch, kcEpochs) — window-based allowance, lazy-settlement, enforces kcEpochs ≤ lockDurationEpochs
Settlement implicit on every publish explicit settle(accountId) lazy-sweep + active sink via EpochStorage.addTokensToEpochRange
Discount MAX_DISCOUNT * conviction / (conviction + C_HALF) — dynamic 6-tier ladder (0/10/20/30/40/50/75%) fixed at creation based on committedTRAC
Ownership account.admin field ERC-721 ownerOf(accountId)
getAccountInfo shape (admin, balance, initialDeposit, lockEpochs, conviction, discountBps) (owner, committedTRAC, baseEpochAllowance, createdAtEpoch, expiresAtEpoch, createdAtTimestamp, expiresAtTimestamp, discountBps, topUpBuffer, agentCount, lastSettledWindow, fullySwept)

DKGAgent.createPublishingConvictionAccount(_amount, _lockEpochs) (packages/agent/src/dkg-agent.ts:3978) carries the V9 lockEpochs parameter — V10 NFT has no per-account lock. This is the smoking gun: even the SDK facade signature was V9-shaped.

3. Daemon route handler is V9-shaped

packages/cli/src/daemon/routes/pca.ts:5-7:

// PCAs are a distinct economic primitive: they're the off-chain
// expression of the on-chain `PublishingConvictionAccount` contract,
// driven by an operator standing up the runbook fixtures

serializeAccountInfo returns the V9 shape: {accountId, admin, balance, initialDeposit, lockEpochs, conviction, discountBps}. No V10 fields (committedTRAC, topUpBalance, lockDurationEpochs, expiresAtEpoch, agentCount, ...).

4. Tests

  • packages/chain/test/archive/conviction-account.test.ts — 9 PCA tests. Archived. Used adapter methods (createConvictionAccount, addConvictionFunds, addPCAAuthorizedKey, getConvictionAccountInfo) that resolve to V9.
  • packages/chain/test/archive/staking-conviction.test.ts — V8 stakeWithLock.
  • packages/evm-module/test/v10-e2e-conviction.test.ts Flow 3 — bypasses SDK entirely, instantiates DKGPublishingConvictionNFT directly via loadFixture. This is the only test that exercises V10 NFT, and it does so by direct contract call, not through the SDK path.

There is no test in the codebase that covers SDK → daemon → chain-adapter → V10 PCA NFT.

5. Current (post-#500) state

  • DKGAgent PCA write methods (createPublishingConvictionAccount, addPublishingConvictionAccountFunds, addPCAAuthorizedKey, isPCAAuthorizedKey, getPublishingConvictionAccountInfo) at dkg-agent.ts:3978-4013 all return null.
  • Daemon routes translate null → HTTP 503 FEATURE_UNAVAILABLE_503.
  • evm-adapter.ts:2152-2188 has three read-only V10 NFT methods (getConvictionAgentAccountId, getConvictionAccountLockDurationEpochs, getPublishingConvictionAccountOwner) — used by the publisher to decide whether to route through the PCA discount branch and what publishEpochs to use. Reads work; writes do not.
  • V9 deploy script 043_deploy_publishing_conviction_account.ts archived with func.skip = async () => true. V9 contract not registered in Hub on V10 deployments.
  • V10 NFT deploy script 053_deploy_dkg_publishing_conviction_nft.ts active. Registered in Hub as "DKGPublishingConvictionNFT".
  • V10 NFT is directly invoked by KnowledgeAssetsV10.publish() (lines 384, 401, 912, 928):
    • agentToAccountId(msg.sender) resolves the publisher's PCA
    • coverPublishingCost(msg.sender, baseCost, kcStartEpoch, kcEpochs) debits the account

What this means

  • Every commit before refactor: archive non-V10 contracts and downstream V8/V9 backward-compat code #500 that claimed to test "publishing conviction account" through the SDK was testing the V9 contract. The V10 PCA NFT contract has never been reachable from pnpm --filter @origintrail-official/dkg-chain test or the daemon HTTP API.
  • Publishing does work end-to-end on V10: a PCA NFT account created via direct contract call will let a registered agent publish at discount because KnowledgeAssetsV10.publish() short-circuits the SDK and calls the NFT directly. But there is no SDK / daemon helper to create the account, top it up, or register an agent.
  • The 503 errors on /api/pca/* are not a regression from refactor: archive non-V10 contracts and downstream V8/V9 backward-compat code #500 — they reflect a pre-existing wiring gap that the archive merely surfaced.

Proposed wiring (V10 NFT through SDK)

packages/chain/src/chain-adapter.ts (interface) + packages/chain/src/evm-adapter.ts (impl)

createConvictionAccount(committedTRAC: bigint): Promise<{ accountId: bigint } & TxResult>;
topUpConvictionAccount(accountId: bigint, amount: bigint): Promise<TxResult>;
registerConvictionAgent(accountId: bigint, agent: string): Promise<TxResult>;
deregisterConvictionAgent(accountId: bigint, agent: string): Promise<TxResult>;
isConvictionAgent(accountId: bigint, agent: string): Promise<boolean>;
settleConvictionAccount(accountId: bigint): Promise<TxResult>;
getConvictionAccountInfo(accountId: bigint): Promise<V10ConvictionAccountInfo | null>;

Each resolves this.contracts.dkgPublishingConvictionNFT and calls the V10 method directly. V10ConvictionAccountInfo mirrors the NFT's getAccountInfo return tuple.

packages/agent/src/dkg-agent.ts

Replace the five null stubs with delegating thin wrappers to the new chain-adapter methods. Drop lockEpochs from createConvictionAccount. Rename addPCAAuthorizedKeyregisterConvictionAgent (V9 "key" → V10 "agent" terminology shift).

packages/cli/src/daemon/routes/pca.ts

  • Response schema: replace V9 fields with V10 fields.
  • POST /api/pca body: drop lockEpochs (now a global parameter).
  • New routes: POST /api/pca/:id/agent (register), DELETE /api/pca/:id/agent/:address (deregister), POST /api/pca/:id/settle (settle).
  • Remove POST /api/pca/:id/authorize (V9 semantics) — replaced by agent registration.

packages/sdk (or wherever the public client lives)

Update ApiClient / dkg.client.api request/response shapes. Add migration note.

Acceptance

  • All V10 NFT write methods exposed through chain-adapter, agent facade, and daemon HTTP API.
  • New integration test in packages/chain/test/conviction-account-v10.test.ts that deploys V10 NFT via fixture and exercises full lifecycle (create → topUp → registerAgent → coverPublishingCost via KAv10 publish → settle).
  • Daemon e2e test: POST /api/pca returns 200 with {accountId, txHash, blockNumber}.
  • MockChainAdapter + NoChainAdapter parity stubs.
  • CHANGELOG / docs note: V10.1+ replaces per-account lock + multi-key with global lock + agent reverse map; clients must update.
  • V10 PCA NFT lifecycle test as standalone on-chain test (not just inside KAv10 publish flow).

Out of scope

  • Re-introducing V9 PCA. V9 contract stays archived.
  • On-chain Hub deregistration of V9 — separate ops task.
  • V10 Staking Conviction NFT SDK wiring — separate feature.

References

  • PR #500 — archived non-V10 contracts.
  • V10 contract: packages/evm-module/contracts/DKGPublishingConvictionNFT.sol
  • Archived V9 reference: packages/evm-module/contracts/archive/PublishingConvictionAccount.sol
  • Pre-archive SDK call snapshot: packages/chain/src/archive/evm-adapter-v8-v9-methods.ts
  • Triage finding L-1 (PCA dead with admin-withdraw bug) closes once V9 stays out of Hub registry.

See also

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions