> For the complete documentation index, see [llms.txt](https://docs.pharos.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.pharos.xyz/tooling-and-infrastructure/pharos-skill-engine-guide/piggybank-skill-guide.md).

# Piggy Bank Tutorial

This guide walks through building a time-locked native token vault (SimpleVault/PiggyBank) as a Pharos Skill — from setting up the Skill Engine in VS Code, writing the contract and reference file inside the Skill Engine folder, updating `SKILL.md`, to testing everything through Claude in your VS Code terminal.

## What You Will Build

By the end of this guide you will have a published Pharos skill that lets anyone ask an AI to:

* Deploy a **Simple Vault** (piggy bank) contract on Pharos
* Deposit PHRS into the vault
* Check how much time remains before withdrawal is allowed
* Withdraw funds after the time lock expires
* Query the full on-chain history of deposits and withdrawals

Along the way, you will learn three core Solidity/EVM concepts:

| Concept           | Where It Appears                          |
| ----------------- | ----------------------------------------- |
| `--value` flag    | Sending native PHRS with a function call  |
| `block.timestamp` | Setting and enforcing a time lock         |
| Event querying    | Reading on-chain history with `cast logs` |

## How the Skill Engine Works

### What is a Pharos Skill?

A Pharos Skill is a folder of files that teaches an AI (Claude) how to interact with the Pharos blockchain. When you ask the AI something like *"Deploy a vault on Pharos"*, it reads your skill files to know exactly which commands to run.

The AI is smart but it does not automatically know your contract. You teach it by writing instruction files in plain Markdown. The AI reads those files and follows them precisely.

### The Flow from User Prompt to On-Chain Action

```
User says "Deploy a Simple Vault with a 1 hour lock"
        ↓
AI reads SKILL.md → finds "Deploy Simple Vault" row in Capability Index
        ↓
AI reads references/vault.md → finds the Deploy section
        ↓
AI follows the Agent Guidelines step by step
        ↓
AI copies SimpleVault.sol to your project, generates deploy script, runs it
        ↓
Contract is live on Pharos. AI shows you the address and explorer link.
```

### Folder Structure Overview

```
pharos-skill-engine/
│
├── SKILL.md                  ← Entry point. Tells the AI what this skill does
│                               and maps user needs to reference files.
│
├── assets/                   ← Files the AI uses directly
│   ├── networks.json         ← RPC URLs, chain IDs, explorer URLs for Pharos
│   ├── erc20/                ← Built-in ERC20 contract template
│   ├── airdrop/              ← Built-in airdrop contracts
│   └── vault/                ← Your vault contract lives here
│
└── references/               ← Instruction manuals written for the AI
    ├── query.md              ← Teaches AI how to check balances
    ├── transaction.md        ← Teaches AI how to send transactions
    ├── contract.md           ← Teaches AI how to deploy contracts
    └── vault.md              ← Your new file — teaches AI about the vault
```

### What is a Reference File?

A reference file is a Markdown file the AI reads at runtime. It is not code and not meant for humans to execute directly. It tells the AI:

* Which `cast` or `forge` command to run
* What each parameter in the command means
* What the terminal output means
* What errors can happen and what to tell the user

Every section in a reference file follows the same 4-part structure:

```
1. Command Template   — the actual command with <placeholders>
2. Parameters table   — what each <placeholder> means
3. Output Parsing     — what fields to look for in the output
4. Error Handling     — what can go wrong and how to respond
```

Plus an `> Agent Guidelines` block that gives the AI an ordered checklist to follow.

## Prerequisites

### Tools to Install

**Foundry** — compiles and deploys Solidity contracts

```bash
curl -L https://foundry.paradigm.xyz | bash
source ~/.zshenv && foundryup
```

Verify installation:

```bash
cast --version
forge --version
```

Both should print a version number. If either fails, re-run `foundryup`.

**Claude Code** — the AI CLI that runs your skill

```bash
npm install -g @anthropic-ai/claude-code
```

Verify:

```bash
claude --version
```

### Wallet Setup

1. Create an EVM-compatible wallet (MetaMask or any EVM wallet)
2. Add the Pharos Atlantic Testnet to your wallet:
   * **Network Name:** `Pharos Atlantic Testnet`
   * **RPC URL:** `https://atlantic.dplabs-internal.com`
   * **Chain ID:** `688689`
   * **Symbol:** `PHRS`
3. Get testnet PHRS from the Pharos faucet or directly from the team.
4. Export your private key — you will need it when deploying

> ⚠️ **Security Warning:** Never share your private key. Never commit it to git. Always use the `$PRIVATE_KEY`environment variable — never paste it directly into commands you share with others.

## Project Structure

Before you start, your skill folder should look like this:

```
pharos-skill-engine-0.1.0/
├── SKILL.md
├── assets/
│   ├── networks.json
│   ├── tokens.json
│   ├── erc20/
│   │   └── StandardERC20.sol
│   ├── airdrop/
│   │   └── (airdrop contracts)
│   └── templates/
│       └── (script templates)
└── references/
    ├── contract.md
    ├── query.md
    ├── script-gen.md
    └── transaction.md
```

After completing this guide it will look like this:

```
pharos-skill-engine-0.1.0/
├── SKILL.md                          ← edited
├── assets/
│   └── vault/
│       └── SimpleVault.sol           ← new
└── references/
    └── vault.md                      ← new
```

You are adding 2 new files and making 2 small edits to `SKILL.md`.

## Step 1 — Write the Smart Contract

### Create the File

Create a new folder and file at this exact path inside the Skill Engine folder:

```
assets/vault/SimpleVault.sol
```

### The Contract

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract SimpleVault {
    address public owner;
    uint256 public unlockTime;

    event Deposited(address indexed sender, uint256 amount, uint256 unlockTime);
    event Withdrawn(address indexed owner, uint256 amount);

    constructor(uint256 _lockDurationSeconds) {
        owner = msg.sender;
        unlockTime = block.timestamp + _lockDurationSeconds;
    }

    function deposit() external payable {
        require(msg.value > 0, "Must send PHRS");
        emit Deposited(msg.sender, msg.value, unlockTime);
    }

    function withdraw() external {
        require(msg.sender == owner, "Not owner");
        require(block.timestamp >= unlockTime, "Still locked");
        uint256 balance = address(this).balance;
        require(balance > 0, "Nothing to withdraw");
        payable(owner).transfer(balance);
        emit Withdrawn(owner, balance);
    }

    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }

    function timeLeft() external view returns (uint256) {
        if (block.timestamp >= unlockTime) return 0;
        return unlockTime - block.timestamp;
    }
}
```

#### Contract Explained

| Part                                | What It Does                                                                       |
| ----------------------------------- | ---------------------------------------------------------------------------------- |
| `owner`                             | Stores the deployer's address. Only this address can withdraw.                     |
| `unlockTime`                        | The Unix timestamp after which withdrawal is allowed. Set once at deploy.          |
| `constructor(_lockDurationSeconds)` | Runs once at deploy. Sets `unlockTime = now + duration`.                           |
| `deposit()`                         | Marked `payable` — means it can receive PHRS. Requires `msg.value > 0`.            |
| `withdraw()`                        | Checks caller is owner AND current time is past `unlockTime`. Sends all PHRS back. |
| `timeLeft()`                        | Read-only. Returns seconds remaining. Returns `0` if unlocked.                     |
| `getBalance()`                      | Read-only. Returns how much PHRS is currently in the vault.                        |
| `Deposited` event                   | Logged on-chain every time someone deposits. Queryable with `cast logs`.           |
| `Withdrawn` event                   | Logged on-chain when owner withdraws. Queryable with `cast logs`.                  |

### Verify the Contract Compiles

```bash
cd ~/Desktop
forge init vault-test
cd vault-test
cp /path/to/pharos-skill-engine-0.1.0/assets/vault/SimpleVault.sol src/SimpleVault.sol
forge build
```

You should see `Compiler run successful`. If you see errors, check that the Solidity version in `foundry.toml` is `^0.8.20` or higher.

## Step 2 — Create the Reference File

### Create the File

Create a new file at:

```
references/vault.md
```

### Why This File Exists

This is the instruction manual the AI reads when a user asks about the vault. Without this file the AI has no idea your vault contract exists or how to use it.

### The Complete File Contents

Paste this entire block into `references/vault.md`:

````
**# Vault Operation Instructions**

This file contains detailed instructions for deploying and interacting with the
SimpleVault (Piggy Bank) contract on Pharos. It teaches three key concepts:
sending native tokens with `--value`, using `block.timestamp` for time locks,
and querying on-chain events.

> **Network Configuration**: The `<rpc>` parameter in all commands is read from
> the corresponding network's `rpcUrl` field in `assets/networks.json`.
> Defaults to the Atlantic testnet.
>
> **Private Key Configuration**: All write operations must explicitly pass the
> private key via the `--private-key` parameter.
> Recommended: `--private-key $PRIVATE_KEY`.

---

**## Deploy SimpleVault**

**### Overview**

SimpleVault is a piggy bank contract. The deployer sets a time lock duration in
seconds at deployment. PHRS deposited into the vault cannot be withdrawn until
the lock expires.

**### Step 1: Generate Deployment Script**

The Agent generates `script/DeploySimpleVault.s.sol` in the user's project:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Script.sol";
import "forge-std/console.sol";
import "";

contract DeploySimpleVault is Script {
    function run() external {
        uint256 lockDurationSeconds = ;
        vm.startBroadcast();
        SimpleVault vault = new SimpleVault(lockDurationSeconds);
        console.log("=== Deploy Result ===");
        console.log("Vault address:", address(vault));
        console.log("Owner:", vault.owner());
        console.log("Unlock time (unix):", vault.unlockTime());
        console.log("Lock duration (seconds):", lockDurationSeconds);
        console.log("Deployer:", msg.sender);
        vm.stopBroadcast();
    }
}
```

**### Step 2: Execute Deployment**

**Command Template**

```bash
forge script script/DeploySimpleVault.s.sol:DeploySimpleVault \
  --rpc-url  \
  --private-key $PRIVATE_KEY \
  --broadcast
```

**Parameters**

|** Parameter **|** Type **|** Required **|** Description **|
|---|---|---|---|
| `lockDurationSeconds` | uint256 | Yes | Lock duration in seconds. `3600` = 1 hour, `86400` = 1 day |
| `--rpc-url` | string | Yes | RPC endpoint URL, read from `assets/networks.json` |
| `--private-key` | string | Yes | Deployer private key via `$PRIVATE_KEY` |

**Output Parsing**

|** Field **|** Description **|
|---|---|
| `Vault address:` | Deployed contract address — save this for all future interactions |
| `Owner:` | Address that can withdraw funds |
| `Unlock time (unix):` | Unix timestamp when withdrawal becomes allowed |

**Error Handling**

|** Error **|** Cause **|** Fix **|
|---|---|---|
| `compiler error` | Contract compilation failed | Check source file path and Foundry setup |
| `insufficient funds` | Not enough balance for gas | Check balance with `cast balance` |
| `connection refused` | Missing `--rpc-url` or RPC unreachable | Confirm `--rpc-url` is passed |

> **Agent Guidelines:**
> 1. Complete "Write Operation Pre-checks" (see SKILL.md)
> 2. Ask user for `lockDurationSeconds` (suggest `3600` for 1 hour as default)
> 3. Copy `assets/vault/SimpleVault.sol` to the user's project at `src/vault/SimpleVault.sol`
> 4. Check deployer balance: `cast balance <deployer> --rpc-url <rpc> --ether`
> 5. Generate `script/DeploySimpleVault.s.sol` with the lock duration filled in
> 6. Read `rpcUrl` from `assets/networks.json`
> 7. Execute `forge script` with `--rpc-url`, `--private-key $PRIVATE_KEY`, `--broadcast`
> 8. Extract vault address from output, show block explorer link: `<explorerUrl>/address/<vaultAddress>`
> 9. Ask if user wants to verify the contract. If yes, wait ~10 seconds then verify.

---

**## Verify SimpleVault**

**Command Template**

```bash
forge verify-contract  src/vault/SimpleVault.sol:SimpleVault \
  --chain-id  \
  --verifier-url /v1/explorer/command_api/contract \
  --verifier blockscout \
  --constructor-args $(cast abi-encode "constructor(uint256)" )
```

**Parameters**

|** Parameter **|** Type **|** Required **|** Description **|
|---|---|---|---|
| `<vault_address>` | string | Yes | Deployed vault contract address |
| `<chain_id>` | number | Yes | Read from `chainId` in `assets/networks.json` |
| `<explorer_api_url>` | string | Yes | Read from `explorerApiUrl` in `assets/networks.json` |
| `<lockDurationSeconds>` | uint256 | Yes | Same value used during deployment |

**Error Handling**

|** Error **|** Cause **|** Fix **|
|---|---|---|
| `contract not found` | Contract not indexed yet | Wait 10–15 seconds and retry |
| `verification failed` | Source mismatch | Check compiler version matches |
| `constructor arguments mismatch` | Wrong args encoding | Confirm `lockDurationSeconds` value |

---

**## Deposit PHRS into the Vault**

**Command Template**

```bash
cast send  "deposit()" \
  --value ether \
  --private-key $PRIVATE_KEY \
  --rpc-url 
```

**Parameters**

|** Parameter **|** Type **|** Required **|** Description **|
|---|---|---|---|
| `<vault_address>` | string | Yes | Vault contract address from deployment |
| `<amount>` | number | Yes | Amount of PHRS to deposit. Example: `0.1ether`, `1ether` |
| `--value` | flag | Yes | Sends native PHRS with the call. Without it the call sends 0 PHRS and reverts |
| `--private-key` | string | Yes | Sender private key via `$PRIVATE_KEY` |
| `--rpc-url` | string | Yes | RPC endpoint URL from `assets/networks.json` |

**Output Parsing**

|** Field **|** Description **|
|---|---|
| `status` | `1` = success, `0` = failed |
| `transactionHash` | Use to look up the Deposited event on the explorer |

**Error Handling**

|** Error **|** Cause **|** Fix **|
|---|---|---|
| `execution reverted: Must send PHRS` | `--value` flag missing or 0 | Add `--value <amount>ether` to the command |
| `insufficient funds` | Wallet balance too low | Check with `cast balance <address> --rpc-url <rpc> --ether` |
| `invalid address` | Wrong vault address format | Confirm address is `0x` + 40 hex chars |

> **Agent Guidelines:** Complete "Write Operation Pre-checks" (see SKILL.md).
> Check wallet balance before sending. After successful deposit, show transaction
> link `<explorerUrl>/tx/<txHash>` and remind user of the unlock time.

---

**## Check Vault Balance**

**Command Template**

```bash
cast call  "getBalance()(uint256)" --rpc-url 
```

**Output Parsing**

- Returns raw balance in wei
- Convert to PHRS: `cast --from-wei <value>`

**Error Handling**

|** Error **|** Cause **|** Fix **|
|---|---|---|
| Empty return value | No contract at that address | Confirm vault address is correct |
| `invalid address` | Wrong address format | Check address is `0x` + 40 hex characters |

---

**## Check Time Remaining Before Withdrawal**

**Command Template**

```bash
cast call  "timeLeft()(uint256)" --rpc-url 
```

**Output Parsing**

- Returns seconds remaining as a number
- `0` means the vault is unlocked — withdrawal is allowed
- Any positive number means that many seconds remain

**Error Handling**

|** Error **|** Cause **|** Fix **|
|---|---|---|
| Empty return value | No contract at that address | Confirm vault address is correct |

> **Agent Guidelines:** Convert seconds to human-readable format
> (e.g. "59 minutes 2 seconds remaining" or "Vault is unlocked — you can withdraw now").
> Also show `unlockTime` with:
> `cast call <vault_address> "unlockTime()(uint256)" --rpc-url <rpc>`

---

**## Withdraw PHRS from the Vault**

**Command Template**

```bash
cast send  "withdraw()" \
  --private-key $PRIVATE_KEY \
  --rpc-url 
```

**Parameters**

|** Parameter **|** Type **|** Required **|** Description **|
|---|---|---|---|
| `<vault_address>` | string | Yes | Vault contract address |
| `--private-key` | string | Yes | Must be the owner's key — only the deployer can withdraw |
| `--rpc-url` | string | Yes | RPC endpoint URL from `assets/networks.json` |

**Output Parsing**

|** Field **|** Description **|
|---|---|
| `status` | `1` = success, `0` = failed |
| `transactionHash` | Use to look up the Withdrawn event on the explorer |

**Error Handling**

|** Error **|** Cause **|** Fix **|
|---|---|---|
| `execution reverted: Still locked` | Time lock not expired yet | Run `timeLeft()` to see remaining time, wait and retry |
| `execution reverted: Not owner` | Wrong private key | Confirm `$PRIVATE_KEY` belongs to the deployer wallet |
| `execution reverted: Nothing to withdraw` | Vault balance is 0 | Deposit PHRS first |
| `insufficient funds` | Not enough gas | Check wallet balance |

> **Agent Guidelines:** Always run `timeLeft()` before attempting withdrawal.
> If result > 0, tell the user how long to wait and do NOT execute `withdraw()`.
> Only proceed when `timeLeft()` returns `0`.
> After success, show `<explorerUrl>/tx/<txHash>`.

---

**## Query Events (Deposit and Withdrawal History)**

**### Query Deposit History**

**Command Template**

```bash
cast logs \
  --rpc-url  \
  --address  \
  "Deposited(address,uint256,uint256)"
```

**Output Parsing**

|** Field **|** Description **|
|---|---|
| `topics[1]` | Depositor address (indexed) |
| `data[0]` | Amount deposited in wei — divide by 10^18 for PHRS |
| `data[1]` | Unlock time as Unix timestamp |
| `blockNumber` | Block where the deposit happened |
| `transactionHash` | Deposit transaction hash |

**### Query Withdrawal History**

**Command Template**

```bash
cast logs \
  --rpc-url  \
  --address  \
  "Withdrawn(address,uint256)"
```

**Output Parsing**

|** Field **|** Description **|
|---|---|
| `topics[1]` | Owner address (indexed) |
| `data[0]` | Amount withdrawn in wei — divide by 10^18 for PHRS |

**Error Handling**

|** Error **|** Cause **|** Fix **|
|---|---|---|
| Empty result | No events emitted yet | Inform user no activity has occurred on this vault |
| `invalid address` | Wrong vault address | Confirm address format |
| Connection timeout | RPC unreachable | Check network connection |

> **Agent Guidelines:** Convert wei to PHRS (divide by 10^18). Convert unix
> timestamps to readable dates. Include transaction links
> `<explorerUrl>/tx/<txHash>` for each event. If no logs returned, clearly
> state no activity has occurred yet.
````

## Step 3 — Edit SKILL.md

You need to make two small edits to `SKILL.md`.

### Edit 1 — Add Vault Keywords to the Description

Find the end of the `description` field. It currently ends with:

```
or generate Web3 scripts targeting Pharos Chain / Pharos Network. Do not attempt...
```

Change it to:

```
or generate Web3 scripts targeting Pharos Chain / Pharos Network, or deploy a
vault, piggy bank, time lock, lock PHRS, or deposit and withdraw with a
time-locked contract. Do not attempt...
```

**Why:** This controls when the AI triggers your skill. Adding these keywords means the AI will load your skill whenever someone asks about vaults or time locks.

### Edit 2 — Add Rows to the Capability Index Table

Find the last row of the Capability Index table. It currently ends with:

```
| Generate contract interaction scripts ... | → `references/script-gen.md` |
```

Add these 5 rows directly after it:

```markdown
| Deploy Simple Vault / Piggy Bank (time-locked PHRS deposit) | `forge script` + built-in Vault template | → `references/vault.md` |
| Deposit PHRS into vault | `cast send` with `--value` | → `references/vault.md#deposit-phrs-into-the-vault` |
| Check vault time lock / time remaining | `cast call timeLeft()` | → `references/vault.md#check-time-remaining-before-withdrawal` |
| Withdraw from vault after time lock expires | `cast send withdraw()` | → `references/vault.md#withdraw-phrs-from-the-vault` |
| Query vault deposit and withdrawal events | `cast logs` | → `references/vault.md#query-events-deposit-and-withdrawal-history` |
```

**Why:** This table is how the AI navigates the skill. When a user asks to deposit PHRS, the AI scans this table, finds the matching row, and opens the reference file linked there.

## Step 4 — Run and Test the Skill

### Setup (One Time Only)

1. **Set your private key in the terminal:**

```bash
export PRIVATE_KEY=0xYourPrivateKeyHere
```

2. **Open Claude Code inside the skill folder:**

```bash
cd /path/to/pharos-skill-engine-0.1.0
```

```bash
claude
```

### Full End-to-End Test Sequence

Run these prompts in order inside Claude Code:

***

3. **Deploy**

> *Deploy a Simple Vault on Pharos with a 5 minute lock*

The AI will ask you to confirm your wallet address and network, check your balance, generate the deploy script, and run it. You will get back a vault address like `0xAbc123...`.

***

4. **Deposit**

> *Deposit 0.1 PHRS into vault at 0xYourVaultAddress*

The AI runs `cast send` with `--value 0.1ether`. You will see a transaction hash.

***

5. **Check Time Remaining**

> *How much time is left on my vault at 0xYourVaultAddress*

The AI runs `timeLeft()` and tells you something like "4 minutes 32 seconds remaining."

***

6. **Try Early Withdrawal — Should Fail Gracefully**

> *Withdraw from my vault at 0xYourVaultAddress*

The AI checks `timeLeft()` first, sees the lock has not expired, and tells you to wait. It will **not** send the transaction.

***

7. **Withdraw After Lock Expires**

Wait 5 minutes, then repeat the withdraw prompt. This time the AI will execute `withdraw()` and your PHRS comes back to your wallet.

***

8. **Query Events**

> *Show me the deposit history for vault 0xYourVaultAddress*

The AI runs `cast logs` and shows you the `Deposited` event with the amount, timestamp, and transaction link.

## Common Errors and Fixes

| Error                              | Cause                       | Fix                                                 |
| ---------------------------------- | --------------------------- | --------------------------------------------------- |
| `Still locked`                     | Too early to withdraw       | Run `timeLeft()` and wait                           |
| `Not owner`                        | Wrong wallet                | Check `$PRIVATE_KEY` matches the deployer address   |
| `Must send PHRS`                   | Missing `--value` flag      | Add `--value 0.1ether` to the deposit command       |
| `Nothing to withdraw`              | Vault is empty              | Deposit PHRS first                                  |
| `insufficient funds`               | Not enough PHRS for gas     | Get testnet PHRS from the faucet                    |
| `connection refused`               | Missing `--rpc-url`         | Always pass `--rpc-url` explicitly in every command |
| `compiler error`                   | Solidity compilation failed | Check Foundry is up to date with `foundryup`        |
| `contract not found` during verify | Explorer not indexed yet    | Wait 10–15 seconds and retry verification           |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.pharos.xyz/tooling-and-infrastructure/pharos-skill-engine-guide/piggybank-skill-guide.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
