From 234001a319825b1a4b5fb449e955b1b346ee1e41 Mon Sep 17 00:00:00 2001 From: Damir Mujic Date: Mon, 8 Jun 2026 21:46:59 +0200 Subject: [PATCH] fix(aip18): buyer link records smart wallet; sync services_needed; no false drift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three real-world findings from end-to-end buyer onboarding (sdk@4.4.5): #2 (Medium) pay-only `actp publish` linked the bare EOA signer as the agent's wallet, but a wallet:auto buyer transacts via its Smart Wallet — so the agirails.app profile didn't match on-chain payments. Providers get the Smart Wallet from on-chain activation; a buyer skips activation, so we now read the address `actp init` derived into .actp/config.json and link THAT (EOA stays the signer; the web derives + verifies). The buyer-link marker records it too. #5 (Low) the publish→web sync config omitted services_needed, so the dashboard never saw what a buyer requests. Now included (opt-in semi-public, §7.1). budget is still never synced — it is a private operational cap (DEC-2). #3 (Low) checkConfigDrift compared the local file to the on-chain AgentRegistry and warned "Local config is ahead / not published" on every client startup for a pure buyer — which is never anchored on-chain by design (DEC-3). It now short-circuits for intent:pay (DB-based buyer reconcile is future work). Verified live: a buyer's `actp balance` no longer emits the drift warning and still initializes the gas-sponsored AutoWallet. tsc clean; 64 targeted tests green. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ACTPClient.ts | 12 ++++++++++++ src/cli/commands/publish.ts | 25 ++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/ACTPClient.ts b/src/ACTPClient.ts index 3090411..1c4d9d9 100644 --- a/src/ACTPClient.ts +++ b/src/ACTPClient.ts @@ -1805,6 +1805,18 @@ export class ACTPClient { (agentBlock && typeof agentBlock.slug === 'string' ? agentBlock.slug : undefined) || (fileSlug !== 'AGIRAILS' ? fileSlug : undefined); + // AIP-18 DEC-3: a pure buyer (intent: pay) is never anchored on-chain — + // its config lives in the local file + the agirails.app DB, not the + // AgentRegistry. Chain-based drift/reconcile does not apply, so skip it + // rather than emit a misleading "not published / config ahead" warning + // on every client startup. (DB-based buyer reconcile is future work.) + const intentVal = + (typeof frontmatter.intent === 'string' ? frontmatter.intent : undefined) || + (agentBlock && typeof agentBlock.intent === 'string' ? agentBlock.intent : undefined); + if (intentVal?.toLowerCase() === 'pay') { + return; + } + const autoSync = process.env.ACTP_AUTO_SYNC !== '0' && process.env.ACTP_AUTO_SYNC !== 'false'; diff --git a/src/cli/commands/publish.ts b/src/cli/commands/publish.ts index 403457c..23befdd 100644 --- a/src/cli/commands/publish.ts +++ b/src/cli/commands/publish.ts @@ -563,6 +563,22 @@ async function runPublish( 'Pay-only agent: skipping on-chain registration. ' + 'Identity is your wallet + agirails.app profile.' ); + + // Resolve the Smart Wallet address for a wallet:auto buyer so the DB + // link (and the buyer-link marker) record the address the agent actually + // transacts from — not the bare EOA signer. Providers get this from + // on-chain activation; a buyer skips activation, so read the address + // `actp init` derived into .actp/config.json. (Falls back to the EOA if + // unknown.) This makes buyer attribution match on-chain payments. + try { + const cfg = loadConfig(projectRoot); + if (cfg.wallet === 'auto' && cfg.smartWallet) { + smartWalletAddress = cfg.smartWallet; + } + } catch { + // Best-effort — the EOA fallback below still produces a valid link. + } + // Write the buyer-link marker so the SDK's auto-wallet gate grants // gas-sponsored transactions to this linked buyer (AIP-18 DEC-8) — a // buyer has no on-chain configHash and no pending-publish, so without @@ -579,7 +595,7 @@ async function runPublish( { version: 1, slug: v4Config!.slug, - wallet: (walletAddress || '').toLowerCase(), + wallet: (smartWalletAddress || walletAddress || '').toLowerCase(), linkedAt: new Date().toISOString(), }, buyerLinkActpDir, @@ -753,6 +769,13 @@ async function runPublish( // the next re-publish. intent: v4Config.intent, capabilities: v4Config.services.map(s => s.type), + // Buyer-side discovery metadata (AIP-18 §7.1, opt-in semi-public) + // so the dashboard/profile reflects what the buyer requests. + // budget is deliberately NOT synced — it is a private operational + // cap (DEC-2) and never leaves the owner's machine. + ...(v4Config.servicesNeeded?.length + ? { services_needed: v4Config.servicesNeeded } + : {}), pricing: { model: "fixed", amount: String(v4Config.pricing.base),