@toon-protocol/rig 3.5.2 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/standalone/network-bootstrap.ts"],"sourcesContent":["/**\n * Network bootstrap for the standalone (embedded-client) rig path (#264):\n * resolve the payment topology — uplink, ILP destinations/routes, settlement\n * chain and its on-chain parameters — from the network itself instead of\n * hand-fed constants.\n *\n * Sources, in strict precedence order (first hit wins, per field):\n *\n * 1. EXPLICIT USER CONFIG — env vars / shared client-config file fields.\n * 2. LIVE kind:10032 ANNOUNCE — the payment peer's `IlpPeerInfo` event\n * discovered on the relay-origin (the relay the paid command resolved\n * via `rig remote`, i.e. what the user pointed rig at). The announce\n * carries `btpEndpoint`/`httpEndpoint` (uplink), `ilpAddress` (channel\n * anchor), `supportedChains` + `settlementAddresses` (settlement), and\n * the out-of-band `routes` map (`{publish, store}` ILP destinations).\n * 3. GENESIS SEED — `@toon-protocol/core`'s committed genesis peer seed\n * (core >= 3.1.1 ships the live devnet apex), the offline fallback when\n * the relay is unreachable or serves no valid announce.\n *\n * Chain-level parameters the announce does NOT carry (EVM TokenNetwork\n * contract, token address, RPC URL) are derived per selected chain:\n * explicit config > announce (`tokenNetworks`/`preferredTokens`, when a peer\n * announces them) > core's deterministic chain presets (`CHAIN_PRESETS`,\n * matched by chain id — e.g. `evm:31337` is the Foundry/anvil deploy whose\n * TOON contract addresses are deterministic; `evm:base:84532` and\n * `solana:devnet` are the public networks the TOON devnet settles on since\n * the zone's self-hosted chain nodes were retired, 2026-07).\n *\n * Settlement-chain selection (#260 root cause 4) — simple and predictable:\n *\n * 1. EXPLICIT — `TOON_CLIENT_CHAIN` env / `chain` config field (family or\n * full chain id), or the first entry of an explicit `supportedChains`.\n * 2. PERSISTED CHANNEL — the chain of the most recently used live channel\n * recorded for this identity in the #262 channel map (a channel there\n * means collateral is already locked on that chain).\n * 3. FUNDED — the first announced chain (EVM or Solana, in announce\n * order) where the identity's token balance is > 0 (one `eth_call` /\n * `getTokenAccountsByOwner` per candidate).\n * 4. DEFAULT — the first EVM chain the peer announces (else the first\n * announced chain), with a printed rationale.\n *\n * This module is pure Node + `@toon-protocol/core` (rig's own core 3.x —\n * distinct from the embedded client's internal core; see\n * `../cli/standalone-mode.ts` for the coexistence note).\n */\n\nimport {\n CHAIN_PRESETS,\n GenesisPeerLoader,\n isEventExpired,\n parseIlpPeerInfo,\n resolveClientNetwork,\n type GenesisPeer,\n type IlpPeerInfo,\n} from '@toon-protocol/core';\nimport {\n queryRelay,\n type NostrEvent,\n type WebSocketFactory,\n} from '../remote-state.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** kind:10032 — ILP peer info announcement (mirrors core's constant). */\nexport const ILP_PEER_INFO_KIND = 10032;\n\n/** Publish/store ILP route hints riding out-of-band in announce content. */\nexport interface AnnouncedRoutes {\n publish?: string;\n store?: string;\n}\n\n/**\n * One announced capability with its FLAT route price (out-of-band\n * `capabilities` content field, e.g. `{capability:'os.publish',\n * address:'g.proxy.relay', price:'1000'}`). The connector (≥3.34.6) gates\n * every paid packet at the destination route's price: a balance-proof claim\n * advancing the channel by less is rejected (F06), so per-packet fees to\n * `address` must be floored at `price`.\n */\nexport interface AnnouncedCapability {\n /** Capability name, e.g. `os.publish` / `os.store`. */\n capability: string;\n /** ILP destination address the capability (and its price) applies to. */\n address: string;\n /** Flat per-packet price, base-unit non-negative integer string. */\n price: string;\n}\n\n/** One schema-valid, unexpired kind:10032 announce seen on the relay. */\nexport interface AnnouncedPeer {\n /** Announcing identity (event author, hex). */\n pubkey: string;\n /**\n * Parsed + validated `IlpPeerInfo` (rig's core 3.x parser), widened with\n * the optional operator-notice field (toon-protocol/toon#183, #78).\n * `notice` is typed `unknown`, not a schema field: the installed core's\n * `parseIlpPeerInfo` builds its result field by field, so `notice` only\n * appears here once core publishes it (see `./notice.ts`'s module doc for\n * that status). `./notice.ts` validates the value as untrusted input\n * either way.\n */\n info: IlpPeerInfo & { notice?: unknown };\n /** Out-of-band `routes` content field, when present and well-formed. */\n routes?: AnnouncedRoutes;\n /** Out-of-band `capabilities` content field (route prices), when valid. */\n capabilities?: AnnouncedCapability[];\n /**\n * Out-of-band `routePrices` map (ILP address → base-unit price string) —\n * the shape the live connector publishes, alongside/instead of\n * `capabilities`. Each node prices its OWN routes, so the store route's\n * price comes from the store node's announce, not the payment peer's.\n */\n routePrices?: Record<string, string>;\n /**\n * Out-of-band `minaTokenIds` content field: chain id → Mina token id\n * (decimal string). The zkApp address for a Mina channel already rides the\n * standard `tokenNetworks` map (mirroring Solana's program id), but the Mina\n * token id — the deployment-specific fungible-token discriminator a custom\n * USDC channel opens on — has no home in core's kind:10032 wire schema, so\n * the connector advertises it here (a content ride-along, like `routes`).\n * Absent for native-MINA settlement (token id defaults to '1' downstream).\n */\n minaTokenIds?: Record<string, string>;\n /**\n * Out-of-band `chainRpcUrls` content field: chain id → JSON-RPC / GraphQL\n * endpoint the deployment KNOWS works for that chain. Core's baked chain\n * presets carry a default RPC per chain, but a baked default can be a stale\n * or broken endpoint (e.g. Base Sepolia's `sepolia.base.org` load balancer\n * fails openChannel→setTotalDeposit with InvalidChannelState) — the same\n * stale-constant failure mode as the base-sepolia preset bug (#103). So the\n * live deployment advertises its OWN working RPC here and the client prefers\n * it over the preset (see {@link resolveChainSettlement}). A content\n * ride-along, like `routes` — core's kind:10032 wire schema is unchanged.\n */\n chainRpcUrls?: Record<string, string>;\n /** Announce timestamp (freshness tiebreaker). */\n createdAt: number;\n}\n\n/** Per-chain settlement parameters resolved for the embedded client. */\nexport interface ChainSettlement {\n /** Chain id as announced/configured, e.g. `evm:31337`. */\n chain: string;\n /** `evm` | `solana` | `mina` | … (first chain-id segment). */\n family: string;\n /** JSON-RPC / GraphQL endpoint, when derivable. */\n rpcUrl?: string;\n /** Preferred token (USDC / SPL mint) address, when derivable. */\n tokenAddress?: string;\n /** EVM TokenNetwork contract, when derivable (EVM only). */\n tokenNetwork?: string;\n /** Solana payment-channel program id, when derivable (Solana only). */\n programId?: string;\n /** Mina payment-channel zkApp address, when derivable (Mina only). */\n zkAppAddress?: string;\n /** Mina token id (decimal string), when derivable (Mina only). */\n minaTokenId?: string;\n /** Mina network id for the account/Schnorr prefix (Mina only). */\n networkId?: 'devnet' | 'mainnet';\n}\n\n/** Why a settlement chain was selected (documented rule, in order). */\nexport type ChainSelectionReason =\n | 'explicit'\n | 'persisted-channel'\n | 'funded'\n | 'default';\n\n/** The selected settlement chain plus its rationale. */\nexport interface ChainSelection {\n chain: string;\n reason: ChainSelectionReason;\n /** One human-readable line explaining the pick. */\n detail: string;\n}\n\n// ---------------------------------------------------------------------------\n// Announce discovery\n// ---------------------------------------------------------------------------\n\n/** Default bounded wait for the kind:10032 relay query. */\nexport const DISCOVERY_TIMEOUT_MS = 5000;\n\nfunction parseRoutes(content: string): AnnouncedRoutes | undefined {\n try {\n const parsed = JSON.parse(content) as { routes?: unknown };\n const routes = parsed.routes;\n if (typeof routes !== 'object' || routes === null) return undefined;\n const { publish, store } = routes as Record<string, unknown>;\n const out: AnnouncedRoutes = {\n ...(typeof publish === 'string' && publish.length > 0 ? { publish } : {}),\n ...(typeof store === 'string' && store.length > 0 ? { store } : {}),\n };\n return out.publish || out.store ? out : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Parse the announce's `routePrices` map (ILP address → base-unit price\n * string), the shape the live connector actually publishes.\n *\n * This is a SEPARATE surface from `capabilities`, not a rename of it: the\n * connector emits `routePrices` on every kind:10032 while `capabilities` is\n * the older, richer array carrying a capability name alongside the price.\n * A fleet publishing only `routePrices` left the price floor unset, which\n * the store route surfaces as `F03 - claim rejected: advances value by 0,\n * less than this route's price` — the floor exists precisely so a\n * bytes-derived fee that rounds to nothing is lifted to what the route\n * charges. Values are validated like every other announced amount.\n */\nfunction parseRoutePrices(\n content: string\n): Record<string, string> | undefined {\n try {\n const parsed = JSON.parse(content) as { routePrices?: unknown };\n const raw = parsed.routePrices;\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n return undefined;\n }\n const out: Record<string, string> = {};\n for (const [address, price] of Object.entries(\n raw as Record<string, unknown>\n )) {\n if (address.length > 0 && typeof price === 'string' && /^\\d+$/.test(price)) {\n out[address] = price;\n }\n }\n return Object.keys(out).length > 0 ? out : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Parse the announce's out-of-band `capabilities` array (route prices).\n * Malformed entries are skipped; a `price` must be a non-negative integer\n * string (the same base-unit serialization every other announced amount\n * uses). Returns undefined when nothing valid is present.\n */\nfunction parseCapabilities(content: string): AnnouncedCapability[] | undefined {\n try {\n const parsed = JSON.parse(content) as { capabilities?: unknown };\n if (!Array.isArray(parsed.capabilities)) return undefined;\n const out: AnnouncedCapability[] = [];\n for (const entry of parsed.capabilities) {\n if (typeof entry !== 'object' || entry === null) continue;\n const { capability, address, price } = entry as Record<string, unknown>;\n if (\n typeof capability === 'string' &&\n typeof address === 'string' &&\n address.length > 0 &&\n typeof price === 'string' &&\n /^\\d+$/.test(price)\n ) {\n out.push({ capability, address, price });\n }\n }\n return out.length > 0 ? out : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Parse the announce's out-of-band `minaTokenIds` map (chain id → Mina token\n * id decimal string). Non-string / non-decimal entries are skipped; keys are\n * accepted verbatim (they are matched against the selected chain id later).\n * Returns undefined when nothing valid is present.\n */\nfunction parseMinaTokenIds(content: string): Record<string, string> | undefined {\n try {\n const parsed = JSON.parse(content) as { minaTokenIds?: unknown };\n const map = parsed.minaTokenIds;\n if (typeof map !== 'object' || map === null || Array.isArray(map)) {\n return undefined;\n }\n const out: Record<string, string> = {};\n for (const [chain, tokenId] of Object.entries(map as Record<string, unknown>)) {\n if (typeof tokenId === 'string' && /^\\d+$/.test(tokenId)) {\n out[chain] = tokenId;\n }\n }\n return Object.keys(out).length > 0 ? out : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Parse the announce's out-of-band `chainRpcUrls` map (chain id → RPC/GraphQL\n * endpoint). Non-string / non-http(s) entries are skipped; keys are accepted\n * verbatim (matched against the selected chain id later). Returns undefined\n * when nothing valid is present.\n */\nfunction parseChainRpcUrls(content: string): Record<string, string> | undefined {\n try {\n const parsed = JSON.parse(content) as { chainRpcUrls?: unknown };\n const map = parsed.chainRpcUrls;\n if (typeof map !== 'object' || map === null || Array.isArray(map)) {\n return undefined;\n }\n const out: Record<string, string> = {};\n for (const [chain, url] of Object.entries(map as Record<string, unknown>)) {\n if (typeof url === 'string' && /^(https?|wss?):\\/\\//.test(url)) {\n out[chain] = url;\n }\n }\n return Object.keys(out).length > 0 ? out : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Query `relayUrl` for kind:10032 announces and return the latest\n * schema-valid, unexpired announce per author. Invalid/expired events are\n * skipped silently (the relay serves plenty of non-peer 10032 experiments).\n */\nexport async function discoverAnnouncedPeers(\n relayUrl: string,\n options: {\n timeoutMs?: number;\n webSocketFactory?: WebSocketFactory;\n } = {}\n): Promise<AnnouncedPeer[]> {\n const factory =\n options.webSocketFactory ?? defaultDiscoveryWebSocketFactory();\n const events = await queryRelay(\n relayUrl,\n { kinds: [ILP_PEER_INFO_KIND], limit: 100 },\n options.timeoutMs ?? DISCOVERY_TIMEOUT_MS,\n factory\n );\n\n const latestByAuthor = new Map<string, NostrEvent>();\n for (const event of events) {\n if (event.kind !== ILP_PEER_INFO_KIND) continue;\n const prev = latestByAuthor.get(event.pubkey);\n if (!prev || event.created_at > prev.created_at) {\n latestByAuthor.set(event.pubkey, event);\n }\n }\n\n const peers: AnnouncedPeer[] = [];\n for (const event of latestByAuthor.values()) {\n if (isEventExpired(event as Parameters<typeof isEventExpired>[0])) {\n continue;\n }\n let info: IlpPeerInfo;\n try {\n info = parseIlpPeerInfo(event as Parameters<typeof parseIlpPeerInfo>[0]);\n } catch {\n continue; // not a schema-valid IlpPeerInfo announce\n }\n const routes = parseRoutes(event.content);\n const capabilities = parseCapabilities(event.content);\n const routePrices = parseRoutePrices(event.content);\n const minaTokenIds = parseMinaTokenIds(event.content);\n const chainRpcUrls = parseChainRpcUrls(event.content);\n peers.push({\n pubkey: event.pubkey,\n info,\n ...(routes ? { routes } : {}),\n ...(capabilities ? { capabilities } : {}),\n ...(routePrices ? { routePrices } : {}),\n ...(minaTokenIds ? { minaTokenIds } : {}),\n ...(chainRpcUrls ? { chainRpcUrls } : {}),\n createdAt: event.created_at,\n });\n }\n return peers;\n}\n\nfunction defaultDiscoveryWebSocketFactory(): WebSocketFactory {\n return (url) => {\n const ctor = (\n globalThis as {\n WebSocket?: new (url: string) => ReturnType<WebSocketFactory>;\n }\n ).WebSocket;\n if (!ctor) {\n throw new Error(\n 'No global WebSocket constructor (Node >= 22 required) for announce discovery'\n );\n }\n return new ctor(url);\n };\n}\n\n/**\n * Pick THE payment peer among discovered announces:\n *\n * 1. the announce authored by a genesis-seed pubkey (the committed apex\n * identity — the strongest signal),\n * 2. else an announce that can actually take paid writes: has an uplink\n * endpoint AND `settlementAddresses`, preferring one whose own\n * `ilpAddress` is its `routes.publish` (the publish edge — that is\n * where rig pays first),\n * 3. freshest `created_at` breaks remaining ties.\n */\nexport function pickPaymentPeer(\n peers: AnnouncedPeer[],\n seedPubkeys: readonly string[]\n): AnnouncedPeer | undefined {\n const seeded = peers.filter((p) => seedPubkeys.includes(p.pubkey));\n if (seeded.length > 0) {\n return seeded.sort((a, b) => b.createdAt - a.createdAt)[0];\n }\n\n const payable = peers.filter(\n (p) =>\n (p.info.httpEndpoint || p.info.btpEndpoint) &&\n p.info.settlementAddresses &&\n Object.keys(p.info.settlementAddresses).length > 0\n );\n if (payable.length === 0) return undefined;\n const publishEdges = payable.filter(\n (p) => p.routes?.publish !== undefined && p.routes.publish === p.info.ilpAddress\n );\n const pool = publishEdges.length > 0 ? publishEdges : payable;\n return pool.sort((a, b) => b.createdAt - a.createdAt)[0];\n}\n\n// ---------------------------------------------------------------------------\n// Per-chain settlement derivation\n// ---------------------------------------------------------------------------\n\n/** Numeric chain id of an `evm:<id>` / `evm:<name>:<id>` chain key. */\nexport function evmChainIdOf(chain: string): number | undefined {\n const parts = chain.split(':');\n if (parts[0] !== 'evm') return undefined;\n const raw = parts.length >= 3 ? parts[2] : parts[1];\n const id = Number.parseInt(raw ?? '', 10);\n return Number.isNaN(id) ? undefined : id;\n}\n\n/**\n * Core chain preset matching an EVM chain key by numeric chain id. Presets\n * carry the DETERMINISTIC TOON contract addresses per chain (e.g. the\n * `anvil` 31337 Foundry deploy), which is what makes `tokenNetwork`\n * derivable without the announce carrying it. Base Sepolia (84532) resolves\n * from core's `base-sepolia` preset directly (>=3.1.1 carries the current\n * public 6-decimal USDC + TokenNetwork; the earlier stale addresses that this\n * function used to correct locally are gone).\n */\nexport function evmPresetForChain(chain: string):\n | { rpcUrl: string; usdcAddress: string; tokenNetworkAddress: string }\n | undefined {\n const id = evmChainIdOf(chain);\n if (id === undefined) return undefined;\n for (const preset of Object.values(CHAIN_PRESETS)) {\n if (preset.chainId === id) {\n return {\n rpcUrl: preset.rpcUrl,\n usdcAddress: preset.usdcAddress,\n tokenNetworkAddress: preset.tokenNetworkAddress,\n };\n }\n }\n return undefined;\n}\n\n/**\n * Public-cluster Solana preset matching a `solana:<cluster>` chain key. Draws\n * from core's client network presets (`resolveClientNetwork`) — the same\n * tables the daemon and townhouse resolve — so the deployed public-devnet\n * program/mint and the mainnet-beta RPC + Circle USDC mint come from ONE\n * source. `programId` is present only where the TOON payment-channel program\n * is actually deployed (public devnet today; mainnet-beta once deployed).\n * Announce-provided values and explicit config always take precedence over\n * these presets (see {@link resolveChainSettlement}) — that precedence is\n * what protected the devnet's former self-hosted validator, whose regenerated\n * program id rode the announce (retired 2026-07; the devnet now settles on\n * the public clusters).\n */\nexport function solanaPresetForChain(chain: string):\n | { rpcUrl?: string; tokenMint?: string; programId?: string }\n | undefined {\n const [family, cluster] = chain.split(':');\n if (family !== 'solana' || !cluster) return undefined;\n const tier =\n cluster === 'mainnet-beta'\n ? 'mainnet'\n : cluster === 'devnet'\n ? 'devnet'\n : cluster === 'testnet'\n ? 'testnet'\n : undefined;\n if (!tier) return undefined;\n const presets = resolveClientNetwork(tier);\n // Look up by the exact chain key: the `testnet` tier resolves to the\n // deployed `solana:devnet` cluster, so `solana:testnet` correctly finds\n // nothing (TOON has no deployment on Solana's testnet cluster).\n const rpcUrl = presets.chainRpcUrls[chain];\n const tokenMint = presets.preferredTokens[chain];\n const programId =\n presets.solanaChannel && presets.supportedChains.includes(chain)\n ? presets.solanaChannel.programId\n : undefined;\n if (!rpcUrl && !tokenMint && !programId) return undefined;\n return {\n ...(rpcUrl ? { rpcUrl } : {}),\n ...(tokenMint ? { tokenMint } : {}),\n ...(programId ? { programId } : {}),\n };\n}\n\n/**\n * Public-network Mina preset matching a `mina:<network>` chain key. Draws from\n * core's client network presets (`resolveClientNetwork`) — the SAME tables the\n * daemon and townhouse resolve — so the deployed devnet graphqlUrl and network\n * id come from ONE source.\n *\n * IMPORTANT — the preset supplies only the NON-deployment-specific defaults\n * (`graphqlUrl`, `networkId`): the public minascan endpoint and the network id\n * derivable from the chain key. It also carries the preset's `zkAppAddress`\n * (and, in a future core release, a `tokenId`) as an OFFLINE-ONLY fallback, but\n * the payment-channel zkApp and the custom-token id are DEPLOYMENT-specific and\n * go stale on redeploy — exactly the drift that produced the stale\n * base-sepolia/solana bug (#103). So the live announce (which advertises the\n * deployment's own zkApp via `tokenNetworks` and its token id via\n * `minaTokenIds`) ALWAYS takes precedence over these preset values in\n * {@link resolveChainSettlement}. `undefined` for non-`mina:*` chains and for\n * networks with no deployed TOON zkApp (e.g. `mina:mainnet`).\n */\nexport function minaPresetForChain(chain: string):\n | {\n graphqlUrl?: string;\n zkAppAddress?: string;\n tokenId?: string;\n networkId?: 'devnet' | 'mainnet';\n }\n | undefined {\n const [family, network] = chain.split(':');\n if (family !== 'mina' || !network) return undefined;\n // Mina has no separate `testnet` network — the deployment is on `mina:devnet`\n // (the core `testnet` tier resolves to the same devnet zkApp, so `mina:testnet`\n // is not a real chain and must resolve to nothing, mirroring `solana:testnet`).\n const tier =\n network === 'mainnet' ? 'mainnet' : network === 'devnet' ? 'devnet' : undefined;\n if (!tier) return undefined;\n const presets = resolveClientNetwork(tier);\n const mina = presets.minaChannel;\n if (!mina) return undefined;\n // Core >= 3.1.1's `minaChannel` carries { graphqlUrl, zkAppAddress,\n // networkId }; a `tokenId` field is read opportunistically so a future core\n // release can add it without a client change (announce still wins).\n const tokenId = (mina as { tokenId?: string }).tokenId;\n return {\n ...(mina.graphqlUrl ? { graphqlUrl: mina.graphqlUrl } : {}),\n ...(mina.zkAppAddress ? { zkAppAddress: mina.zkAppAddress } : {}),\n ...(tokenId ? { tokenId } : {}),\n ...(mina.networkId ? { networkId: mina.networkId } : {}),\n };\n}\n\n/** Explicit per-chain maps from the shared client-config file. */\nexport interface ExplicitChainConfig {\n chainRpcUrls?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n tokenNetworks?: Record<string, string>;\n}\n\n/**\n * Resolve one chain's settlement parameters: explicit config > announce >\n * core chain preset. Fields stay undefined when no source covers them —\n * callers decide whether that is fatal (see\n * {@link TokenNetworkUnderivableError} /\n * {@link SolanaChannelUnderivableError}).\n *\n * For `solana:*` chains the chain-keyed maps carry the SPL analogues: the\n * `preferredTokens` entry is the token mint, and the `tokenNetworks` entry is\n * the payment-channel PROGRAM id (the map is chain-keyed settlement-contract\n * addressing — TokenNetwork contract on EVM, channel program on Solana),\n * surfaced as {@link ChainSettlement.programId}.\n */\nexport function resolveChainSettlement(\n chain: string,\n explicit: ExplicitChainConfig,\n announce?: AnnouncedPeer\n): ChainSettlement {\n const family = chain.split(':')[0] ?? chain;\n const evmPreset = family === 'evm' ? evmPresetForChain(chain) : undefined;\n const solPreset = family === 'solana' ? solanaPresetForChain(chain) : undefined;\n const minaPreset = family === 'mina' ? minaPresetForChain(chain) : undefined;\n\n const rpcUrl =\n explicit.chainRpcUrls?.[chain] ??\n // The live deployment's advertised RPC beats the baked preset default: a\n // preset RPC can be a stale/broken endpoint (e.g. Base Sepolia's\n // sepolia.base.org fails setTotalDeposit) while the announce carries the\n // deployment's known-good URL. Same drift lesson as #103.\n announce?.chainRpcUrls?.[chain] ??\n evmPreset?.rpcUrl ??\n solPreset?.rpcUrl ??\n // For Mina the \"RPC\" is the GraphQL endpoint (non-deployment-specific).\n minaPreset?.graphqlUrl;\n const tokenAddress =\n explicit.preferredTokens?.[chain] ??\n announce?.info.preferredTokens?.[chain] ??\n (evmPreset?.usdcAddress || undefined) ??\n solPreset?.tokenMint;\n const tokenNetwork =\n family === 'evm'\n ? (explicit.tokenNetworks?.[chain] ??\n announce?.info.tokenNetworks?.[chain] ??\n (evmPreset?.tokenNetworkAddress || undefined))\n : undefined;\n const programId =\n family === 'solana'\n ? (explicit.tokenNetworks?.[chain] ??\n announce?.info.tokenNetworks?.[chain] ??\n solPreset?.programId)\n : undefined;\n // Mina: the settlement-contract slot (`tokenNetworks`, chain-keyed) carries\n // the payment-channel zkApp address — the announce advertises it exactly as\n // Solana advertises its program id. The token id rides the out-of-band\n // `minaTokenIds` content map. Both prefer explicit config > announce > the\n // (drift-prone) preset fallback; the graphqlUrl/networkId come from the\n // preset (or the derived chain key).\n const zkAppAddress =\n family === 'mina'\n ? (explicit.tokenNetworks?.[chain] ??\n announce?.info.tokenNetworks?.[chain] ??\n minaPreset?.zkAppAddress)\n : undefined;\n const minaTokenId =\n family === 'mina'\n ? (announce?.minaTokenIds?.[chain] ?? minaPreset?.tokenId)\n : undefined;\n const networkId =\n family === 'mina'\n ? (minaPreset?.networkId ??\n (chain.split(':')[1] === 'mainnet' ? 'mainnet' : 'devnet'))\n : undefined;\n\n return {\n chain,\n family,\n ...(rpcUrl ? { rpcUrl } : {}),\n ...(tokenAddress ? { tokenAddress } : {}),\n ...(tokenNetwork ? { tokenNetwork } : {}),\n ...(programId ? { programId } : {}),\n ...(zkAppAddress ? { zkAppAddress } : {}),\n ...(minaTokenId ? { minaTokenId } : {}),\n ...(networkId ? { networkId } : {}),\n };\n}\n\n/** An EVM chain was selected but its TokenNetwork cannot be derived. */\nexport class TokenNetworkUnderivableError extends Error {\n constructor(chain: string, announce: AnnouncedPeer | undefined, relayUrl: string) {\n const announceRef = announce\n ? `the kind:10032 announce from ${announce.pubkey.slice(0, 16)}… on ${relayUrl}`\n : `no kind:10032 announce was found on ${relayUrl}`;\n super(\n `cannot derive the TokenNetwork contract for settlement chain ` +\n `\"${chain}\": ${announceRef} carries no tokenNetworks[\"${chain}\"], ` +\n `and no built-in chain preset matches its chain id — add ` +\n `tokenNetworks[\"${chain}\"] to the client config (or pick another ` +\n `chain via TOON_CLIENT_CHAIN / the chain config field)`\n );\n this.name = 'TokenNetworkUnderivableError';\n }\n}\n\n/**\n * A Solana chain was selected but its channel parameters cannot be derived.\n * The missing pieces are named so the error is directly actionable: a\n * `solanaChannel` config object always works; the chain-keyed maps\n * (`tokenNetworks` = program id, `preferredTokens` = mint,\n * `chainRpcUrls` = RPC) work per-chain.\n */\nexport class SolanaChannelUnderivableError extends Error {\n constructor(\n chain: string,\n missing: string[],\n announce: AnnouncedPeer | undefined,\n relayUrl: string\n ) {\n const announceRef = announce\n ? `the kind:10032 announce from ${announce.pubkey.slice(0, 16)}… on ${relayUrl} does not carry them`\n : `no kind:10032 announce was found on ${relayUrl}`;\n super(\n `cannot derive the Solana channel parameters for settlement chain ` +\n `\"${chain}\" (missing: ${missing.join(', ')}): ${announceRef}, and ` +\n `no built-in preset covers this cluster — add a solanaChannel ` +\n `{ rpcUrl, programId, tokenMint } object to the client config (or ` +\n `set tokenNetworks[\"${chain}\"] = <program id>, ` +\n `preferredTokens[\"${chain}\"] = <mint>, chainRpcUrls[\"${chain}\"]), ` +\n `or pick another chain via TOON_CLIENT_CHAIN / the chain config field`\n );\n this.name = 'SolanaChannelUnderivableError';\n }\n}\n\n/**\n * A Mina chain was selected but its channel parameters cannot be derived. The\n * missing pieces are named so the error is directly actionable: an explicit\n * `minaChannel` config object always works; the live announce normally\n * supplies the zkApp (`tokenNetworks[\"mina:…\"]`) and token id\n * (`minaTokenIds[\"mina:…\"]`), while the graphqlUrl comes from a core preset.\n */\nexport class MinaChannelUnderivableError extends Error {\n constructor(\n chain: string,\n missing: string[],\n announce: AnnouncedPeer | undefined,\n relayUrl: string\n ) {\n const announceRef = announce\n ? `the kind:10032 announce from ${announce.pubkey.slice(0, 16)}… on ${relayUrl} does not carry them`\n : `no kind:10032 announce was found on ${relayUrl}`;\n super(\n `cannot derive the Mina channel parameters for settlement chain ` +\n `\"${chain}\" (missing: ${missing.join(', ')}): ${announceRef}, and ` +\n `no built-in preset covers this network — add a minaChannel ` +\n `{ graphqlUrl, zkAppAddress, tokenId, networkId } object to the client ` +\n `config (or set tokenNetworks[\"${chain}\"] = <zkApp address> and ` +\n `chainRpcUrls[\"${chain}\"] = <graphql url>), or pick another chain via ` +\n `TOON_CLIENT_CHAIN / the chain config field`\n );\n this.name = 'MinaChannelUnderivableError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Settlement-chain selection (#260 root cause 4)\n// ---------------------------------------------------------------------------\n\n/** The slice of a #262 channel-map record chain selection consumes. */\nexport interface ChannelRecordLike {\n chain: string;\n lastUsedAt: string;\n /** True when the withdraw flow closed/settled the channel. */\n closed: boolean;\n}\n\n/** Async token-balance probe (injectable; default is a raw `eth_call`). */\nexport type EvmBalanceProbe = (args: {\n rpcUrl: string;\n tokenAddress: string;\n owner: string;\n}) => Promise<bigint>;\n\n/**\n * Async SPL token-balance probe (injectable; default is a raw\n * `getTokenAccountsByOwner`). `tokenAddress` is the SPL mint and `owner`\n * the identity's base58 Solana address — the same argument shape as\n * {@link EvmBalanceProbe} so both feed the one funded-chain loop.\n */\nexport type SolanaBalanceProbe = (args: {\n rpcUrl: string;\n tokenAddress: string;\n owner: string;\n}) => Promise<bigint>;\n\nexport interface SelectChainOptions {\n /**\n * Explicit chain choice: full chain id (`evm:31337`), a family\n * (`evm` | `solana` | `mina`) matched against announced chains, or the\n * first entry of an explicit `supportedChains` config.\n */\n explicitChain?: string;\n /** Chains the payment peer announces (announce order preserved). */\n announcedChains: readonly string[];\n /** #262 channel-map records for this identity (any anchor). */\n records?: readonly ChannelRecordLike[];\n /** Identity's EVM address (funded-chain probe, `evm:*` candidates). */\n evmAddress?: string;\n /**\n * Identity's base58 Solana address (funded-chain probe, `solana:*`\n * candidates). Absent (e.g. the identity's Ed25519 key could not be\n * derived) means Solana chains are simply not probed.\n */\n solanaAddress?: string;\n /** Per-chain settlement resolver (for RPC/token of probe candidates). */\n resolveSettlement: (chain: string) => ChainSettlement;\n /** EVM balance probe; probe errors just skip the candidate. */\n probeBalance?: EvmBalanceProbe;\n /** Solana balance probe; probe errors just skip the candidate. */\n probeSolanaBalance?: SolanaBalanceProbe;\n}\n\n/**\n * Select the settlement chain per the documented rule: explicit >\n * persisted channel > funded chain (EVM + Solana, in announce order) >\n * first announced EVM chain.\n */\nexport async function selectSettlementChain(\n options: SelectChainOptions\n): Promise<ChainSelection> {\n const { explicitChain, announcedChains } = options;\n\n // 1 — explicit config always wins (even when the peer does not announce\n // it: the user may know better than a stale announce).\n if (explicitChain) {\n if (explicitChain.includes(':')) {\n // Chain negotiation is an EXACT string intersection: a full chain id\n // spelled differently from the announce for the SAME EVM chain\n // (`evm:base:84532` vs announced `evm:84532`) would strand the pin and\n // let negotiation fall through to another chain entirely. Align an\n // explicit EVM chain id to the announced spelling (same rule the\n // configured-supportedChains path applies).\n if (!announcedChains.includes(explicitChain)) {\n const chainId = evmChainIdOf(explicitChain);\n const equivalent =\n chainId !== undefined\n ? announcedChains.find((c) => evmChainIdOf(c) === chainId)\n : undefined;\n if (equivalent) {\n return {\n chain: equivalent,\n reason: 'explicit',\n detail:\n `chain ${explicitChain} set by config → announced spelling ` +\n `${equivalent} (same EVM chain id ${chainId})`,\n };\n }\n }\n return {\n chain: explicitChain,\n reason: 'explicit',\n detail: `chain ${explicitChain} set by config`,\n };\n }\n const familyMatch = announcedChains.find(\n (c) => (c.split(':')[0] ?? c) === explicitChain\n );\n if (!familyMatch) {\n throw new Error(\n `configured chain family \"${explicitChain}\" is not announced by the ` +\n `payment peer (announced: ${announcedChains.join(', ') || 'none'}) — ` +\n 'set a full chain id (e.g. \"evm:31337\") to force it'\n );\n }\n return {\n chain: familyMatch,\n reason: 'explicit',\n detail: `chain family \"${explicitChain}\" set by config → ${familyMatch}`,\n };\n }\n\n // 2 — a live persisted channel means collateral is already locked there.\n const live = (options.records ?? [])\n .filter((r) => !r.closed && announcedChains.includes(r.chain))\n .sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));\n const persisted = live[0];\n if (persisted) {\n return {\n chain: persisted.chain,\n reason: 'persisted-channel',\n detail: `existing payment channel on ${persisted.chain} (rig channel map)`,\n };\n }\n\n // 3 — first announced chain where the identity holds tokens, probing\n // EVM and Solana candidates in ANNOUNCE ORDER (the peer's stated\n // preference breaks a both-funded tie). Families without an address or\n // probe are skipped, as are candidates whose RPC/token are underivable.\n const evmChains = announcedChains.filter((c) => c.startsWith('evm:'));\n for (const chain of announcedChains) {\n const family = chain.split(':')[0];\n let probe: EvmBalanceProbe | SolanaBalanceProbe | undefined;\n let owner: string | undefined;\n if (family === 'evm') {\n probe = options.probeBalance;\n owner = options.evmAddress;\n } else if (family === 'solana') {\n probe = options.probeSolanaBalance;\n owner = options.solanaAddress;\n }\n if (!probe || !owner) continue;\n const settlement = options.resolveSettlement(chain);\n if (!settlement.rpcUrl || !settlement.tokenAddress) continue;\n try {\n const balance = await probe({\n rpcUrl: settlement.rpcUrl,\n tokenAddress: settlement.tokenAddress,\n owner,\n });\n if (balance > 0n) {\n return {\n chain,\n reason: 'funded',\n detail: `wallet holds ${balance} token base units on ${chain}`,\n };\n }\n } catch {\n // Unreachable RPC / bad token — not a candidate; fall through.\n }\n }\n\n // 4 — predictable default: the first EVM chain the peer announces.\n const fallback = evmChains[0] ?? announcedChains[0];\n if (!fallback) {\n throw new Error(\n 'the payment peer announces no settlement chains — cannot select a ' +\n 'chain for paid writes (set supportedChains/chain in the client config)'\n );\n }\n return {\n chain: fallback,\n reason: 'default',\n detail: evmChains[0]\n ? `first EVM chain announced by the payment peer`\n : `first chain announced by the payment peer (no EVM chain announced)`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Default balance probes (raw JSON-RPC, no extra deps)\n// ---------------------------------------------------------------------------\n\n/** ERC-20 `balanceOf(address)` selector. */\nconst BALANCE_OF_SELECTOR = '0x70a08231';\n\n/**\n * Read an ERC-20 balance with one raw `eth_call` (keeps the probe free of\n * viem — the embedded client is not started yet when selection runs).\n */\nexport async function evmTokenBalance(args: {\n rpcUrl: string;\n tokenAddress: string;\n owner: string;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n}): Promise<bigint> {\n const fetchImpl = args.fetchImpl ?? fetch;\n const owner = args.owner.replace(/^0x/, '').toLowerCase().padStart(64, '0');\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), args.timeoutMs ?? 5000);\n try {\n const res = await fetchImpl(args.rpcUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'eth_call',\n params: [\n { to: args.tokenAddress, data: `${BALANCE_OF_SELECTOR}${owner}` },\n 'latest',\n ],\n }),\n signal: controller.signal,\n });\n if (!res.ok) {\n throw new Error(`eth_call failed: HTTP ${res.status}`);\n }\n const body = (await res.json()) as { result?: unknown; error?: { message?: string } };\n if (typeof body.result !== 'string') {\n throw new Error(`eth_call failed: ${body.error?.message ?? 'no result'}`);\n }\n return BigInt(body.result === '0x' ? '0x0' : body.result);\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Read an SPL token balance with one raw `getTokenAccountsByOwner` call\n * (keeps the probe free of @solana/web3.js — the embedded client is not\n * started yet when selection runs). `tokenAddress` is the mint; balances\n * are summed across all of the owner's token accounts for that mint (an\n * owner can hold the mint in more than one account).\n */\nexport async function solanaTokenBalance(args: {\n rpcUrl: string;\n tokenAddress: string;\n owner: string;\n fetchImpl?: typeof fetch;\n timeoutMs?: number;\n}): Promise<bigint> {\n const fetchImpl = args.fetchImpl ?? fetch;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), args.timeoutMs ?? 5000);\n try {\n const res = await fetchImpl(args.rpcUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'getTokenAccountsByOwner',\n params: [\n args.owner,\n { mint: args.tokenAddress },\n { encoding: 'jsonParsed' },\n ],\n }),\n signal: controller.signal,\n });\n if (!res.ok) {\n throw new Error(`getTokenAccountsByOwner failed: HTTP ${res.status}`);\n }\n const body = (await res.json()) as {\n result?: { value?: unknown };\n error?: { message?: string };\n };\n const accounts = body.result?.value;\n if (!Array.isArray(accounts)) {\n throw new Error(\n `getTokenAccountsByOwner failed: ${body.error?.message ?? 'no result'}`\n );\n }\n let total = 0n;\n for (const account of accounts) {\n const amount = (\n account as {\n account?: {\n data?: {\n parsed?: { info?: { tokenAmount?: { amount?: unknown } } };\n };\n };\n }\n )?.account?.data?.parsed?.info?.tokenAmount?.amount;\n if (typeof amount === 'string') total += BigInt(amount);\n }\n return total;\n } finally {\n clearTimeout(timer);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Genesis seed access (rig's own core 3.x — live devnet apex since core 3.x)\n// ---------------------------------------------------------------------------\n\n/** The committed genesis peer seed (first entry), if any. */\nexport function loadGenesisSeed(): GenesisPeer | undefined {\n return GenesisPeerLoader.loadGenesisPeers()[0];\n}\n\n/** All committed genesis-seed pubkeys (announce-selection preference). */\nexport function genesisSeedPubkeys(): string[] {\n return GenesisPeerLoader.loadGenesisPeers().map((p) => p.pubkey);\n}\n"],"mappings":"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","names":[]}
package/dist/cli/rig.js CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  readClientConfigFile,
31
31
  resolveAccountIndex,
32
32
  resolveIdentity
33
- } from "../chunk-FFQXFV4W.js";
33
+ } from "../chunk-PWNZWQOH.js";
34
34
  import {
35
35
  ChannelMapStore,
36
36
  RIG_STANDALONE_ENV,
@@ -45,7 +45,7 @@ import {
45
45
  } from "../chunk-B5ISARMU.js";
46
46
  import {
47
47
  minaPresetForChain
48
- } from "../chunk-TTJCBGKW.js";
48
+ } from "../chunk-UZZ2RN7A.js";
49
49
  import {
50
50
  ARWEAVE_GATEWAYS,
51
51
  COMMENT_KIND,
@@ -1098,7 +1098,7 @@ function loadPaidSession(deps, relayUrl) {
1098
1098
  });
1099
1099
  }
1100
1100
  var defaultLoadStandalone = async (options) => {
1101
- const mod = await import("../standalone-mode-LBWGU7NV.js");
1101
+ const mod = await import("../standalone-mode-5MNNJC2D.js");
1102
1102
  return mod.createStandaloneContext(options);
1103
1103
  };
1104
1104
  function identityReport(ctx) {
@@ -1240,13 +1240,11 @@ function parsePushArgs(args) {
1240
1240
  return flags;
1241
1241
  }
1242
1242
  function pointerGateway(env) {
1243
- return (env["RIG_ARWEAVE_GATEWAY"] ?? "https://ar-io.dev").replace(
1244
- /\/+$/,
1245
- ""
1246
- );
1243
+ const preferred = ARWEAVE_GATEWAYS[0] ?? "https://arweave.net";
1244
+ return (env["RIG_ARWEAVE_GATEWAY"] ?? preferred).replace(/\/+$/, "");
1247
1245
  }
1248
- function mirrorGateway(primary) {
1249
- return ARWEAVE_GATEWAYS.find((g) => g !== primary);
1246
+ function mirrorGateways(primary) {
1247
+ return ARWEAVE_GATEWAYS.filter((g) => g.replace(/\/+$/, "") !== primary);
1250
1248
  }
1251
1249
  function planRigPointer(args) {
1252
1250
  const html = generateRigPointerHtml({
@@ -1459,11 +1457,15 @@ async function runPush(args, deps) {
1459
1457
  io2.out(
1460
1458
  `Rig page: ${rigPage2.url}` + (rigPage2.status === "published" ? ` paid ${rigPage2.feePaid}` : "") + " (renders this repo from Arweave)"
1461
1459
  );
1462
- const mirror = rigPage2.txId ? mirrorGateway(gateway) : void 0;
1463
- if (mirror) {
1460
+ const mirrors = rigPage2.txId ? mirrorGateways(gateway) : [];
1461
+ if (mirrors.length > 0 && rigPage2.txId) {
1462
+ const txId = rigPage2.txId;
1464
1463
  io2.out(
1465
- ` also: ${mirror}/${rigPage2.txId} (same page; gateways index fresh uploads at different speeds)`
1464
+ " also: (same page \u2014 try these if the address above does not load; gateways index fresh uploads at different speeds, and any one of them can be down)"
1466
1465
  );
1466
+ for (const mirror of mirrors) {
1467
+ io2.out(` ${mirror}/${txId}`);
1468
+ }
1467
1469
  }
1468
1470
  } else if (rigPage2.detail) {
1469
1471
  io2.err(`rig page: ${rigPage2.detail}`);
@@ -4156,7 +4158,7 @@ function writeEntryConfig(configPath, file) {
4156
4158
  }
4157
4159
  async function loadGenesisSeedDefault() {
4158
4160
  try {
4159
- const { loadGenesisSeed } = await import("../network-bootstrap-CTYIZMP6.js");
4161
+ const { loadGenesisSeed } = await import("../network-bootstrap-27FYE3N7.js");
4160
4162
  return loadGenesisSeed();
4161
4163
  } catch {
4162
4164
  return void 0;
@@ -4681,7 +4683,7 @@ function sharedDevnetOrigin(env, file, originRelays = []) {
4681
4683
  }
4682
4684
  async function loadGenesisSeedDefault2() {
4683
4685
  try {
4684
- const { loadGenesisSeed } = await import("../network-bootstrap-CTYIZMP6.js");
4686
+ const { loadGenesisSeed } = await import("../network-bootstrap-27FYE3N7.js");
4685
4687
  return loadGenesisSeed();
4686
4688
  } catch {
4687
4689
  return void 0;
@@ -5543,7 +5545,7 @@ async function resolveProfileRelay(flagRelay, originRelay, toonRelays) {
5543
5545
  ].filter((u) => u !== void 0 && isWebSocketRelay2(u));
5544
5546
  if (candidates[0] !== void 0) return candidates[0];
5545
5547
  try {
5546
- const { loadGenesisSeed } = await import("../network-bootstrap-CTYIZMP6.js");
5548
+ const { loadGenesisSeed } = await import("../network-bootstrap-27FYE3N7.js");
5547
5549
  const seed = loadGenesisSeed();
5548
5550
  if (seed?.relayUrl && isWebSocketRelay2(seed.relayUrl)) return seed.relayUrl;
5549
5551
  } catch {
@@ -6060,6 +6062,13 @@ function renderMario(mario) {
6060
6062
  }
6061
6063
  var MARIO_PAYMENT_NOTE = "Payment is mARIO from this identity's Solana wallet, charged on-chain by the ar.io registry program \u2014 NOT through TOON ILP payment channels. Fund this wallet with `rig fund --chain solana` (or send $ARIO to the Solana address above).";
6062
6064
  var DVM_PAYMENT_NOTE = "Brokered buy (--via): the DVM pays the mARIO name price from ITS Solana wallet via the kind:5095 job; this wallet needs only dust SOL to spawn the ANT it owns. On the paid path the job fee is enforced by the connector payment proxy in front of the DVM; when --via targets the DVM backend directly the job payment is STUBBED (dev/e2e only). The ArNS purchase itself is real either way, and the name is owned by YOUR ANT from inception.";
6065
+ var ARNS_NULL_PROCESS_ID = "11111111111111111111111111111111";
6066
+ function antProcessIdOf(id) {
6067
+ if (id === null || id === void 0 || id === ARNS_NULL_PROCESS_ID) {
6068
+ return null;
6069
+ }
6070
+ return id;
6071
+ }
6063
6072
  var MIN_ARIO_SDK_VERSION = "4.0.3";
6064
6073
  var ArnsSdkUnavailableError = class extends Error {
6065
6074
  constructor(cause, module = "@ar.io/sdk") {
@@ -6247,7 +6256,11 @@ var defaultLoadArns = async (options) => {
6247
6256
  throw err;
6248
6257
  }
6249
6258
  return {
6250
- processId: raw.processId ?? null,
6259
+ // #79 AC4: the registry writes the all-ones placeholder when no ANT
6260
+ // is attached. The seam contract is "a process id or null", so the
6261
+ // placeholder is normalized to null right here — every caller then
6262
+ // sees "no ANT" instead of a valid-looking System Program address.
6263
+ processId: antProcessIdOf(raw.processId),
6251
6264
  type: raw.type ?? null,
6252
6265
  ...raw.startTimestamp !== void 0 ? { startTimestamp: raw.startTimestamp * 1e3 } : {},
6253
6266
  ...raw.endTimestamp !== void 0 ? { endTimestamp: raw.endTimestamp * 1e3 } : {},
@@ -6737,9 +6750,9 @@ async function runBuy(name, flags, deps) {
6737
6750
  "write"
6738
6751
  );
6739
6752
  if (via !== null) {
6740
- const spawn2 = await signed.sdk.spawnAnt({ name });
6753
+ const spawn3 = await signed.sdk.spawnAnt({ name });
6741
6754
  if (!flags.json) {
6742
- io2.out(`Spawned ANT ${spawn2.processId} (tx ${spawn2.signature})`);
6755
+ io2.out(`Spawned ANT ${spawn3.processId} (tx ${spawn3.signature})`);
6743
6756
  }
6744
6757
  const submit = deps.submitDvmBuyJob ?? defaultSubmitDvmBuyJob;
6745
6758
  const receipt2 = await submit({
@@ -6747,19 +6760,19 @@ async function runBuy(name, flags, deps) {
6747
6760
  name,
6748
6761
  type,
6749
6762
  years,
6750
- processId: spawn2.processId,
6763
+ processId: spawn3.processId,
6751
6764
  nostrSecretKey: signed.nostrSecretKey
6752
6765
  });
6753
6766
  const result2 = {
6754
6767
  registryTxId: receipt2.registryTxId,
6755
- antProcessId: spawn2.processId,
6768
+ antProcessId: spawn3.processId,
6756
6769
  syncAttributesTxId: receipt2.syncAttributesTxId,
6757
6770
  dvmQuotedMario: receipt2.quotedMario
6758
6771
  };
6759
6772
  if (flags.json) {
6760
6773
  io2.emitJson(
6761
6774
  buildJson(true, {
6762
- spawn: { processId: spawn2.processId, signature: spawn2.signature },
6775
+ spawn: { processId: spawn3.processId, signature: spawn3.signature },
6763
6776
  result: result2
6764
6777
  })
6765
6778
  );
@@ -6767,7 +6780,7 @@ async function runBuy(name, flags, deps) {
6767
6780
  io2.out(
6768
6781
  `Registered "${name}" via the DVM \u2014 registry tx ${result2.registryTxId}`
6769
6782
  );
6770
- io2.out(` ANT process (owned by YOU): ${spawn2.processId}`);
6783
+ io2.out(` ANT process (owned by YOU): ${spawn3.processId}`);
6771
6784
  if (receipt2.syncAttributesTxId) {
6772
6785
  io2.out(` Trait sync: ${receipt2.syncAttributesTxId}`);
6773
6786
  } else {
@@ -6781,20 +6794,33 @@ async function runBuy(name, flags, deps) {
6781
6794
  }
6782
6795
  return 0;
6783
6796
  }
6797
+ const spawn2 = await signed.sdk.spawnAnt({ name });
6798
+ if (!flags.json) {
6799
+ io2.out(`Spawned ANT ${spawn2.processId} (tx ${spawn2.signature})`);
6800
+ }
6784
6801
  const receipt = await signed.sdk.buyRecord({
6785
6802
  name,
6786
6803
  type,
6787
- ...years !== null ? { years } : {}
6804
+ ...years !== null ? { years } : {},
6805
+ processId: spawn2.processId
6788
6806
  });
6789
- const result = {
6790
- registryTxId: receipt.id,
6791
- antProcessId: receipt.processId ?? null
6792
- };
6807
+ const antProcessId = antProcessIdOf(receipt.processId ?? spawn2.processId);
6808
+ const result = { registryTxId: receipt.id, antProcessId };
6809
+ const spawnInfo = { processId: spawn2.processId, signature: spawn2.signature };
6810
+ if (antProcessId === null) {
6811
+ const hint = `bought "${name}" (registry tx ${result.registryTxId}) but no ANT is attached \u2014 the spend is on-chain and recoverable, but the name cannot resolve until an ANT is attached to it`;
6812
+ if (flags.json) {
6813
+ io2.emitJson(buildJson(true, { spawn: spawnInfo, result, hint }));
6814
+ } else {
6815
+ io2.err(hint);
6816
+ }
6817
+ return 1;
6818
+ }
6793
6819
  if (flags.json) {
6794
- io2.emitJson(buildJson(true, { result }));
6820
+ io2.emitJson(buildJson(true, { spawn: spawnInfo, result }));
6795
6821
  } else {
6796
6822
  io2.out(`Registered "${name}" \u2014 registry tx ${result.registryTxId}`);
6797
- if (result.antProcessId) io2.out(` ANT process: ${result.antProcessId}`);
6823
+ io2.out(` ANT process: ${result.antProcessId}`);
6798
6824
  io2.out(
6799
6825
  `Point it at content with \`rig name set ${name} <txId>\`, then it resolves at https://${name}.<gateway>/`
6800
6826
  );
@@ -6810,12 +6836,17 @@ async function runSet(name, txId, flags, deps) {
6810
6836
  "read"
6811
6837
  );
6812
6838
  const record = await ctx.sdk.getArNSRecord({ name });
6813
- if (!record || !record.processId) {
6839
+ if (!record) {
6814
6840
  throw new Error(
6815
6841
  `no ArNS record for "${name}" on ${ctx.network} \u2014 buy it first with \`rig name buy ${name}\` (or check \`rig name status ${name}\`)`
6816
6842
  );
6817
6843
  }
6818
- const antProcessId = record.processId;
6844
+ const antProcessId = antProcessIdOf(record.processId);
6845
+ if (antProcessId === null) {
6846
+ throw new Error(
6847
+ `"${name}" is registered on ${ctx.network} but has NO ANT attached (the registry placeholder ${ARNS_NULL_PROCESS_ID}) \u2014 \`rig name set\` needs a real ANT process, so there is nothing to write to. Check \`rig name status ${name}\`; the name has to be re-bound to an ANT before it can resolve.`
6848
+ );
6849
+ }
6819
6850
  const undername = flags.undername ?? null;
6820
6851
  const via = flags.via ?? null;
6821
6852
  const buildJson = (executed, extra = {}) => ({
@@ -6949,9 +6980,12 @@ async function runStatus(name, flags, deps) {
6949
6980
  "read"
6950
6981
  );
6951
6982
  const record = await ctx.sdk.getArNSRecord({ name });
6983
+ const antProcessId = antProcessIdOf(record?.processId);
6984
+ const antMissing = record !== null && antProcessId === null;
6985
+ const antMissingHint = `"${name}" is registered on ${ctx.network} but has NO ANT attached (the registry placeholder ${ARNS_NULL_PROCESS_ID}) \u2014 it cannot resolve, and \`rig name set\` cannot write records against it. The name has to be re-bound to an ANT.`;
6952
6986
  let targets = null;
6953
- if (record?.processId) {
6954
- const ant = await ctx.sdk.ant(record.processId);
6987
+ if (antProcessId !== null) {
6988
+ const ant = await ctx.sdk.ant(antProcessId);
6955
6989
  targets = await ant.getRecords();
6956
6990
  }
6957
6991
  if (flags.json) {
@@ -6962,18 +6996,20 @@ async function runStatus(name, flags, deps) {
6962
6996
  network: ctx.network,
6963
6997
  processId: ctx.processId ?? null,
6964
6998
  registered: record !== null,
6999
+ antMissing,
6965
7000
  identity: ctx.identity,
6966
7001
  solanaAddress: ctx.solanaAddress,
6967
7002
  record: record ? {
6968
- antProcessId: record.processId,
7003
+ antProcessId,
6969
7004
  type: record.type,
6970
7005
  startTimestamp: record.startTimestamp ?? null,
6971
7006
  endTimestamp: record.endTimestamp ?? null,
6972
7007
  undernameLimit: record.undernameLimit ?? null
6973
7008
  } : null,
6974
- targets
7009
+ targets,
7010
+ ...antMissing ? { hint: antMissingHint } : {}
6975
7011
  });
6976
- return 0;
7012
+ return antMissing ? 1 : 0;
6977
7013
  }
6978
7014
  io2.out(`ArNS name "${name}" on ${ctx.network}:`);
6979
7015
  if (!record) {
@@ -6981,7 +7017,7 @@ async function runStatus(name, flags, deps) {
6981
7017
  io2.out(renderIdentityLine(ctx.identity));
6982
7018
  return 0;
6983
7019
  }
6984
- io2.out(` ANT process: ${record.processId ?? "(unknown)"}`);
7020
+ io2.out(` ANT process: ${antProcessId ?? "NONE \u2014 no ANT is attached"}`);
6985
7021
  io2.out(` Type: ${record.type ?? "(unknown)"}`);
6986
7022
  if (record.type === "permabuy") {
6987
7023
  io2.out(" Expiry: never (permabuy)");
@@ -6999,10 +7035,16 @@ async function runStatus(name, flags, deps) {
6999
7035
  ` ${label} \u2192 ${target.transactionId} (ttl ${target.ttlSeconds}s)`
7000
7036
  );
7001
7037
  }
7038
+ } else if (antMissing) {
7039
+ io2.out(" Records: none \u2014 there is no ANT to hold them.");
7002
7040
  } else {
7003
7041
  io2.out(" Records: none set \u2014 point one with `rig name set`.");
7004
7042
  }
7005
7043
  io2.out(renderIdentityLine(ctx.identity));
7044
+ if (antMissing) {
7045
+ io2.err(antMissingHint);
7046
+ return 1;
7047
+ }
7006
7048
  return 0;
7007
7049
  }
7008
7050
  async function runName(args, deps) {
@@ -7134,7 +7176,7 @@ function writeSiteRecord(env, record) {
7134
7176
 
7135
7177
  // src/cli/site.ts
7136
7178
  var MANIFEST_CONTENT_TYPE = "application/x.arweave-manifest+json";
7137
- var DEFAULT_GATEWAY = "https://ar-io.dev";
7179
+ var DEFAULT_GATEWAY = ARWEAVE_GATEWAYS[0] ?? "https://arweave.net";
7138
7180
  var SITE_USAGE = `Usage: rig site <publish|url> [ref] [options]
7139
7181
 
7140
7182
  Serve a pushed repo as a permanent, path-routed website. \`rig push\` already