GOAT Network
AgentKit

GOAT Name Service (.goat)

The gns plugin provides a complete ENS-style .goat namespace stack:

  • Read paths: check availability, get name details, list names owned by an address, estimate registration price.
  • Two-phase ENS-style registration: commitwaitForCommitment (min/max commitment age verified on-chain) → register (with permit or pre-approve) → renew.
  • Profile management: setAddress, setProfileRecords (text + addr multicall), setPrimaryName (reverse record).
  • x402-paid registration: pay for a .goat registration in USDC/USDT on Ethereum, Optimism, BSC, GOAT Mainnet, Base, or Arbitrum. The GNS backend completes registration on GOAT mainnet after the x402 payment is detected.

End users register names via the agentkit-gns CLI; programmatic consumers use the plugin's 15 actions directly.

Quick Start (CLI)

Look up + register a .goat name on GOAT mainnet
# Check availability
npx -p @goatnetwork/agentkit agentkit-gns search alice

# Quote the registration price
npx -p @goatnetwork/agentkit agentkit-gns price alice --years 1 --token USDC

# Register on GOAT mainnet (requires USDC/USDT already on GOAT)
GOAT_PRIVATE_KEY=0x... \
  npx -p @goatnetwork/agentkit agentkit-gns register alice --years 1 --token USDC
x402 register paid from BNB Smart Chain USDT
GOAT_PRIVATE_KEY=0x... \
PAYMENT_CHAIN_RPC_URL=https://bsc-dataseed.bnbchain.org \
  npx -p @goatnetwork/agentkit agentkit-gns x402-register alice \
    --pay-chain 56 \
    --pay-token USDT \
    --pay-token-contract 0x55d398326f99059fF775485246999027B3197955

Actions

Read

ActionNameRiskDescription
checkAvailabilityActiongoat.gns.checkAvailabilityreadIs the .goat name available?
getNameDetailsActiongoat.gns.getNameDetailsreadFull record: owner, expiry, resolver, primary, profile records
getMyNamesActiongoat.gns.getMyNamesreadAll .goat names owned by a given address
estimatePriceActiongoat.gns.estimatePricereadQuote registration price for a name + duration + payment token

Two-phase registration

ActionNameRiskDescription
commitActiongoat.gns.commitmediumGenerate a 32-byte secret, compute commit hash, broadcast on-chain commit
waitForCommitmentActiongoat.gns.waitForCommitmentreadPoll on-chain commit storage until block.timestamp ≥ revealAt (or detect expiry)
registerActiongoat.gns.registerhighReveal-phase register; re-derives the commit hash on-chain and rejects parameter drift
renewActiongoat.gns.renewhighExtend an existing registration

commit returns the 32-byte secret as a top-level sensitiveOutputFields value — redacted by default in hook events; the CLI exposes it under --reveal-secrets only.

Profile

ActionNameRiskDescription
setAddressActiongoat.gns.setAddressmediumSet / update the address that a name resolves to
setProfileRecordsActiongoat.gns.setProfileRecordsmediumBatch set text + addr records via PublicResolver multicall
setPrimaryNameActiongoat.gns.setPrimaryNamemediumSet the reverse (primary) record for the caller

x402-paid registration

ActionNameRiskDescription
x402CreateOrderActiongoat.gns.x402.createOrdermediumCreate an x402-paid registration order via the GNS backend
x402SubmitSignatureActiongoat.gns.x402.submitSignaturehighEIP-712 sign the calldata authorization (GOAT-adapter chain)
x402PayOrderActiongoat.gns.x402.payOrderhighBroadcast the payment-chain ERC-20 transfer that fulfills the order
x402GetOrderStatusActiongoat.gns.x402.getOrderStatusreadPoll lifecycle: CHECKOUT_VERIFIED → PAYMENT_CONFIRMED → INVOICED

x402 Registration — Orchestration

The CLI (agentkit-gns x402-register) and the examples/gns-x402-register demo both run this 8-step sequence:

x402-register orchestration
1. x402.config preflight     — validate (payChain, payToken, payTokenContract)
                                 against the merchant's authoritative config
2. estimatePrice (GOAT-side) — quote totalWei in the GOAT-side stablecoin
3. commit (GOAT)             — bind (GOAT-side stablecoin, totalWei) into
                                 the on-chain commit hash
4. waitForCommitment         — poll until block.timestamp ≥ revealAt
                                 (minCommitmentAge = 60s; CLI default
                                  timeout 180s)
5. x402.createOrder          — pin commitMaxAmountWei so the backend
                                 re-derives a matching hash
6. x402.submitSignature      — user signs the calldata typed data
7. x402.payOrder             — broadcast payment-chain ERC-20 transfer
                                 (e.g. 3 USDT on BSC)
8. poll getOrderStatus       — until INVOICED (true success) or terminal
                                 failure (FAILED / EXPIRED / CANCELLED)

Lifecycle states

The GNS backend's OrderStatus enum (no PAYMENT_PENDING):

StateMeaning
CHECKOUT_VERIFIEDOrder created and signature authorized; ready for payment-chain payment
PAYMENT_CONFIRMEDUpstream watcher detected the payment-chain ERC-20 transfer
INVOICEDTrue success — the adaptor callback executed on GOAT and the .goat name is registered
FAILED / EXPIRED / CANCELLEDTerminal failure

For DELEGATE / callback orders (the GNS flow), PAYMENT_CONFIRMED is an intermediate state — clients must keep polling until INVOICED to confirm the name was actually registered. The CLI does this automatically.

Commit-hash binding invariant

The on-chain commit's payment tuple must match what the GNS backend re-derives in createOrder, or registration is rejected with Commitment does not match registration payload. AgentKit binds:

  • paymentToken = GOAT-side stablecoin address resolved by canonical symbol via the SDK's GNS_PAYMENT_TOKENS table (mirrors the backend's paymentTokenAddressFromSymbol).
  • maxPaymentAmount = totalWei from /names/quote, forwarded to createOrder as commitMaxAmountWei.

The register action additionally re-derives the commitment hash on-chain and rejects any drift of (secret / token / amount / years / owner / resolver / data / reverseRecord / referrer) vs the original commit.

payOrder Security Model

x402.payOrder is a money path. Beyond the lifecycle gate (require CHECKOUT_VERIFIED, refuse to double-pay or pay a dead order), it cross-checks every caller-supplied routing field against three trust anchors:

  1. Status endpoint OrderProofchainId, amountWei, fromAddress (strict type + value).
  2. calldataSignRequest.message (backend-signed, verified on-chain by the adaptor):
    • message.ownerinput.payToAddress — caller cannot redirect the merchant's payment-receive address (mandatory; fail-closed if absent).
    • message.payer ≡ wallet address — bind the signed payer authorization to the broadcasting wallet.
  3. x402Config token allowlistinput.tokenContract must appear in chains[payChainId].tokens[].contract. Unknown tokens or unsupported chains are refused.

A 3-way payer binding (wallet.getAddress() ≡ payer.getAddress() ≡ message.payer) catches mis-wired payer adapters where wallet and payer wrap different signers.

A module-scope inflight Map dedupes concurrent payOrder calls for the same orderId (double-pay TOCTOU defense).

Network Addresses (Predeployed)

The SDK's getGnsContracts(network) returns the full registry. GOAT mainnet ships with these addresses pre-wired:

Contractgoat-mainnet (chain 2345)
Controller0x271AfB3d228BB3169F5d10b8F34e227EEF43a5d9
PublicResolver0xC624A087E2fEC3bcB63fdA984829Eb18E56be210
EnsRegistry0xEce959d7669a81964b86c3704335fD0332087BAe
ReverseRegistrar0xC7e6944fb92bc810C2784b6595ff8753A5364Ae4
BaseRegistrar0x8aB04C7c002C4B2c655aFec245296d8ef874933F
x402Adaptor0x03Ae465d2ccd719416f9BC52783F268DC2f277e6
PriceBook0xa651803328a0236cEFf271e136131Ad8071365B9
GoatNameWrapper0x15F5875c7CDA8C0576e649677eBA938cC9082dFC
StaticMetadataService0xC2c18F8afeBc5EDE41c801016023B38Fad15DEC2

GOAT-side payment tokens

SymbolAddressDecimals
USDC0x3022b87ac063DE95b1570F46f5e470F8B53112D86
USDT0xE1AD845D93853fff44990aE0DcecD8575293681e6

Programmatic Usage

Register .goat names via the SDK
import { JsonRpcProvider, Wallet } from 'ethers';
import { ActionProvider, ExecutionRuntime, PolicyEngine } from '@goatnetwork/agentkit';
import { EvmWalletProvider } from '@goatnetwork/agentkit/core';
import {
  HttpGnsApiAdapter,
  commitAction,
  waitForCommitmentAction,
  registerAction,
  estimatePriceAction,
  getGnsContracts,
  resolveGnsPaymentToken,
} from '@goatnetwork/agentkit/plugins';

const networkKey = 'goat-mainnet';
const contracts = getGnsContracts(networkKey);
const api = new HttpGnsApiAdapter({ network: networkKey });

const rpcUrl = 'https://rpc.goat.network';
const wallet = new EvmWalletProvider(
  new Wallet(process.env.GOAT_PRIVATE_KEY!, new JsonRpcProvider(rpcUrl)),
  new JsonRpcProvider(rpcUrl),
  networkKey,
);

const provider = new ActionProvider();
provider.register(estimatePriceAction(api));
provider.register(commitAction(contracts, wallet));
provider.register(waitForCommitmentAction(contracts, wallet));
provider.register(registerAction(contracts, wallet));

const policy = new PolicyEngine({
  allowedNetworks: [networkKey],
  maxRiskWithoutConfirm: 'low',
  writeEnabled: true,
});
const runtime = new ExecutionRuntime(policy, { maxRetries: 0, retryDelayMs: 200 });

// 1. Quote the GOAT-side price
const goatToken = resolveGnsPaymentToken(networkKey, 'USDC')!;
const quote = await runtime.run(
  provider.get('goat.gns.estimatePrice'),
  { traceId: 't1', network: networkKey, now: Date.now() },
  { name: 'alice', years: 1, tokenContract: goatToken.address },
  { confirmed: true },
);
const totalWei = (quote.output as { totalWei: string }).totalWei;

// 2. Commit (binds the same paymentToken + maxPaymentAmount the backend will re-derive)
const commit = await runtime.run(
  provider.get('goat.gns.commit'),
  { traceId: 't2', network: networkKey, now: Date.now() },
  {
    name: 'alice',
    owner: (await wallet.getAddress()) as `0x${string}`,
    years: 1,
    paymentTokenAddress: goatToken.address,
    paymentTokenSymbol: goatToken.symbol,
    amountWei: totalWei,
  },
  { confirmed: true, revealedFields: ['secret'] },
);

// 3. Wait for the reveal window, then call register / renew per your flow.

Environment Variables

VariableRequiredDescription
GOAT_PRIVATE_KEYAlwaysWallet key for the GOAT-side wallet (owner of the registered name)
PAYMENT_CHAIN_RPC_URLx402-register onlyRPC URL of the payment chain you're paying from (e.g. BSC). When the payment chain is GOAT Mainnet, you can use the GOAT RPC/default configuration.
PAYMENT_CHAIN_PRIVATE_KEYOptionalPayment-chain signer key for x402-register; falls back to GOAT_PRIVATE_KEY (same wallet on both chains)
GNS_API_BASE_URLOptionalOverride default https://gns-api.goat.network/api
GOAT_MAINNET_RPC_URLOptionalOverride the default GOAT mainnet RPC endpoint

Run agentkit-gns doctor for a full env diagnostic.

Examples

On this page