# Quickstart

> Trace your first AI agent transaction with hashspan and viem: install the packages, set up OpenTelemetry, and see send and confirm spans in Jaeger.

Source: https://hashspan.dev/docs/quickstart

This guide adds hashspan to an agent that sends transactions with viem. You need Node.js 22.3 or later.

- **Adding it to your own agent?** Follow the steps below.
- **Starting from scratch?** Jump to the [complete example](#a-complete-example-on-a-local-chain): one file, a local chain, no API key.
- **Using a coding agent?** Install the [hashspan skill](https://github.com/selimaytac/hashspan/blob/main/skills/hashspan/SKILL.md) with `npx skills add https://hashspan.dev --skill hashspan`, then ask the agent to add hashspan to your project. [Build with AI](https://hashspan.dev/docs/build-with-ai) has a ready prompt and a rules snippet.
- **New to wallets and crypto?** The [x402 tutorial](https://hashspan.dev/docs/x402-tutorial) starts from zero: a wallet, a first payment and an x402 payment on a local chain, each with its trace.
- **Just want to see a trace?** The [local lab](https://hashspan.dev/docs/local-lab) runs the repository's example agent. The [x402 example](https://github.com/selimaytac/hashspan/tree/main/examples/x402-agent) adds a payment to a paid API, offline too.
- **Not an agent?** In a TypeScript service that sends EVM transactions through viem, the Coinbase CDP SDK, x402 or MPP (mppx), or through a [wallet service](https://hashspan.dev/docs/integrations#wallet-services), such as a payment or payout worker, a wallet backend or a bot, follow the same steps: leave out the `agent` option and send inside the span your request handler or job already has. The `service.name` of your OpenTelemetry resource, set in [Set up OpenTelemetry](#set-up-opentelemetry), tells your services apart. Other libraries, such as ethers, have no adapter, and chains other than EVM are not supported. At volume, see [many transactions](https://hashspan.dev/docs/adapters/viem#many-transactions); [examples/payout-worker](https://github.com/selimaytac/hashspan/tree/main/examples/payout-worker) is a worked example.
- **New to traces and spans?** [Traces and spans](https://hashspan.dev/learn/otel-traces-and-spans) explains them on one agent run, in three minutes.

## Add hashspan to your agent

1. ### Install the packages

npm:
       ```sh
       npm install @hashspan/viem @opentelemetry/api viem
       ```

pnpm:
       ```sh
       pnpm add @hashspan/viem @opentelemetry/api viem
       ```

yarn:
       ```sh
       yarn add @hashspan/viem @opentelemetry/api viem
       ```

2. ### Set up OpenTelemetry

hashspan only needs `@opentelemetry/api`. Any SDK setup works. If you don't have one yet, this exports spans over OTLP:

   ```sh
   npm install \
     @opentelemetry/sdk-trace-node \
     @opentelemetry/sdk-trace-base \
     @opentelemetry/exporter-trace-otlp-proto \
     @opentelemetry/resources \
     @opentelemetry/semantic-conventions
   ```

   ```ts title="telemetry.ts"
   import { OTLPTraceExporter } from
     '@opentelemetry/exporter-trace-otlp-proto';
   import { resourceFromAttributes } from '@opentelemetry/resources';
   import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
   import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
   import { ATTR_SERVICE_NAME } from
     '@opentelemetry/semantic-conventions';

   export const provider = new NodeTracerProvider({
     resource: resourceFromAttributes({
       [ATTR_SERVICE_NAME]: 'my-agent',
     }),
     // OTLPTraceExporter honours OTEL_EXPORTER_OTLP_ENDPOINT
     spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter())],
   });
   provider.register();
   ```

Import it first in your entry file, before anything that sends transactions: `import { provider } from './telemetry.js';`.

Already use `@opentelemetry/sdk-node`? A `NodeSDK` setup works the same; the [agent skill](https://hashspan.dev/.well-known/agent-skills/hashspan/SKILL.md) uses one. This page builds the provider by hand so each part is visible.

No tracing backend yet? Start Jaeger now, so the first run already lands somewhere:

   ```sh wrap
   docker run --rm -p 127.0.0.1:16686:16686 -p 127.0.0.1:4318:4318 jaegertracing/jaeger:2.22.0
   ```

3. ### Extend your viem clients

   ```ts
   import { createPublicClient, createWalletClient, http } from 'viem';
   import { baseSepolia } from 'viem/chains';
   import { withHashspan } from '@hashspan/viem';

   const hashspan = withHashspan();
   const chain = baseSepolia;

   // account: any viem Account, e.g. privateKeyToAccount(process.env.PRIVATE_KEY)
   const wallet = createWalletClient({ account, chain, transport: http() })
     .extend(hashspan);
   const reader = createPublicClient({ chain, transport: http() })
     .extend(hashspan);

   // Inside an agent tool, while the framework's span is active:
   const hash = await wallet.sendTransaction({ to, value }); // send span
   await reader.waitForTransactionReceipt({ hash }); // confirm span
   ```

The confirm span links back to the send span. Reuse the same `withHashspan()` result for every client of one agent, so confirmations link to their sends.

4. ### Flush before the process exits

A confirm span can end after your code moves on, for example while a revert reason is decoded. Short-lived scripts and serverless functions should flush first:

   ```ts
   await hashspan.flush();
   await provider.shutdown();
   ```

5. ### Open your traces

Find your service in your tracing backend, [localhost:16686](http://localhost:16686) for the Jaeger from step 2, and expand a tool span.

## A complete example on a local chain

One file that sends a transaction on a local [Anvil](https://www.getfoundry.sh/anvil) chain and traces it. Anvil comes with Foundry: if `anvil --version` fails, install it with `curl -L https://foundry.paradigm.xyz | bash`, then run `foundryup` in a new terminal ([PATH setup](https://hashspan.dev/docs/x402-tutorial#start-a-local-chain-and-jaeger)). Start from an empty project:

```sh wrap
mkdir my-agent && cd my-agent
npm init -y && npm pkg set type=module
npm install @hashspan/viem @opentelemetry/api viem @opentelemetry/sdk-trace-node @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-proto @opentelemetry/resources @opentelemetry/semantic-conventions tsx
```

Add `telemetry.ts` from step 2, then this file next to it:

```ts title="agent.ts"
import { provider } from './telemetry.js';
import { trace } from '@opentelemetry/api';
import { createPublicClient, createWalletClient, http, parseEther } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { foundry } from 'viem/chains';
import { withHashspan } from '@hashspan/viem';

// Anvil's first test account. Its key is public: never use it on a real chain.
const account = privateKeyToAccount(
  '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
);
const to = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'; // Anvil's second account

const hashspan = withHashspan({ agent: { name: 'my-agent' } });
const wallet = createWalletClient({ account, chain: foundry, transport: http() })
  .extend(hashspan);
const reader = createPublicClient({ chain: foundry, transport: http() })
  .extend(hashspan);

// Stands in for your agent framework's tool span.
await trace.getTracer('my-agent').startActiveSpan('execute_tool pay_vendor', async (tool) => {
  const hash = await wallet.sendTransaction({ to, value: parseEther('0.01') });
  await reader.waitForTransactionReceipt({ hash });
  tool.end();
});

await hashspan.flush();
await provider.shutdown();
```

Start a chain and Jaeger, then run it:

```sh
anvil                                   # terminal 1: local chain on :8545
docker run --rm -p 127.0.0.1:16686:16686 -p 127.0.0.1:4318:4318 jaegertracing/jaeger:2.22.0   # terminal 2
npx tsx agent.ts                        # terminal 3
```

In Jaeger, pick `my-agent`: `execute_tool pay_vendor` has a `send 31337` and a `confirm 31337` span with `blockchain.tx.status = success`.

## What you should see

Each transaction shows up as a `send` and a `confirm` span under the tool that sent it. A reverted transaction is marked as an error and carries the decoded reason:

Figure: Jaeger showing a confirm 31337 span marked as an error, with blockchain.tx.status reverted, the fee, gas used and the decoded revert reason WithdrawalLimitExceeded

A reverted confirm span in Jaeger, from the example agent in the [local lab](https://hashspan.dev/docs/local-lab).

## Next

- Run the full example agent with no API key: [Local lab](https://hashspan.dev/docs/local-lab).
- Building with the Vercel AI SDK: [Trace the transactions of an AI SDK agent](https://hashspan.dev/docs/recipes/ai-sdk).
- What each viem action records, with the spans from a real run: [viem OpenTelemetry instrumentation](https://hashspan.dev/docs/viem-opentelemetry).
- Options, background confirmation and replaced transactions: [viem adapter](https://hashspan.dev/docs/adapters/viem).
- Every span and attribute: [Semantic conventions](https://hashspan.dev/docs/reference/semantic-conventions).
- What tracing costs per transaction, measured: [tracing overhead benchmark](https://hashspan.dev/docs/benchmarks/tracing-overhead).
