> For the complete documentation index, see [llms.txt](https://docs.townsq.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.townsq.xyz/townsquare-lending-vault/townsquare-vault-setup.md).

# TownSquare Vault Setup

## TownSqVault — Deploy, Deposit, Withdraw

This repo contains `TownSqVault`, an upgradeable ERC-4626 vault that supplies deposits into TownSq via `spokeTokenOperations.topup(...)`, and uses a **2-step withdrawal** flow:

* Step 1: `initiateWithdraw(shares, receiver, owner)` initiates the withdrawal and burns shares.
* Step 2: After a timelock (currently **6 hours**), `withdraw(owner)` finalizes by transferring assets to `receiver` (or mints shares back if transfer fails).

***

### What gets deployed

`TownSqVault` is written as an **upgradeable** contract and is intended to be used behind a proxy.

* **Implementation**: `src/TownSqVault.sol`
* **Proxy pattern**: `TransparentUpgradeableProxy`
* **Initializer**: `TownSqVault.initialize(...)` (called once via proxy constructor or upgrade tooling)

***

### Deployment (Foundry)

#### Prerequisites

* **Foundry** installed (`forge`, `cast`)
* RPC URL for your target network (the script is currently set up for **chainId = 143**)
* A deployer private key loaded in your environment (examples use Foundry’s `--sender` + broadcast key)

#### Parameters you must supply

`TownSqVault.initialize(...)` requires:

* **Owner**: `_owner` (admin of vault settings like fees)
* **TownSq addresses**:
  * `_spokeOperations`
  * `_spokeTokenOperations`
  * `_assetHubPool`
  * `_loanManager`
* **Asset**: `_asset` (ERC20 address; current script uses USDC)
* **Pool Id**: `_assetPoolId` (vault collateral pool id in TownSq)
* **Timelock**: `initialTimelock` (vault’s configurable timelock param; separate from withdrawal init timelock)
* **Chain / loan identity**:
  * `chainId`
  * `vaultLoanTypeId`
  * `nonce`
  * `loanName`
* **ERC20 share token metadata**:
  * `_name`
  * `_symbol`
* **Messaging params**: `_vaultMessageParams` (`Messages.MessageParams`)

#### Example values (from `script/TownSqVault.s.sol`)

Your script currently uses (chain 143):

* `initialOwner`: `0xa031f11d7CDF039eeF0e73E47Bd5B487f3659B65`
* `USDC`: `0x754704Bc059F8C67012fEd69BC8A327a5aafb603`
* `spokeOperations`: `0x63CB1CF5aCCbCC57e0cCa047bE9673EA5022b8DB`
* `spokeTokenOperations`: `0xA457235B68606a7921b7c525D92e9592e793b4C0`
* `assetHubPool`: `0xdb4E67F878289A820046f46f6304fd6Ee1449281`
* `loanManager`: `0xC4C20EFbEfA4Bde14091a3040d112cF981d8B2DB`
* `vaultAssetPoolId`: `10`
* `vaultLoanTypeId`: `1`
* `nonce`: `0x00000001`
* `loanName`: `"USDC VAULT"`
* `name/symbol`: `"USDC VAULT"` / `"vUSDC"`
* `messageParams`: `{ adapterId: 1, returnAdapterId: 1, receiverValue: 0, gasLimit: 5_000_000, returnGasLimit: 5_000_000 }`

#### Deploying a new proxy (recommended pattern)

The canonical flow is:

1. Deploy `TownSqVault` implementation.
2. Encode `initializerData = abi.encodeCall(TownSqVault.initialize, (...))`.
3. Deploy `TransparentUpgradeableProxy(impl, admin, initializerData)`.

You already do this in tests (`test/TownSqVault.t.sol`).

#### Running the script

There is a working Foundry invocation note at the bottom of `script/TownSqVault.s.sol`:

```bash
forge script --chain 143 script/TownSqVault.s.sol:TownSqVaultScript \
  --rpc-url "$MONAD_RPC_URL" \
  --broadcast --slow -g 300 -vvvv \
  --interactives 1 \
  --sender 0xa031f11d7CDF039eeF0e73E47Bd5B487f3659B65
```

Notes:

* `--broadcast` writes transactions + receipts into `broadcast/...`.
* If you’re deploying a **new** vault proxy, ensure your script uses the proxy-constructor deployment pattern (implementation + `TransparentUpgradeableProxy`) and not only an upgrade call.

***

### Depositing

#### User flow (ERC-4626 deposit)

To deposit `assets` of the underlying token:

1. Approve the vault to spend your asset.
2. Call `deposit(assets, receiver)` on the vault.

In Solidity terms:

* **User** calls `TownSqVault.deposit(assets, receiver)`
* Vault:
  * accrues interest (`_accrueInterest()`)
  * calculates shares using `lastTotalAssets` and current `totalSupply()`
  * transfers the ERC20 `assets` into the vault (ERC-4626 `_deposit`)
  * supplies to TownSq by calling `spokeTokenOperations.topup(...)`
  * updates `lastTotalAssets += assets`

#### Example (cast)

Replace:

* `$VAULT` with the proxy address
* `$ASSET` with the underlying asset (e.g. USDC)
* `$AMOUNT` with the amount in smallest units (USDC has 6 decimals)

```bash
cast send "$ASSET" "approve(address,uint256)" "$VAULT" "$AMOUNT" --rpc-url "$MONAD_RPC_URL" --private-key "$PK"
cast send "$VAULT" "deposit(uint256,address)" "$AMOUNT" "$(cast wallet address --private-key "$PK")" --rpc-url "$MONAD_RPC_URL" --private-key "$PK"
```

#### Shares / decimals note

The vault’s share token uses **18 decimals** (ERC20 default), and computes an internal `decimalOffset` as:

* `decimalOffset = 18 - ERC20(asset).decimals()`

So for USDC (6 decimals), `decimalOffset = 12`. This is why in tests a USDC deposit can mint “18-decimal shares”.

***

### Withdrawing (2-step flow)

Withdrawals are not done via the standard ERC-4626 `withdraw(...)` / `redeem(...)` path in this contract. Instead, the user must use:

1. `initiateWithdraw(shares, receiver, owner)`
2. wait **6 hours**
3. `withdraw(owner)`

#### Step 1 — initiateWithdraw

Call:

* `initiateWithdraw(uint256 shares, address receiver, address owner)`

What it does:

* Reverts if `owner` already has a pending withdrawal (`ErrorsLib.AlreadyInitiated()`).
* Accrues interest.
* Converts `shares → assets` using current totals (rounding up).
* Calls `_withdrawFromTownSq(assets, owner, receiver)`:
  * Ensures requested assets don’t exceed vault’s expected supply.
  * Enforces liquidity via `_withdrawable()` (deposit minus borrows from `assetHubPool`).
  * Stores `initiatedWithdrawal[owner] = { receiver, assets, shares, timeInitiated, true }`
  * Burns the owner’s shares immediately.
  * Updates `lastTotalAssets` down.
  * Calls `spokeOperations.withdraw(...)` to start withdrawing from TownSq.

If TownSq liquidity is insufficient, this step can revert with:

* `ErrorsLib.NotEnoughLiquidity()`

#### Step 2

The finalize call requires a minimum delay:

* `ConstantsLib.WITHDRAW_INITIALIZATION_TIMELOCK = 6 hours`

If you call finalize too early:

* `ErrorsLib.WithdrawTimeLockNotExpired()`

#### Step 3 — withdraw (finalize)

Call:

* `withdraw(address owner)`

What it does:

* Reverts if there is no initiated withdrawal (`ErrorsLib.NoInitiatedWithdrawal()`).
* Reverts if 6 hours hasn’t elapsed.
* Attempts to transfer `assets` to `receiver` via `safeTransferExternal(receiver, assets)`.
  * If the transfer **succeeds**: emits `EventsLib.AssetWithdraw(owner, receiver, assets)` and clears the pending state.
  * If the transfer **fails** (caught by `try/catch`): it **mints back** the burned shares to `receiver` and clears the pending state.

This “mint-back” fallback ensures the user isn’t stuck permanently if the final transfer cannot be executed for some reason.

#### Example (cast)

Assuming you want to withdraw *all shares* you own:

```bash
# shares = vault share-token balance
SHARES=$(cast call "$VAULT" "balanceOf(address)(uint256)" "$(cast wallet address --private-key "$PK")" --rpc-url "$MONAD_RPC_URL")

# initiate withdraw
cast send "$VAULT" "initiateWithdraw(uint256,address,address)" \
  "$SHARES" \
  "$(cast wallet address --private-key "$PK")" \
  "$(cast wallet address --private-key "$PK")" \
  --rpc-url "$MONAD_RPC_URL" --private-key "$PK"

# ... wait 6 hours ...

# finalize
cast send "$VAULT" "withdraw(address)" "$(cast wallet address --private-key "$PK")" \
  --rpc-url "$MONAD_RPC_URL" --private-key "$PK"
```

***

### Operational notes

* **Liquidity matters**: `initiateWithdraw` enforces available liquidity using `assetHubPool` totals. If TownSq has outstanding borrows, withdrawals may revert until liquidity is available.
* **Interest + fees**: the vault accrues interest on interaction (`deposit`, `initiateWithdraw`) via `_accrueInterest()`. If `fee != 0`, it mints fee shares to `feeRecipient`.
* **Timelock variables**:
  * `WITHDRAW_INITIALIZATION_TIMELOCK` is a hard-coded constant (6 hours).
  * `timelock` is a separate vault parameter set in `initialize()` via `_setTimelock(initialTimelock)` and bounded for non-zero values.
