> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arc.io/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Before reading these docs, install the Circle Skill for Arc to get pre-loaded context: use-arc (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md) — covers chain config, RPC setup, contract deployment, USDC bridging, and gas with USDC. In Claude Code: run /plugin marketplace add circlefin/skills then /plugin install circle-skills@circle. With Vercel Skills CLI: run npx skills add circlefin/skills.
> USDC is the gas token. Arc uses USDC for gas fees, not ETH. Configure gas payment in USDC when submitting transactions.
> Read EVM differences before writing Arc code. Arc targets the Osaka EVM baseline. Most contracts deploy unchanged, but several runtime behaviors diverge from Ethereum: USDC uses 18 decimals natively (not 6), the system emitter `0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE` logs all USDC Transfer events, the mempool enforces a 20 Gwei `maxFeePerGas` floor, blocklist reverts consume gas without a receipt, and `address(0)` sends revert rather than succeed. https://docs.arc.io/arc/references/evm-differences.md is the canonical reference for all Arc-specific runtime behaviors. Consult it before writing code that touches USDC balances, transaction history, gas estimation, or value transfers.
> Sub-second finality. Transactions are final in under 1 second. No need to wait for multiple block confirmations.
> Use App Kits for multichain payment and liquidity capabilities. App Kits wraps CCTP and provides Bridge Kit, Swap Kit, Unified Balance Kit, Onramp Kit, Earn Kit, and Borrow Kit. Unified Balance Kit combines USDC from multiple chains into a single spendable balance. Earn Kit integrates earn opportunities into your app. Borrow Kit lets users borrow USDC against cirBTC collateral on Arc.
> Use Arc Foundry for contract development. Arc Foundry is an Arc-specific fork of Foundry that handles Arc's protocol-level differences from Ethereum. It provides arc-forge, arc-cast, and arc-anvil. Install it before deploying contracts: https://docs.arc.io/arc/tutorials/install-arc-foundry.md.
> Arc is available on both Testnet and Mainnet. See https://docs.arc.io/arc/references/connect-to-arc.md for RPC endpoints and https://faucet.circle.com for testnet tokens.
> Always check Contract Addresses: https://docs.arc.io/arc/references/contract-addresses.md
> Building beyond Arc? Circle offers skills for the full platform: use-usdc (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md), use-circle-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md), use-developer-controlled-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md), use-user-controlled-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-user-controlled-wallets/SKILL.md), use-modular-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-modular-wallets/SKILL.md), use-gateway (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md), use-smart-contract-platform (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-smart-contract-platform/SKILL.md). Full Circle developer docs: https://developers.circle.com/llms.txt.

# Create your first ERC-8183 job

> Create an ERC-8183 job, fund escrow with USDC, submit a deliverable hash, and complete settlement on Arc Testnet.

export const TryOnArcStudio = () => {
  const [copied, setCopied] = useState(false);
  const [prompt, setPrompt] = useState("");
  useEffect(() => {
    const url = `https://docs.arc.io${window.location.pathname}.md`;
    setPrompt(`Read the guide at ${url}, implement it in a new project, then run it and show me how it works.`);
  }, []);
  const arcStudioUrl = `https://studio.arc.io/app?prompt=${encodeURIComponent(prompt)}`;
  const handleArcStudioClick = () => {
    try {
      if (typeof navigator !== "undefined" && navigator.clipboard && prompt) {
        navigator.clipboard.writeText(prompt).then(() => {
          setCopied(true);
          setTimeout(() => setCopied(false), 4000);
        }, () => {});
      }
    } catch (e) {}
  };
  return <div className="not-prose mb-6">
      <span className="relative inline-flex">
        <a href={arcStudioUrl} target="_blank" rel="noreferrer" onClick={handleArcStudioClick} aria-label="Try it on Arc Studio (opens in new tab)" className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-[#3E74BB] border border-[#3E74BB] text-sm font-medium text-white no-underline hover:bg-[#345f9c] hover:border-[#345f9c] transition-all">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z" />
          </svg>
          Try it on Arc Studio
        </a>
        {copied && <span role="status" aria-live="polite" className="absolute left-0 top-full mt-2 w-72 text-sm text-gray-500 dark:text-[#888]">
            Opening Arc Studio — prompt copied to your clipboard in case you need to
            sign in first.
          </span>}
      </span>
    </div>;
};

<TryOnArcStudio />

This quickstart guides you through the ERC-8183 job lifecycle on Arc Testnet.
You'll create developer-controlled smart contract account wallets, create a job,
fund escrow with USDC, submit a deliverable hash, and complete the job as the
evaluator. Select the tab that matches your preferred setup.

## ERC-8183 contract on Arc testnet

| Contract | Address |
| - | - |
| AgenticCommerce reference implementation | [`0x0747EEf0706327138c69792bF28Cd525089e4583`](https://explorer.testnet.arc.io/address/0x0747EEf0706327138c69792bF28Cd525089e4583) |

<Tabs>
  <Tab title="Circle Wallets">
    ## Prerequisites

    Before you begin, make sure you have:

    1. A [Circle Developer Console](https://console.circle.com) account
    2. An API key created in the Console: **Keys → Create a key → API key → Standard
       Key**
    3. Your entity secret generated and registered using the
       [Circle Console](https://console.circle.com/wallets/dev/configurator/entity-secret)
       or the
       [SDK](https://developers.circle.com/wallets/dev-controlled/register-entity-secret#create-an-entity-secret-using-the-sdk)

    <Accordion icon="circle-info" title="New to Circle dev-controlled wallets? See what credentials you need.">
      Circle wallets use three credentials. An [**API
      key**](https://developers.circle.com/api-reference/keys) authenticates your
      requests to Circle APIs. An [**entity
      secret**](https://developers.circle.com/wallets/dev-controlled/entity-secret-management)
      authorizes each wallet operation. A [**wallet set
      ID**](https://developers.circle.com/wallets/dev-controlled/create-your-first-wallet)
      groups your wallets.
    </Accordion>

    ## Step 1. Set up your project

    Create a project directory, install dependencies, and configure your
    environment.

    ### 1.1. Create the project and install dependencies

    <CodeGroup>
      ```shell Node.js theme={null}
      mkdir erc8183-quickstart
      cd erc8183-quickstart
      npm init -y
      npm pkg set type=module
      npm pkg set scripts.start="tsx --env-file=.env index.ts"

      npm install @circle-fin/developer-controlled-wallets viem
      npm install --save-dev tsx typescript @types/node
      ```

      ```shell Python theme={null}
      mkdir erc8183-quickstart
      cd erc8183-quickstart
      python3 -m venv .venv
      source .venv/bin/activate

      pip install circle-developer-controlled-wallets web3 python-dotenv
      ```
    </CodeGroup>

    ### 1.2. Configure TypeScript (optional)

    <Tip>
      This step is optional. It helps prevent missing types in your IDE or editor.
    </Tip>

    Create a `tsconfig.json` file:

    ```shell theme={null}
    npx tsc --init
    ```

    Then, update the `tsconfig.json` file:

    ```shell theme={null}
    cat <<'EOF' > tsconfig.json
    {
      "compilerOptions": {
        "target": "ESNext",
        "module": "ESNext",
        "moduleResolution": "bundler",
        "strict": true,
        "types": ["node"]
      }
    }
    EOF
    ```

    ### 1.3. Set environment variables

    Create a `.env` file in the project directory and add your Circle credentials:

    ```text theme={null}
    CIRCLE_API_KEY=YOUR_API_KEY
    CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET
    ```

    * `CIRCLE_API_KEY` is your Circle Developer API key.
    * `CIRCLE_ENTITY_SECRET` is your registered Entity Secret.

    The `npm run start` command loads variables from `.env` using Node.js native
    env-file support. The `python index.py` command loads the same `.env` file using
    `python-dotenv`.

    <Tip>
      Prefer editing `.env` files in your IDE or editor so credentials are not
      leaked to your shell history.
    </Tip>

    ## Step 2. Create developer-controlled wallets

    In this step, you create two Arc Testnet dev-controlled wallets for the ERC-8183
    flow: a client wallet and a provider wallet. In this quickstart, the client also
    acts as the evaluator. If you already have two Arc Testnet funded dev-controlled
    wallets for this flow, skip to [Step 4](#step-4-create-the-job).

    The Step 2 through 9 sections explain the flow in smaller pieces. Not every step
    includes a code snippet, and the snippets are not cumulative. To run the full
    workflow end to end, use the [complete script](#full-job-lifecycle-script) at
    the end of this tutorial.

    <CodeGroup>
      ```typescript index.ts theme={null}
      const walletSet = await circleClient.createWalletSet({
        name: "ERC8183 Job Wallets",
      });

      const walletsResponse = await circleClient.createWallets({
        blockchains: ["ARC-TESTNET"],
        count: 2,
        walletSetId: walletSet.data?.walletSet?.id ?? "",
        accountType: "SCA",
      });

      const clientWallet = walletsResponse.data?.wallets?.[0]!;
      const providerWallet = walletsResponse.data?.wallets?.[1]!;

      console.log(`Client:   ${clientWallet.address} (${clientWallet.id})`);
      console.log(`Provider: ${providerWallet.address} (${providerWallet.id})`);
      console.log(`Evaluator: ${clientWallet.address} (${clientWallet.id})`);
      ```

      ```python index.py theme={null}
      from circle.web3 import developer_controlled_wallets, utils
      import os
      from dotenv import load_dotenv

      load_dotenv()

      circle_client = utils.init_developer_controlled_wallets_client(
          api_key=os.getenv("CIRCLE_API_KEY"),
          entity_secret=os.getenv("CIRCLE_ENTITY_SECRET"),
      )

      wallet_sets_api = developer_controlled_wallets.WalletSetsApi(circle_client)
      wallets_api = developer_controlled_wallets.WalletsApi(circle_client)

      wallet_set = wallet_sets_api.create_wallet_set(
          developer_controlled_wallets.CreateWalletSetRequest.from_dict({
              "name": "ERC8183 Job Wallets",
          })
      )

      wallets_response = wallets_api.create_wallet(
          developer_controlled_wallets.CreateWalletRequest.from_dict({
              "blockchains": ["ARC-TESTNET"],
              "count": 2,
              "walletSetId": wallet_set.data.wallet_set.actual_instance.id,
              "accountType": "SCA",
          })
      )

      client_wallet = wallets_response.data.wallets[0].actual_instance
      provider_wallet = wallets_response.data.wallets[1].actual_instance

      print(f"Client:   {client_wallet.address} ({client_wallet.id})")
      print(f"Provider: {provider_wallet.address} ({provider_wallet.id})")
      print(f"Evaluator: {client_wallet.address} ({client_wallet.id})")
      ```
    </CodeGroup>

    ## Step 3. Fund the client wallet

    The script will pause to allow you to fund the client wallet with Arc Testnet
    USDC from one of these faucets:

    * [Circle Faucet](https://faucet.circle.com)
    * [Circle Console Faucet](https://console.circle.com/faucet)

    You only fund the client wallet as the script transfers starter USDC to the
    provider wallet automatically before the ERC-8183 flow begins.

    <Note>
      The public faucet is rate-limited, so this quickstart avoids requiring a
      second faucet request for the provider wallet.
    </Note>

    ## Step 4. Create the job

    Call `createJob(provider, evaluator, expiredAt, description, hook)` on the
    deployed ERC-8183 reference implementation. This creates the job in the `Open`
    state. This quickstart uses `address(0)` for `hook` so the flow stays on the
    default non-hooked path.

    <CodeGroup>
      ```typescript index.ts theme={null}
      const createJobTx = await circleClient.createContractExecutionTransaction({
        walletAddress: clientWallet.address!,
        blockchain: "ARC-TESTNET",
        contractAddress: AGENTIC_COMMERCE_CONTRACT,
        abiFunctionSignature: "createJob(address,address,uint256,string,address)",
        abiParameters: [
          providerWallet.address!,
          clientWallet.address!,
          expiredAt.toString(),
          "ERC-8183 demo job on Arc Testnet",
          "0x0000000000000000000000000000000000000000",
        ],
        fee: { type: "level", config: { feeLevel: "MEDIUM" } },
      });
      ```

      ```python index.py theme={null}
      create_job_request = (
          developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict(
              {
                  "walletAddress": client_wallet.address,
                  "blockchain": "ARC-TESTNET",
                  "contractAddress": AGENTIC_COMMERCE_CONTRACT,
                  "abiFunctionSignature": "createJob(address,address,uint256,string,address)",
                  "abiParameters": [
                      provider_wallet.address,
                      client_wallet.address,
                      str(expired_at),
                      "ERC-8183 demo job on Arc Testnet",
                      "0x0000000000000000000000000000000000000000",
                  ],
                  "feeLevel": "MEDIUM",
              }
          )
      )
      ```
    </CodeGroup>

    ## Step 5. Set the budget

    In this deployed contract, the provider sets the job price by calling
    `setBudget(jobId, amount, optParams)`.

    <CodeGroup>
      ```typescript index.ts theme={null}
      const setBudgetTx = await circleClient.createContractExecutionTransaction({
        walletAddress: providerWallet.address!,
        blockchain: "ARC-TESTNET",
        contractAddress: AGENTIC_COMMERCE_CONTRACT,
        abiFunctionSignature: "setBudget(uint256,uint256,bytes)",
        abiParameters: [jobId.toString(), JOB_BUDGET.toString(), "0x"],
        fee: { type: "level", config: { feeLevel: "MEDIUM" } },
      });
      ```

      ```python index.py theme={null}
      set_budget_request = (
          developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict(
              {
                  "walletAddress": provider_wallet.address,
                  "blockchain": "ARC-TESTNET",
                  "contractAddress": AGENTIC_COMMERCE_CONTRACT,
                  "abiFunctionSignature": "setBudget(uint256,uint256,bytes)",
                  "abiParameters": [str(job_id), JOB_BUDGET, "0x"],
                  "feeLevel": "MEDIUM",
              }
          )
      )
      ```
    </CodeGroup>

    ## Step 6. Approve USDC and fund escrow

    Before the client can fund the job, the USDC contract must approve the ERC-8183
    contract to transfer the escrow amount. Then the client calls
    `fund(jobId, optParams)` to move the job into the `Funded` state.

    <CodeGroup>
      ```typescript index.ts theme={null}
      const approveTx = await circleClient.createContractExecutionTransaction({
        walletAddress: clientWallet.address!,
        blockchain: "ARC-TESTNET",
        contractAddress: "0x3600000000000000000000000000000000000000",
        abiFunctionSignature: "approve(address,uint256)",
        abiParameters: [AGENTIC_COMMERCE_CONTRACT, JOB_BUDGET.toString()],
        fee: { type: "level", config: { feeLevel: "MEDIUM" } },
      });

      const fundTx = await circleClient.createContractExecutionTransaction({
        walletAddress: clientWallet.address!,
        blockchain: "ARC-TESTNET",
        contractAddress: AGENTIC_COMMERCE_CONTRACT,
        abiFunctionSignature: "fund(uint256,bytes)",
        abiParameters: [jobId.toString(), "0x"],
        fee: { type: "level", config: { feeLevel: "MEDIUM" } },
      });
      ```

      ```python index.py theme={null}
      approve_request = (
          developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict(
              {
                  "walletAddress": client_wallet.address,
                  "blockchain": "ARC-TESTNET",
                  "contractAddress": "0x3600000000000000000000000000000000000000",
                  "abiFunctionSignature": "approve(address,uint256)",
                  "abiParameters": [AGENTIC_COMMERCE_CONTRACT, JOB_BUDGET],
                  "feeLevel": "MEDIUM",
              }
          )
      )

      fund_request = (
          developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict(
              {
                  "walletAddress": client_wallet.address,
                  "blockchain": "ARC-TESTNET",
                  "contractAddress": AGENTIC_COMMERCE_CONTRACT,
                  "abiFunctionSignature": "fund(uint256,bytes)",
                  "abiParameters": [str(job_id), "0x"],
                  "feeLevel": "MEDIUM",
              }
          )
      )
      ```
    </CodeGroup>

    ## Step 7. Submit the deliverable

    The provider submits a `bytes32` deliverable hash, moving the job into the
    `Submitted` state.

    <CodeGroup>
      ```typescript index.ts theme={null}
      const deliverableHash = keccak256(toHex("arc-erc8183-demo-deliverable"));

      const submitTx = await circleClient.createContractExecutionTransaction({
        walletAddress: providerWallet.address!,
        blockchain: "ARC-TESTNET",
        contractAddress: AGENTIC_COMMERCE_CONTRACT,
        abiFunctionSignature: "submit(uint256,bytes32,bytes)",
        abiParameters: [jobId.toString(), deliverableHash, "0x"],
        fee: { type: "level", config: { feeLevel: "MEDIUM" } },
      });
      ```

      ```python index.py theme={null}
      deliverable_hash = Web3.to_hex(
          Web3.keccak(text="arc-erc8183-demo-deliverable")
      )

      submit_request = (
          developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict(
              {
                  "walletAddress": provider_wallet.address,
                  "blockchain": "ARC-TESTNET",
                  "contractAddress": AGENTIC_COMMERCE_CONTRACT,
                  "abiFunctionSignature": "submit(uint256,bytes32,bytes)",
                  "abiParameters": [str(job_id), deliverable_hash, "0x"],
                  "feeLevel": "MEDIUM",
              }
          )
      )
      ```
    </CodeGroup>

    ## Step 8. Complete the job

    The evaluator completes the job by calling `complete(jobId, reason, optParams)`.
    In this quickstart, the client is also the evaluator.

    <CodeGroup>
      ```typescript index.ts theme={null}
      const reasonHash = keccak256(toHex("deliverable-approved"));

      const completeTx = await circleClient.createContractExecutionTransaction({
        walletAddress: clientWallet.address!,
        blockchain: "ARC-TESTNET",
        contractAddress: AGENTIC_COMMERCE_CONTRACT,
        abiFunctionSignature: "complete(uint256,bytes32,bytes)",
        abiParameters: [jobId.toString(), reasonHash, "0x"],
        fee: { type: "level", config: { feeLevel: "MEDIUM" } },
      });
      ```

      ```python index.py theme={null}
      reason_hash = Web3.to_hex(Web3.keccak(text="deliverable-approved"))

      complete_request = (
          developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict(
              {
                  "walletAddress": client_wallet.address,
                  "blockchain": "ARC-TESTNET",
                  "contractAddress": AGENTIC_COMMERCE_CONTRACT,
                  "abiFunctionSignature": "complete(uint256,bytes32,bytes)",
                  "abiParameters": [str(job_id), reason_hash, "0x"],
                  "feeLevel": "MEDIUM",
              }
          )
      )
      ```
    </CodeGroup>

    ## Step 9. Check the final job state

    Read the job back from the contract to confirm it reached `Completed`. This
    reference implementation does not return the deliverable in `getJob()`, so the
    script prints the submitted deliverable hash from local flow state instead.

    <CodeGroup>
      ```typescript index.ts theme={null}
      const job = await publicClient.readContract({
        address: AGENTIC_COMMERCE_CONTRACT,
        abi: agenticCommerceAbi,
        functionName: "getJob",
        args: [jobId],
      });

      console.log(`Job ID: ${jobId}`);
      console.log(`Status: ${STATUS_NAMES[Number(job.status)]}`);
      console.log(`Budget: ${formatUnits(job.budget, 6)} USDC`);
      console.log(`Hook: ${job.hook}`);
      console.log(`Deliverable hash submitted: ${deliverableHash}`);
      ```

      ```python index.py theme={null}
      contract = web3.eth.contract(
          address=Web3.to_checksum_address(AGENTIC_COMMERCE_CONTRACT),
          abi=agentic_commerce_abi,
      )
      job = contract.functions.getJob(job_id).call()

      print(f"Job ID: {job_id}")
      print(f"Status: {STATUS_NAMES[int(job[7])]}")
      print(f"Budget: {Web3.from_wei(job[5], 'mwei')} USDC")
      print(f"Hook: {job[8]}")
      print(f"Deliverable hash submitted: {deliverable_hash}")
      ```
    </CodeGroup>

    ## Full job lifecycle script

    The following complete scripts combine all the preceding steps into a single
    runnable file.

    <CodeGroup>
      ```typescript index.ts expandable theme={null}
      import { createInterface } from "node:readline/promises";
      import { setTimeout as delay } from "node:timers/promises";
      import { stdin as input, stdout as output } from "node:process";
      import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
      import {
        createPublicClient,
        decodeEventLog,
        formatUnits,
        http,
        keccak256,
        parseUnits,
        toHex,
        type Address,
        type Hex,
      } from "viem";
      import { arcTestnet } from "viem/chains";

      // To bootstrap provider wallet during setup (see Step 3)
      const PROVIDER_STARTER_BALANCE = "1";

      const AGENTIC_COMMERCE_CONTRACT =
        "0x0747EEf0706327138c69792bF28Cd525089e4583" as Address;
      const JOB_BUDGET = parseUnits("5", 6); // 5 USDC (ERC-20, 6 decimals)

      const circleClient = initiateDeveloperControlledWalletsClient({
        apiKey: process.env.CIRCLE_API_KEY!,
        entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
      });

      const publicClient = createPublicClient({
        chain: arcTestnet,
        transport: http(),
      });

      const agenticCommerceAbi = [
        {
          type: "function",
          name: "createJob",
          stateMutability: "nonpayable",
          inputs: [
            { name: "provider", type: "address" },
            { name: "evaluator", type: "address" },
            { name: "expiredAt", type: "uint256" },
            { name: "description", type: "string" },
            { name: "hook", type: "address" },
          ],
          outputs: [{ name: "jobId", type: "uint256" }],
        },
        {
          type: "function",
          name: "setBudget",
          stateMutability: "nonpayable",
          inputs: [
            { name: "jobId", type: "uint256" },
            { name: "amount", type: "uint256" },
            { name: "optParams", type: "bytes" },
          ],
          outputs: [],
        },
        {
          type: "function",
          name: "fund",
          stateMutability: "nonpayable",
          inputs: [
            { name: "jobId", type: "uint256" },
            { name: "optParams", type: "bytes" },
          ],
          outputs: [],
        },
        {
          type: "function",
          name: "submit",
          stateMutability: "nonpayable",
          inputs: [
            { name: "jobId", type: "uint256" },
            { name: "deliverable", type: "bytes32" },
            { name: "optParams", type: "bytes" },
          ],
          outputs: [],
        },
        {
          type: "function",
          name: "complete",
          stateMutability: "nonpayable",
          inputs: [
            { name: "jobId", type: "uint256" },
            { name: "reason", type: "bytes32" },
            { name: "optParams", type: "bytes" },
          ],
          outputs: [],
        },
        {
          type: "function",
          name: "getJob",
          stateMutability: "view",
          inputs: [{ name: "jobId", type: "uint256" }],
          outputs: [
            {
              type: "tuple",
              components: [
                { name: "id", type: "uint256" },
                { name: "client", type: "address" },
                { name: "provider", type: "address" },
                { name: "evaluator", type: "address" },
                { name: "description", type: "string" },
                { name: "budget", type: "uint256" },
                { name: "expiredAt", type: "uint256" },
                { name: "status", type: "uint8" },
                { name: "hook", type: "address" },
              ],
            },
          ],
        },
        {
          type: "event",
          name: "JobCreated",
          inputs: [
            { indexed: true, name: "jobId", type: "uint256" },
            { indexed: true, name: "client", type: "address" },
            { indexed: true, name: "provider", type: "address" },
            { indexed: false, name: "evaluator", type: "address" },
            { indexed: false, name: "expiredAt", type: "uint256" },
            { indexed: false, name: "hook", type: "address" },
          ],
          anonymous: false,
        },
      ] as const;

      const STATUS_NAMES = [
        "Open",
        "Funded",
        "Submitted",
        "Completed",
        "Rejected",
        "Expired",
      ];

      function extractJobId(txHash: Hex) {
        return publicClient
          .getTransactionReceipt({ hash: txHash })
          .then((receipt) => {
            for (const log of receipt.logs) {
              try {
                const decoded = decodeEventLog({
                  abi: agenticCommerceAbi,
                  data: log.data,
                  topics: log.topics,
                });
                if (decoded.eventName === "JobCreated") {
                  return decoded.args.jobId;
                }
              } catch {
                continue;
              }
            }
            throw new Error("Could not parse JobCreated event");
          });
      }

      async function waitForTransaction(txId: string, label: string) {
        process.stdout.write(`  Waiting for ${label}`);
        for (let i = 0; i < 60; i++) {
          await delay(2000);
          const tx = await circleClient.getTransaction({ id: txId });
          const data = tx.data?.transaction;

          if (data?.state === "COMPLETE" && data.txHash) {
            const txHash = data.txHash;
            console.log(
              ` ✓\n  Tx: ${arcTestnet.blockExplorers.default.url}/tx/${txHash}`,
            );
            return txHash as Hex;
          }
          if (data?.state === "FAILED") {
            throw new Error(`${label} failed onchain`);
          }
          process.stdout.write(".");
        }
        throw new Error(`${label} timed out`);
      }

      async function printBalances(
        title: string,
        wallets: Array<{ label: string; id?: string; address?: string | null }>,
      ) {
        console.log(`\n${title}:`);

        for (const wallet of wallets) {
          const balances = await circleClient.getWalletTokenBalance({
            id: wallet.id!,
          });
          const usdc = balances.data?.tokenBalances?.find(
            (b) => b.token?.symbol === "USDC",
          );
          console.log(`  ${wallet.label}: ${wallet.address}`);
          console.log(`    USDC: ${usdc?.amount ?? "0"}`);
        }
      }

      async function main() {
        console.log("── Step 1: Create wallets ──");

        const walletSet = await circleClient.createWalletSet({
          name: "ERC8183 Job Wallets",
        });

        const walletsResponse = await circleClient.createWallets({
          blockchains: ["ARC-TESTNET"],
          count: 2,
          walletSetId: walletSet.data?.walletSet?.id ?? "",
          accountType: "SCA",
        });

        const clientWallet = walletsResponse.data?.wallets?.[0]!;
        const providerWallet = walletsResponse.data?.wallets?.[1]!;

        console.log("\n── Step 2: Fund the client wallet ──");
        console.log("  Fund this wallet with Arc Testnet USDC:");
        console.log(`  Client: ${clientWallet.address}`);
        console.log(`  Wallet ID: ${clientWallet.id}`);
        console.log("  Public faucet:  https://faucet.circle.com");
        console.log("  Console faucet: https://console.circle.com/faucet");
        console.log("\n  This script will fund the provider wallet automatically.");

        const rl = createInterface({ input, output });
        await rl.question("\nPress Enter after the client wallet is funded... ");
        rl.close();

        console.log("\n── Step 3: Transfer starter USDC to provider ──");
        const transferTx = await circleClient.createTransaction({
          walletAddress: clientWallet.address!,
          blockchain: "ARC-TESTNET",
          tokenAddress: "0x3600000000000000000000000000000000000000",
          destinationAddress: providerWallet.address!,
          amount: [PROVIDER_STARTER_BALANCE],
          fee: { type: "level", config: { feeLevel: "MEDIUM" } },
        });
        await waitForTransaction(
          transferTx.data?.id!,
          "transfer starter USDC to provider",
        );

        console.log("\n── Step 4: Check balances ──");
        await printBalances("Balances", [
          { label: "Client", ...clientWallet },
          { label: "Provider", ...providerWallet },
        ]);

        const now = await publicClient.getBlock();
        const expiredAt = now.timestamp + 3600n;

        console.log("\n── Step 5: Create job - createJob() ──");
        const createJobTx = await circleClient.createContractExecutionTransaction({
          walletAddress: clientWallet.address!,
          blockchain: "ARC-TESTNET",
          contractAddress: AGENTIC_COMMERCE_CONTRACT,
          abiFunctionSignature: "createJob(address,address,uint256,string,address)",
          abiParameters: [
            providerWallet.address!,
            clientWallet.address!,
            expiredAt.toString(),
            "ERC-8183 demo job on Arc Testnet",
            "0x0000000000000000000000000000000000000000",
          ],
          fee: { type: "level", config: { feeLevel: "MEDIUM" } },
        });
        const createJobTxHash = await waitForTransaction(
          createJobTx.data?.id!,
          "create job",
        );
        const jobId = await extractJobId(createJobTxHash);
        console.log(`  Job ID: ${jobId}`);

        console.log("\n── Step 6: Set budget - setBudget() ──");
        const setBudgetTx = await circleClient.createContractExecutionTransaction({
          walletAddress: providerWallet.address!,
          blockchain: "ARC-TESTNET",
          contractAddress: AGENTIC_COMMERCE_CONTRACT,
          abiFunctionSignature: "setBudget(uint256,uint256,bytes)",
          abiParameters: [jobId.toString(), JOB_BUDGET.toString(), "0x"],
          fee: { type: "level", config: { feeLevel: "MEDIUM" } },
        });
        await waitForTransaction(setBudgetTx.data?.id!, "set budget");

        console.log("\n── Step 7: Approve USDC - approve() ──");
        const approveTx = await circleClient.createContractExecutionTransaction({
          walletAddress: clientWallet.address!,
          blockchain: "ARC-TESTNET",
          contractAddress: "0x3600000000000000000000000000000000000000",
          abiFunctionSignature: "approve(address,uint256)",
          abiParameters: [AGENTIC_COMMERCE_CONTRACT, JOB_BUDGET.toString()],
          fee: { type: "level", config: { feeLevel: "MEDIUM" } },
        });
        await waitForTransaction(approveTx.data?.id!, "approve USDC");

        console.log("\n── Step 8: Fund escrow - fund() ──");
        const fundTx = await circleClient.createContractExecutionTransaction({
          walletAddress: clientWallet.address!,
          blockchain: "ARC-TESTNET",
          contractAddress: AGENTIC_COMMERCE_CONTRACT,
          abiFunctionSignature: "fund(uint256,bytes)",
          abiParameters: [jobId.toString(), "0x"],
          fee: { type: "level", config: { feeLevel: "MEDIUM" } },
        });
        await waitForTransaction(fundTx.data?.id!, "fund escrow");

        console.log("\n── Step 9: Submit deliverable - submit() ──");
        const deliverableHash = keccak256(toHex("arc-erc8183-demo-deliverable"));
        const submitTx = await circleClient.createContractExecutionTransaction({
          walletAddress: providerWallet.address!,
          blockchain: "ARC-TESTNET",
          contractAddress: AGENTIC_COMMERCE_CONTRACT,
          abiFunctionSignature: "submit(uint256,bytes32,bytes)",
          abiParameters: [jobId.toString(), deliverableHash, "0x"],
          fee: { type: "level", config: { feeLevel: "MEDIUM" } },
        });
        await waitForTransaction(submitTx.data?.id!, "submit deliverable");

        console.log("\n── Step 10: Complete job - complete() ──");
        const reasonHash = keccak256(toHex("deliverable-approved"));
        const completeTx = await circleClient.createContractExecutionTransaction({
          walletAddress: clientWallet.address!,
          blockchain: "ARC-TESTNET",
          contractAddress: AGENTIC_COMMERCE_CONTRACT,
          abiFunctionSignature: "complete(uint256,bytes32,bytes)",
          abiParameters: [jobId.toString(), reasonHash, "0x"],
          fee: { type: "level", config: { feeLevel: "MEDIUM" } },
        });
        await waitForTransaction(completeTx.data?.id!, "complete job");

        console.log("\n── Step 11: Check final job state ──");
        const job = await publicClient.readContract({
          address: AGENTIC_COMMERCE_CONTRACT,
          abi: agenticCommerceAbi,
          functionName: "getJob",
          args: [jobId],
        });
        console.log(`  Job ID: ${jobId}`);
        console.log(`  Status: ${STATUS_NAMES[Number(job.status)]}`);
        console.log(`  Budget: ${formatUnits(job.budget, 6)} USDC`);
        console.log(`  Hook: ${job.hook}`);
        console.log(`  Deliverable hash submitted: ${deliverableHash}`);

        console.log("\n── Step 12: Check final balances ──");
        await printBalances("Balances", [
          { label: "Client", ...clientWallet },
          { label: "Provider", ...providerWallet },
        ]);
      }

      main().catch((error) => {
        console.error("\nError:", error.message || error);
        process.exit(1);
      });
      ```

      ```python index.py expandable theme={null}
      import os
      import sys
      import time

      from circle.web3 import developer_controlled_wallets, utils
      from dotenv import load_dotenv
      from web3 import Web3

      load_dotenv()

      # Bootstrap the provider wallet from the client wallet to avoid a second faucet request.
      PROVIDER_STARTER_BALANCE = "1"

      AGENTIC_COMMERCE_CONTRACT = "0x0747EEf0706327138c69792bF28Cd525089e4583"
      JOB_BUDGET = "5000000" # 5 USDC (ERC-20, 6 decimals)

      circle_client = utils.init_developer_controlled_wallets_client(
          api_key=os.getenv("CIRCLE_API_KEY"),
          entity_secret=os.getenv("CIRCLE_ENTITY_SECRET"),
      )

      wallet_sets_api = developer_controlled_wallets.WalletSetsApi(circle_client)
      wallets_api = developer_controlled_wallets.WalletsApi(circle_client)
      transactions_api = developer_controlled_wallets.TransactionsApi(circle_client)

      web3 = Web3(Web3.HTTPProvider("https://rpc.testnet.arc.io"))

      agentic_commerce_abi = [
          {
              "type": "function",
              "name": "getJob",
              "stateMutability": "view",
              "inputs": [{"name": "jobId", "type": "uint256"}],
              "outputs": [
                  {
                      "type": "tuple",
                      "components": [
                          {"name": "id", "type": "uint256"},
                          {"name": "client", "type": "address"},
                          {"name": "provider", "type": "address"},
                          {"name": "evaluator", "type": "address"},
                          {"name": "description", "type": "string"},
                          {"name": "budget", "type": "uint256"},
                          {"name": "expiredAt", "type": "uint256"},
                          {"name": "status", "type": "uint8"},
                          {"name": "hook", "type": "address"},
                      ],
                  }
              ],
          },
          {
              "type": "event",
              "name": "JobCreated",
              "inputs": [
                  {"indexed": True, "name": "jobId", "type": "uint256"},
                  {"indexed": True, "name": "client", "type": "address"},
                  {"indexed": True, "name": "provider", "type": "address"},
                  {"indexed": False, "name": "evaluator", "type": "address"},
                  {"indexed": False, "name": "expiredAt", "type": "uint256"},
                  {"indexed": False, "name": "hook", "type": "address"},
              ],
              "anonymous": False,
          },
      ]

      STATUS_NAMES = [
          "Open",
          "Funded",
          "Submitted",
          "Completed",
          "Rejected",
          "Expired",
      ]


      def extract_job_id(tx_hash: str) -> int:
          contract = web3.eth.contract(
              address=Web3.to_checksum_address(AGENTIC_COMMERCE_CONTRACT),
              abi=agentic_commerce_abi,
          )
          receipt = web3.eth.get_transaction_receipt(tx_hash)
          logs = contract.events.JobCreated().process_receipt(receipt)

          if not logs:
              raise RuntimeError("Could not parse JobCreated event")

          return int(logs[0]["args"]["jobId"])


      def wait_for_transaction(tx_id: str, label: str) -> str:
          sys.stdout.write(f"  Waiting for {label}")
          sys.stdout.flush()

          for _ in range(60):
              time.sleep(2)
              tx = transactions_api.get_transaction(id=tx_id)
              transaction = tx.data.transaction

              if transaction.state == "COMPLETE" and transaction.tx_hash:
                  tx_hash = transaction.tx_hash
                  print(f" ✓\n  Tx: https://explorer.testnet.arc.io/tx/{tx_hash}")
                  return tx_hash
              if transaction.state == "FAILED":
                  raise RuntimeError(f"{label} failed onchain")

              sys.stdout.write(".")
              sys.stdout.flush()

          raise RuntimeError(f"{label} timed out")


      def print_balances(title: str, wallets: list[dict[str, str]]) -> None:
          print(f"\n{title}:")

          for wallet in wallets:
              balances = wallets_api.list_wallet_balance(id=wallet["id"])
              usdc_amount = "0"

              for entry in balances.data.token_balances or []:
                  balance = getattr(entry, "actual_instance", entry)
                  token = getattr(balance, "token", None)
                  token = getattr(token, "actual_instance", token)

                  if token and getattr(token, "symbol", None) == "USDC":
                      usdc_amount = getattr(balance, "amount", "0")
                      break

              print(f"  {wallet['label']}: {wallet['address']}")
              print(f"    USDC: {usdc_amount}")


      def main() -> None:
          print("── Step 1: Create wallets ──")

          wallet_set = wallet_sets_api.create_wallet_set(
              developer_controlled_wallets.CreateWalletSetRequest.from_dict({
                  "name": "ERC8183 Job Wallets",
              })
          )

          wallets_response = wallets_api.create_wallet(
              developer_controlled_wallets.CreateWalletRequest.from_dict({
                  "blockchains": ["ARC-TESTNET"],
                  "count": 2,
                  "walletSetId": wallet_set.data.wallet_set.actual_instance.id,
                  "accountType": "SCA",
              })
          )

          client_wallet = wallets_response.data.wallets[0].actual_instance
          provider_wallet = wallets_response.data.wallets[1].actual_instance

          print("\n── Step 2: Fund the client wallet ──")
          print("  Fund this wallet with Arc Testnet USDC:")
          print(f"  Client: {client_wallet.address}")
          print(f"  Wallet ID: {client_wallet.id}")
          print("  Public faucet:  https://faucet.circle.com")
          print("  Console faucet: https://console.circle.com/faucet")
          print("\n  This script will fund the provider wallet automatically.")
          input("\nPress Enter after the client wallet is funded... ")

          print("\n── Step 3: Transfer starter USDC to provider ──")
          transfer_request = (
              developer_controlled_wallets.CreateTransferTransactionForDeveloperRequest.from_dict(
                  {
                      "walletAddress": client_wallet.address,
                      "blockchain": "ARC-TESTNET",
                      "tokenAddress": "0x3600000000000000000000000000000000000000",
                      "destinationAddress": provider_wallet.address,
                      "amounts": [PROVIDER_STARTER_BALANCE],
                      "feeLevel": "MEDIUM",
                  }
              )
          )
          transfer_response = transactions_api.create_developer_transaction_transfer(
              create_transfer_transaction_for_developer_request=transfer_request
          )
          wait_for_transaction(
              transfer_response.data.id,
              "transfer starter USDC to provider",
          )

          print("\n── Step 4: Check balances ──")
          print_balances(
              "Balances",
              [
                  {"label": "Client", "address": client_wallet.address, "id": client_wallet.id},
                  {"label": "Provider", "address": provider_wallet.address, "id": provider_wallet.id},
              ],
          )

          expired_at = web3.eth.get_block("latest")["timestamp"] + 3600

          print("\n── Step 5: Create job - createJob() ──")
          create_job_request = (
              developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict(
                  {
                      "walletAddress": client_wallet.address,
                      "blockchain": "ARC-TESTNET",
                      "contractAddress": AGENTIC_COMMERCE_CONTRACT,
                      "abiFunctionSignature": "createJob(address,address,uint256,string,address)",
                      "abiParameters": [
                          provider_wallet.address,
                          client_wallet.address,
                          str(expired_at),
                          "ERC-8183 demo job on Arc Testnet",
                          "0x0000000000000000000000000000000000000000",
                      ],
                      "feeLevel": "MEDIUM",
                  }
              )
          )
          create_job_response = transactions_api.create_developer_transaction_contract_execution(
              create_job_request
          )
          create_job_tx_hash = wait_for_transaction(
              create_job_response.data.id,
              "create job",
          )
          job_id = extract_job_id(create_job_tx_hash)
          print(f"  Job ID: {job_id}")

          print("\n── Step 6: Set budget - setBudget() ──")
          set_budget_request = (
              developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict(
                  {
                      "walletAddress": provider_wallet.address,
                      "blockchain": "ARC-TESTNET",
                      "contractAddress": AGENTIC_COMMERCE_CONTRACT,
                      "abiFunctionSignature": "setBudget(uint256,uint256,bytes)",
                      "abiParameters": [str(job_id), JOB_BUDGET, "0x"],
                      "feeLevel": "MEDIUM",
                  }
              )
          )
          set_budget_response = transactions_api.create_developer_transaction_contract_execution(
              set_budget_request
          )
          wait_for_transaction(set_budget_response.data.id, "set budget")

          print("\n── Step 7: Approve USDC - approve() ──")
          approve_request = (
              developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict(
                  {
                      "walletAddress": client_wallet.address,
                      "blockchain": "ARC-TESTNET",
                      "contractAddress": "0x3600000000000000000000000000000000000000",
                      "abiFunctionSignature": "approve(address,uint256)",
                      "abiParameters": [AGENTIC_COMMERCE_CONTRACT, JOB_BUDGET],
                      "feeLevel": "MEDIUM",
                  }
              )
          )
          approve_response = transactions_api.create_developer_transaction_contract_execution(
              approve_request
          )
          wait_for_transaction(approve_response.data.id, "approve USDC")

          print("\n── Step 8: Fund escrow - fund() ──")
          fund_request = (
              developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict(
                  {
                      "walletAddress": client_wallet.address,
                      "blockchain": "ARC-TESTNET",
                      "contractAddress": AGENTIC_COMMERCE_CONTRACT,
                      "abiFunctionSignature": "fund(uint256,bytes)",
                      "abiParameters": [str(job_id), "0x"],
                      "feeLevel": "MEDIUM",
                  }
              )
          )
          fund_response = transactions_api.create_developer_transaction_contract_execution(
              fund_request
          )
          wait_for_transaction(fund_response.data.id, "fund escrow")

          print("\n── Step 9: Submit deliverable - submit() ──")
          deliverable_hash = Web3.to_hex(
              Web3.keccak(text="arc-erc8183-demo-deliverable")
          )
          submit_request = (
              developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict(
                  {
                      "walletAddress": provider_wallet.address,
                      "blockchain": "ARC-TESTNET",
                      "contractAddress": AGENTIC_COMMERCE_CONTRACT,
                      "abiFunctionSignature": "submit(uint256,bytes32,bytes)",
                      "abiParameters": [str(job_id), deliverable_hash, "0x"],
                      "feeLevel": "MEDIUM",
                  }
              )
          )
          submit_response = transactions_api.create_developer_transaction_contract_execution(
              submit_request
          )
          wait_for_transaction(submit_response.data.id, "submit deliverable")

          print("\n── Step 10: Complete job - complete() ──")
          reason_hash = Web3.to_hex(Web3.keccak(text="deliverable-approved"))
          complete_request = (
              developer_controlled_wallets.CreateContractExecutionTransactionForDeveloperRequest.from_dict(
                  {
                      "walletAddress": client_wallet.address,
                      "blockchain": "ARC-TESTNET",
                      "contractAddress": AGENTIC_COMMERCE_CONTRACT,
                      "abiFunctionSignature": "complete(uint256,bytes32,bytes)",
                      "abiParameters": [str(job_id), reason_hash, "0x"],
                      "feeLevel": "MEDIUM",
                  }
              )
          )
          complete_response = transactions_api.create_developer_transaction_contract_execution(
              complete_request
          )
          wait_for_transaction(complete_response.data.id, "complete job")

          print("\n── Step 11: Check final job state ──")
          contract = web3.eth.contract(
              address=Web3.to_checksum_address(AGENTIC_COMMERCE_CONTRACT),
              abi=agentic_commerce_abi,
          )
          job = contract.functions.getJob(job_id).call()
          print(f"  Job ID: {job_id}")
          print(f"  Status: {STATUS_NAMES[int(job[7])]}")
          print(f"  Budget: {Web3.from_wei(job[5], 'mwei')} USDC")
          print(f"  Hook: {job[8]}")
          print(f"  Deliverable hash submitted: {deliverable_hash}")

          print("\n── Step 12: Check final balances ──")
          print_balances(
              "Balances",
              [
                  {"label": "Client", "address": client_wallet.address, "id": client_wallet.id},
                  {"label": "Provider", "address": provider_wallet.address, "id": provider_wallet.id},
              ],
          )


      if __name__ == "__main__":
          try:
              main()
          except Exception as error:
              print(f"\nError: {error}")
              sys.exit(1)
      ```
    </CodeGroup>

    Run the script:

    <CodeGroup>
      ```shell Node.js theme={null}
      npm run start
      ```

      ```shell Python theme={null}
      python index.py
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Viem">
    ## Prerequisites

    Before you begin, make sure you have:

    1. Installed [Node.js v22+](https://nodejs.org/)
    2. Two self-managed EVM wallets for Arc Testnet
       * Testnet USDC in both wallets to pay for gas and for the client wallet to
         fund escrow

    ## Step 1. Set up your project

    Create a project directory, install dependencies, and configure your
    environment.

    ### 1.1. Create the project and install dependencies

    ```shell theme={null}
    mkdir erc8183-quickstart
    cd erc8183-quickstart
    npm init -y
    npm pkg set type=module
    npm pkg set scripts.start="tsx --env-file=.env index.ts"

    npm install viem
    npm install --save-dev tsx typescript @types/node
    ```

    ### 1.2. Configure TypeScript (optional)

    <Tip>
      This step is optional. It helps prevent missing types in your IDE or editor.
    </Tip>

    Create a `tsconfig.json` file:

    ```shell theme={null}
    npx tsc --init
    ```

    Then, update the `tsconfig.json` file:

    ```shell theme={null}
    cat <<'EOF' > tsconfig.json
    {
      "compilerOptions": {
        "target": "ESNext",
        "module": "ESNext",
        "moduleResolution": "bundler",
        "strict": true,
        "types": ["node"]
      }
    }
    EOF
    ```

    ### 1.3. Set environment variables

    Create a `.env` file in the project directory and add the two private keys used
    for the flow:

    ```text theme={null}
    CLIENT_PRIVATE_KEY=0xYOUR_CLIENT_PRIVATE_KEY
    PROVIDER_PRIVATE_KEY=0xYOUR_PROVIDER_PRIVATE_KEY
    ```

    * `CLIENT_PRIVATE_KEY` is the `0x`-prefixed private key for the Arc Testnet
      wallet that creates the job, approves USDC, funds escrow, and completes the
      job as the evaluator.
    * `PROVIDER_PRIVATE_KEY` is the `0x`-prefixed private key for the Arc Testnet
      wallet that sets the budget and submits the deliverable.

    The `npm run start` command loads variables from `.env` using Node.js native
    env-file support.

    <Tip>
      Prefer editing `.env` files in your IDE or editor so credentials are not
      leaked to your shell history.
    </Tip>

    ## Step 2. Prepare your wallets

    In this step, you prepare two self-managed Arc Testnet wallets for the ERC-8183
    flow. One wallet acts as the client and evaluator, and the other acts as the
    provider. If you already have two funded Arc Testnet wallets for this flow, skip
    to [Step 3](#step-3-create-the-job).

    The Step 3 through 8 code snippets explain the flow in smaller pieces. They are
    not cumulative and will not run if pasted together. To run the full workflow end
    to end, use the [complete script](#full-job-lifecycle-script-2) at the end of
    this tab.

    ### 2.1. Create or fund your wallets

    Create two self-managed EVM wallets if you do not already have them. For
    example, you can generate throwaway wallets with Foundry:

    ```shell theme={null}
    cast wallet new --json
    ```

    Run it twice, once for the client wallet and once for the provider wallet, then
    fund both wallets with Arc Testnet USDC so they can submit transactions. Fund
    the client wallet with Arc Testnet USDC so it can escrow the job budget.

    ### 2.2. Confirm wallet roles

    * the client wallet creates the job, approves USDC, funds escrow, and completes
      the job as the evaluator
    * the provider wallet sets the budget and submits the deliverable

    ## Step 3. Create the job

    Call `createJob(provider, evaluator, expiredAt, description, hook)` on the
    deployed ERC-8183 reference implementation. This creates the job in the `Open`
    state. This quickstart uses `address(0)` for `hook` so the flow stays on the
    default non-hooked path.

    ```typescript index.ts theme={null}
    const block = await publicClient.getBlock();
    const expiredAt = block.timestamp + 3600n;

    const createJobHash = await clientWalletClient.writeContract({
      address: AGENTIC_COMMERCE_CONTRACT,
      abi: agenticCommerceAbi,
      functionName: "createJob",
      args: [
        providerAccount.address,
        clientAccount.address,
        expiredAt,
        "ERC-8183 demo job on Arc Testnet",
        "0x0000000000000000000000000000000000000000",
      ],
    });
    ```

    ## Step 4. Set the budget

    In this deployed contract, the provider sets the job price by calling
    `setBudget(jobId, amount, optParams)`.

    ```typescript index.ts theme={null}
    const setBudgetHash = await providerWalletClient.writeContract({
      address: AGENTIC_COMMERCE_CONTRACT,
      abi: agenticCommerceAbi,
      functionName: "setBudget",
      args: [jobId, JOB_BUDGET, "0x"],
    });
    ```

    ## Step 5. Approve USDC and fund escrow

    Before the client can fund the job, the USDC contract must approve the ERC-8183
    contract to transfer the escrow amount. Then the client calls
    `fund(jobId, optParams)` to move the job into the `Funded` state.

    ```typescript index.ts theme={null}
    const approveHash = await clientWalletClient.writeContract({
      address: USDC_CONTRACT,
      abi: erc20Abi,
      functionName: "approve",
      args: [AGENTIC_COMMERCE_CONTRACT, JOB_BUDGET],
    });

    const fundHash = await clientWalletClient.writeContract({
      address: AGENTIC_COMMERCE_CONTRACT,
      abi: agenticCommerceAbi,
      functionName: "fund",
      args: [jobId, "0x"],
    });
    ```

    ## Step 6. Submit the deliverable

    The provider submits a `bytes32` deliverable hash, moving the job into the
    `Submitted` state.

    ```typescript index.ts theme={null}
    const deliverableHash = keccak256(toHex("arc-erc8183-demo-deliverable"));

    const submitHash = await providerWalletClient.writeContract({
      address: AGENTIC_COMMERCE_CONTRACT,
      abi: agenticCommerceAbi,
      functionName: "submit",
      args: [jobId, deliverableHash, "0x"],
    });
    ```

    ## Step 7. Complete the job

    The evaluator completes the job by calling `complete(jobId, reason, optParams)`.
    In this quickstart, the client is also the evaluator.

    ```typescript index.ts theme={null}
    const reasonHash = keccak256(toHex("work-delivered-and-approved"));

    const completeHash = await clientWalletClient.writeContract({
      address: AGENTIC_COMMERCE_CONTRACT,
      abi: agenticCommerceAbi,
      functionName: "complete",
      args: [jobId, reasonHash, "0x"],
    });
    ```

    ## Step 8. Check the final job state

    Read the job back from the contract to confirm it reached `Completed`. This
    reference implementation does not return the deliverable in `getJob()`, so the
    script prints the submitted deliverable hash from local flow state instead.

    ```typescript index.ts theme={null}
    const job = await publicClient.readContract({
      address: AGENTIC_COMMERCE_CONTRACT,
      abi: agenticCommerceAbi,
      functionName: "getJob",
      args: [jobId],
    });

    console.log(`Job ID: ${job.id}`);
    console.log(`Status: ${STATUS_NAMES[Number(job.status)]}`);
    console.log(`Budget: ${formatUnits(job.budget, 6)} USDC`);
    console.log(`Hook: ${job.hook}`);
    console.log(`Deliverable hash submitted: ${deliverableHash}`);
    ```

    This complete script combines the preceding steps into a single runnable file.

    ## Full job lifecycle script

    ```typescript index.ts expandable theme={null}
    import {
      createPublicClient,
      createWalletClient,
      decodeEventLog,
      formatUnits,
      http,
      keccak256,
      toHex,
    } from "viem";
    import { privateKeyToAccount } from "viem/accounts";
    import { arcTestnet } from "viem/chains";

    const AGENTIC_COMMERCE_CONTRACT = "0x0747EEf0706327138c69792bF28Cd525089e4583";
    const USDC_CONTRACT = "0x3600000000000000000000000000000000000000";
    const JOB_BUDGET = 1_000_000n;

    const clientAccount = privateKeyToAccount(
      process.env.CLIENT_PRIVATE_KEY as `0x${string}`,
    );
    const providerAccount = privateKeyToAccount(
      process.env.PROVIDER_PRIVATE_KEY as `0x${string}`,
    );

    const publicClient = createPublicClient({
      chain: arcTestnet,
      transport: http(),
    });

    const clientWalletClient = createWalletClient({
      account: clientAccount,
      chain: arcTestnet,
      transport: http(),
    });

    const providerWalletClient = createWalletClient({
      account: providerAccount,
      chain: arcTestnet,
      transport: http(),
    });

    const agenticCommerceAbi = [
      {
        name: "createJob",
        type: "function",
        stateMutability: "nonpayable",
        inputs: [
          { name: "provider", type: "address" },
          { name: "evaluator", type: "address" },
          { name: "expiredAt", type: "uint256" },
          { name: "description", type: "string" },
          { name: "hook", type: "address" },
        ],
        outputs: [{ name: "jobId", type: "uint256" }],
      },
      {
        name: "setBudget",
        type: "function",
        stateMutability: "nonpayable",
        inputs: [
          { name: "jobId", type: "uint256" },
          { name: "amount", type: "uint256" },
          { name: "optParams", type: "bytes" },
        ],
        outputs: [],
      },
      {
        name: "fund",
        type: "function",
        stateMutability: "nonpayable",
        inputs: [
          { name: "jobId", type: "uint256" },
          { name: "optParams", type: "bytes" },
        ],
        outputs: [],
      },
      {
        name: "submit",
        type: "function",
        stateMutability: "nonpayable",
        inputs: [
          { name: "jobId", type: "uint256" },
          { name: "deliverable", type: "bytes32" },
          { name: "optParams", type: "bytes" },
        ],
        outputs: [],
      },
      {
        name: "complete",
        type: "function",
        stateMutability: "nonpayable",
        inputs: [
          { name: "jobId", type: "uint256" },
          { name: "reason", type: "bytes32" },
          { name: "optParams", type: "bytes" },
        ],
        outputs: [],
      },
      {
        name: "getJob",
        type: "function",
        stateMutability: "view",
        inputs: [{ name: "jobId", type: "uint256" }],
        outputs: [
          {
            type: "tuple",
            components: [
              { name: "id", type: "uint256" },
              { name: "client", type: "address" },
              { name: "provider", type: "address" },
              { name: "evaluator", type: "address" },
              { name: "description", type: "string" },
              { name: "budget", type: "uint256" },
              { name: "expiredAt", type: "uint256" },
              { name: "status", type: "uint8" },
              { name: "hook", type: "address" },
            ],
          },
        ],
      },
      {
        name: "JobCreated",
        type: "event",
        anonymous: false,
        inputs: [
          { indexed: true, name: "jobId", type: "uint256" },
          { indexed: true, name: "client", type: "address" },
          { indexed: true, name: "provider", type: "address" },
          { indexed: false, name: "evaluator", type: "address" },
          { indexed: false, name: "expiredAt", type: "uint256" },
          { indexed: false, name: "hook", type: "address" },
        ],
      },
    ] as const;

    const erc20Abi = [
      {
        name: "approve",
        type: "function",
        stateMutability: "nonpayable",
        inputs: [
          { name: "spender", type: "address" },
          { name: "amount", type: "uint256" },
        ],
        outputs: [{ name: "", type: "bool" }],
      },
      {
        name: "balanceOf",
        type: "function",
        stateMutability: "view",
        inputs: [{ name: "account", type: "address" }],
        outputs: [{ name: "", type: "uint256" }],
      },
    ] as const;

    const STATUS_NAMES = [
      "Open",
      "Funded",
      "Submitted",
      "Completed",
      "Rejected",
      "Expired",
    ];

    async function waitForTransaction(hash: `0x${string}`, label: string) {
      process.stdout.write(`  Waiting for ${label}`);
      const receipt = await publicClient.waitForTransactionReceipt({ hash });
      console.log(` ✓\n  Tx: ${arcTestnet.blockExplorers.default.url}/tx/${hash}`);
      return receipt;
    }

    async function printBalances(title: string) {
      console.log(`\n${title}:`);

      const clientBalance = await publicClient.readContract({
        address: USDC_CONTRACT,
        abi: erc20Abi,
        functionName: "balanceOf",
        args: [clientAccount.address],
      });
      const providerBalance = await publicClient.readContract({
        address: USDC_CONTRACT,
        abi: erc20Abi,
        functionName: "balanceOf",
        args: [providerAccount.address],
      });

      console.log(`  Client: ${clientAccount.address}`);
      console.log(`    USDC: ${formatUnits(clientBalance, 6)}`);
      console.log(`  Provider: ${providerAccount.address}`);
      console.log(`    USDC: ${formatUnits(providerBalance, 6)}`);
    }

    async function main(): Promise<void> {
      console.log("── Step 1: Prepare accounts ──");
      console.log(`  Client: ${clientAccount.address}`);
      console.log(`  Provider: ${providerAccount.address}`);
      console.log(`  Evaluator: ${clientAccount.address}`);

      console.log("\n── Step 2: Check balances ──");
      await printBalances("Balances");

      const block = await publicClient.getBlock();
      const expiredAt = block.timestamp + 3600n;

      console.log("\n── Step 3: Create job - createJob() ──");
      const createJobHash = await clientWalletClient.writeContract({
        address: AGENTIC_COMMERCE_CONTRACT,
        abi: agenticCommerceAbi,
        functionName: "createJob",
        args: [
          providerAccount.address,
          clientAccount.address,
          expiredAt,
          "ERC-8183 demo job on Arc Testnet",
          "0x0000000000000000000000000000000000000000",
        ],
      });
      const createJobReceipt = await waitForTransaction(
        createJobHash,
        "create job",
      );

      let jobId: bigint | undefined;
      for (const log of createJobReceipt.logs) {
        try {
          const decoded = decodeEventLog({
            abi: agenticCommerceAbi,
            data: log.data,
            topics: log.topics,
          });
          if (decoded.eventName === "JobCreated") {
            jobId = decoded.args.jobId;
            break;
          }
        } catch {
          continue;
        }
      }

      if (jobId == null) {
        throw new Error("Could not parse JobCreated event");
      }

      console.log(`  Job ID: ${jobId}`);

      console.log("\n── Step 4: Set budget - setBudget() ──");
      const setBudgetHash = await providerWalletClient.writeContract({
        address: AGENTIC_COMMERCE_CONTRACT,
        abi: agenticCommerceAbi,
        functionName: "setBudget",
        args: [jobId, JOB_BUDGET, "0x"],
      });
      await waitForTransaction(setBudgetHash, "set budget");

      console.log("\n── Step 5: Approve USDC - approve() ──");
      const approveHash = await clientWalletClient.writeContract({
        address: USDC_CONTRACT,
        abi: erc20Abi,
        functionName: "approve",
        args: [AGENTIC_COMMERCE_CONTRACT, JOB_BUDGET],
      });
      await waitForTransaction(approveHash, "approve USDC");

      console.log("\n── Step 6: Fund escrow - fund() ──");
      const fundHash = await clientWalletClient.writeContract({
        address: AGENTIC_COMMERCE_CONTRACT,
        abi: agenticCommerceAbi,
        functionName: "fund",
        args: [jobId, "0x"],
      });
      await waitForTransaction(fundHash, "fund escrow");

      console.log("\n── Step 7: Submit deliverable - submit() ──");
      const deliverableHash = keccak256(toHex("arc-erc8183-demo-deliverable"));
      const submitHash = await providerWalletClient.writeContract({
        address: AGENTIC_COMMERCE_CONTRACT,
        abi: agenticCommerceAbi,
        functionName: "submit",
        args: [jobId, deliverableHash, "0x"],
      });
      await waitForTransaction(submitHash, "submit deliverable");

      console.log("\n── Step 8: Complete job - complete() ──");
      const reasonHash = keccak256(toHex("work-delivered-and-approved"));
      const completeHash = await clientWalletClient.writeContract({
        address: AGENTIC_COMMERCE_CONTRACT,
        abi: agenticCommerceAbi,
        functionName: "complete",
        args: [jobId, reasonHash, "0x"],
      });
      await waitForTransaction(completeHash, "complete job");

      console.log("\n── Step 9: Check final job state ──");
      const job = await publicClient.readContract({
        address: AGENTIC_COMMERCE_CONTRACT,
        abi: agenticCommerceAbi,
        functionName: "getJob",
        args: [jobId],
      });

      console.log(`  Job ID: ${job.id}`);
      console.log(`  Status: ${STATUS_NAMES[Number(job.status)]}`);
      console.log(`  Budget: ${formatUnits(job.budget, 6)} USDC`);
      console.log(`  Hook: ${job.hook}`);
      console.log(`  Deliverable hash submitted: ${deliverableHash}`);

      console.log("\n── Step 10: Check final balances ──");
      await printBalances("Balances");
    }

    void main().catch((error) => {
      console.error("\nError:", error);
      process.exit(1);
    });
    ```

    Run the script:

    ```shell theme={null}
    npm run start
    ```
  </Tab>
</Tabs>

## Verify the result

If the flow succeeds, the output should show:

* a created job ID
* a completed final status
* the client balance reduced by the funded escrow amount
* the provider balance increased after completion

<Note>
  If platform or evaluator fees are configured on the deployed contract, the
  provider receives the net amount after fees rather than the full job budget.
</Note>

You can also inspect the transaction links in the terminal output on
[Arc Testnet Explorer](https://explorer.testnet.arc.io).

## Summary

After completing this quickstart, you've successfully:

* Set up a project for running an ERC-8183 job flow on Arc Testnet
* Prepared client and provider wallets for the client, provider, and evaluator
  roles
* Walked through an example ERC-8183 job lifecycle
* Confirmed balances and job state in the script output and reviewed
  transactions on the Arc Testnet Explorer
