# EVM Zebec Card SDK

An SDK for interacting with Zebec Instant Card EVM contracts.

## Installation

```bash
npm install @zebec-network/evm-card-sdk

yarn add @zebec-network/evm-card-sdk
```

## Supported Chains

| Chain           | Chain ID | Enum value                      |
| --------------- | -------- | ------------------------------- |
| Ethereum        | 1        | `SupportedChain.Mainnet`        |
| Sepolia         | 11155111 | `SupportedChain.Sepolia`        |
| Base            | 8453     | `SupportedChain.Base`           |
| BSC             | 56       | `SupportedChain.Bsc`            |
| BSC Testnet     | 97       | `SupportedChain.BscTestnet`     |
| Odyssey         | 153153   | `SupportedChain.Odyssey`        |
| Odyssey Testnet | 131313   | `SupportedChain.OdysseyTestnet` |
| Polygon         | 137      | `SupportedChain.Polygon`        |
| Polygon Amoy    | 80002    | `SupportedChain.PolygonAmoy`    |

The `SupportedChain` enum and `parseSupportedChain(chainId)` helper are exported for use with chain IDs. `parseSupportedChain` throws if the chain is unsupported — handy for validating user input before constructing the service.

```ts
import { parseSupportedChain, ODYSSEY_CHAIN_IDS } from "@zebec-network/evm-card-sdk";

const chain = parseSupportedChain(11155111); // SupportedChain.Sepolia
const isOdyssey = ODYSSEY_CHAIN_IDS.includes(chain);
```

---

## Quick Start

```ts
import { ZebecCardService, SupportedChain } from "@zebec-network/evm-card-sdk";
import { ethers } from "ethers";

const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();

const service = new ZebecCardService(signer, SupportedChain.Sepolia);
```

> **Odyssey chains** (`OdysseyTestnet`, `Odyssey`) use a different contract (`OdysseyZebecCard`) and support a different set of methods. The service throws `Method not supported for this chain` when you call a method that is not available on the current chain. See chain-specific notes in each method below.

### Two ways to buy a card

The SDK supports two purchase flows:

1. **Direct purchase** (`buyCardDirect`) — single transaction; USDC (or any supported token via `swapAndBuyCardDirect` / `swapAndBuyCardOdyssey`) is pulled from the user's wallet at purchase time. Available on **all chains**.
2. **Vault-based purchase** (`depositUsdc` → `buyCard`) — user first tops up an on-contract USDC vault, then buys cards from that balance. `withdraw` returns unused vault balance. **Non-Odyssey chains only.**

### Gas overrides

Every write method accepts an optional `overrides?: ethers.Overrides`. The SDK applies `DEFAULT_GAS_LIMIT = 3_000_000` when `overrides.gasLimit` is not provided.

---

## API Reference

### `ZebecCardService`

#### Constructor

```ts
new ZebecCardService(signer: ethers.Signer, chainId: number)
```

| Parameter | Type            | Description                          |
| --------- | --------------- | ------------------------------------ |
| `signer`  | `ethers.Signer` | Ethers signer from your wallet       |
| `chainId` | `number`        | One of the supported chain IDs above |

**Public properties:**

| Property    | Type                            | Description                        |
| ----------- | ------------------------------- | ---------------------------------- |
| `zebecCard` | `ZebecCard \| OdysseyZebecCard` | Main Zebec Card contract           |
| `usdcToken` | `Token`                         | USDC ERC-20 contract               |
| `weth`      | `Weth`                          | WETH contract                      |
| `signer`    | `ethers.Signer`                 | Signer passed to the constructor   |
| `chainId`   | `number`                        | Chain ID passed to the constructor |

#### Method availability

| Method                                  | Non-Odyssey | Odyssey | Notes                                    |
| --------------------------------------- | :---------: | :-----: | ---------------------------------------- |
| `approve` / `wrapEth`                   |     ✅      |   ✅    | Token utilities                          |
| `depositUsdc`                           |     ✅      |   ❌    | Vault top-up                             |
| `withdraw`                              |     ✅      |   ❌    | Vault withdrawal                         |
| `buyCard`                               |     ✅      |   ❌    | From vault balance                       |
| `buyCardDirect`                         |     ✅      |   ✅    | From wallet (no vault)                   |
| `swapAndDeposit`                        |     ✅      |   ❌    | 1inch swap → vault                       |
| `swapAndBuyCardDirect`                  |     ✅      |   ❌    | 1inch swap → card                        |
| `swapAndBuyCardOdyssey`                 |     ❌      |   ✅    | Native ETH swap → card                   |
| `setReloadableFee` / `getReloadableFee` |     ✅      |   ❌    | Carbon (reloadable) card fee             |
| `setFee`                                |     ❌      |   ✅    | Per-tier fee on Odyssey                  |
| `getMinimumUsdcAmount`                  |     ❌      |   ✅    | Computes min USDC for a given ETH amount |
| All other admin/query                   |     ✅      |   ✅    |                                          |

---

### Token Utilities

#### `approve`

Approves a token spender. Only submits a transaction if the current allowance is less than the requested amount. Returns `null` if no approval is needed.

```ts
const tx = await service.approve({
	token: tokenAddress, // ERC-20 token address
	spender: spenderAddress,
	amount: "1000", // Human-readable amount (e.g. USDC units)
});

if (tx) {
	const receipt = await tx.wait();
	console.log("approval hash:", receipt?.hash);
}
```

#### `wrapEth`

Wraps native ETH into WETH.

```ts
const tx = await service.wrapEth({ amount: "0.001" });
const receipt = await tx.wait();
console.log("txHash:", receipt?.hash);
```

---

### Vault Flow (Non-Odyssey only)

`ZebecCard` keeps a per-user USDC balance ("card vault") on-contract. Top it up with `depositUsdc`, buy cards from it with `buyCard`, and withdraw any leftover with `withdraw`. None of these methods are available on Odyssey chains — they throw `Method not supported for this chain`.

#### `depositUsdc`

Deposits USDC from the user's wallet into their card vault. Requires the `ZebecCard` contract to be approved as an USDC spender first.

```ts
const token = await service.usdcToken.getAddress();
const spender = await service.zebecCard.getAddress();
const amount = "1000";

const approval = await service.approve({ token, spender, amount });
if (approval) await approval.wait();

const tx = await service.depositUsdc({ amount });
await tx.wait();
```

#### `withdraw`

Withdraws USDC from the user's card vault back to their wallet.

```ts
const tx = await service.withdraw({ amount: "5.0" });
await tx.wait();
```

#### `buyCard`

Buys a card by debiting the user's vault balance. The SDK validates locally before submitting:

- email format (via the `isEmailValid` helper)
- vault balance ≥ requested amount
- amount within `minCardAmount` / `maxCardAmount` from `cardConfig`
- daily purchase total ≤ `dailyCardBuyLimit`

```ts
const tx = await service.buyCard({
	amount: "199",
	cardType: "silver",
	buyerEmail: "user@example.com",
});
await tx.wait();
```

#### `swapAndDeposit`

Swaps a source token to USDC via the 1inch aggregator and deposits the result into the user's vault in a single transaction. Use [`fetchSwapData`](#fetching-swap-quote) below to obtain `swapData`. The source token must be approved both for the 1inch router (so the aggregator can pull it) and for the `ZebecCard` contract.

```ts
const tx = await service.swapAndDeposit({ swapData });
await tx.wait();
```

---

### Card Purchase

#### `buyCardDirect`

Buys a card in a single transaction — USDC is pulled directly from the user's wallet (no prior vault deposit needed). Requires approval of the `ZebecCard` contract to spend USDC. Works on **all supported chains**.

The SDK validates email format, amount range, and daily purchase limit before submitting.

```ts
const token = await service.usdcToken.getAddress();
const spender = await service.zebecCard.getAddress();
const amount = "199";

const approval = await service.approve({ token, spender, amount });
if (approval) {
	await approval.wait();
}

const tx = await service.buyCardDirect({
	amount,
	cardType: "carbon",
	buyerEmail: "user@example.com",
});
const receipt = await tx.wait();
console.log("txhash:", receipt?.hash);
```

Card type mapping (handled internally — pass `"silver"` or `"carbon"`):

| `cardType` value | Contract value     |
| ---------------- | ------------------ |
| `"silver"`       | `"non_reloadable"` |
| `"carbon"`       | `"reloadable"`     |

---

### Swap & Buy

These methods allow users to pay with tokens other than USDC. The contract handles the swap to USDC internally.

> **Non-Odyssey chains** use the 1inch aggregator. **Odyssey chains** use a native ETH swap path.

#### Fetching Swap Quote

Fetch swap data from the Zebec backend before calling swap methods:

```ts
const urlParams = new URLSearchParams({
	src, // source token address
	dst, // destination token address (USDC)
	from, // user wallet address
	origin, // user wallet address
	amount, // amount in source token smallest unit
	slippage: "5",
	compatibility: "true",
	chainId: chainId.toString(),
	receiver, // ZebecCard contract address
	disableEstimate: "true",
});

const url = `https://api.card.zebec.io/swap/get1inchswapquotes?${urlParams}`;
const swapData = await fetch(url, {
	headers: { Accept: "application/json", "Content-Type": "application/json; charset=utf-8" },
}).then((r) => r.json());
```

#### `swapAndBuyCardDirect`

Swaps a source token to USDC and buys a card in one transaction. **Non-Odyssey chains only.**

Requires approval of the `ZebecCard` contract to spend the source token.

```ts
const approval = await service.approve({
	token: srcTokenAddress,
	spender: await service.zebecCard.getAddress(),
	amount: srcAmount,
});
if (approval) await approval.wait();

const tx = await service.swapAndBuyCardDirect({
	swapData,
	cardType: "carbon",
	buyerEmail: "user@example.com",
});
const receipt = await tx.wait();
console.log("txhash:", receipt?.hash);
```

#### `swapAndBuyCardOdyssey`

Swaps native ETH to USDC and buys a card in one transaction. **Odyssey chains only.**

```ts
const tx = await service.swapAndBuyCardOdyssey({
	cardType: "silver",
	buyerEmail: "user@example.com",
	ether: "1265", // Amount of native ETH (in smallest unit)
	slippage: 1, // Slippage tolerance in percent
});
const receipt = await tx.wait();
console.log("txhash:", receipt?.hash);
```

---

### Query Methods

#### `getUserBalance`

Returns the user's USDC vault balance as a human-readable string.

```ts
const balance = await service.getUserBalance({ userAddress: signerAddress });
console.log("balance:", balance);
```

#### `getCardPurhcaseOfDay`

Returns the user's card purchase info for the current day.

```ts
const purchase = await service.getCardPurhcaseOfDay({ userAddress: signerAddress });
console.log("total purchased today:", purchase.totalCardPurchased);
console.log("timestamp:", purchase.cardPurchasedTimestamp);
```

Returns a `CardPurchaseOfDay` object:

```ts
{
	totalCardPurchased: string; // Total USDC value purchased today
	cardPurchasedTimestamp: number; // Unix timestamp of last purchase
}
```

#### `getCardConfig`

Returns the current contract configuration.

```ts
const config = await service.getCardConfig();
console.log(config);
```

Returns a `CardConfig` object:

```ts
{
	nativeFeePercent: string;
	nonNativeFeePercent: string;
	revenueFeePercent: string;
	totalCardSold: bigint;
	cardVault: string;
	revenueVault: string;
	commissionVault: string;
	usdcAddress: string;
	minCardAmount: string;
	maxCardAmount: string;
	dailyCardPurchaseLimit: string;
}
```

#### `getFeeTiers`

Returns configured fee tiers.

```ts
const tiers = await service.getFeeTiers();
// [{ feePercent: "1.5", minAmount: "0", maxAmount: "500" }, ...]
```

#### `getAdmin`

Returns the admin (owner) address of the contract.

```ts
const admin = await service.getAdmin();
console.log("admin:", admin);
```

#### `getCustomFee`

Returns the custom fee configured for a specific token, as a percentage string.

```ts
const fee = await service.getCustomFee({ tokenAddress: "0x..." });
console.log("fee:", fee); // e.g. "2.5"
```

#### `getReloadableFee`

Returns the reloadable (carbon) card fee as a percentage string. **Non-Odyssey chains only.**

```ts
const fee = await service.getReloadableFee();
console.log("reloadable fee:", fee);
```

#### `getMinimumUsdcAmount`

Returns the minimum USDC amount for a given ETH amount with slippage applied. **Odyssey chains only.**

```ts
const minUsdc = await service.getMinimumUsdcAmount("1265", 1);
console.log("min USDC:", minUsdc);
```

---

### Admin Methods

Admin methods can only be called by the contract owner. Use a `ZebecCardService` instance created with the admin signer.

#### `setNativeFee`

```ts
await (await service.setNativeFee({ feeInPercent: "1.5" })).wait();
```

#### `setNonNativeFee`

```ts
await (await service.setNonNativeFee({ feeInPercent: "2.5" })).wait();
```

#### `setRevenueFee`

```ts
await (await service.setRevenueFee({ feeInPercent: "5.0" })).wait();
```

#### `setRevenueVault`

```ts
await (await service.setRevenueVault({ vaultAddress: "0x..." })).wait();
```

#### `setCommissionVault`

```ts
await (await service.setCommissionVault({ vaultAddress: "0x..." })).wait();
```

#### `setCardVault`

```ts
await (await service.setCardVault({ vaultAddress: "0x..." })).wait();
```

#### `setUsdcAddress`

```ts
await (await service.setUsdcAddress({ tokenAddress: "0x..." })).wait();
```

#### `setMinCardAmount`

```ts
await (await service.setMinCardAmount({ minCardAmount: "10" })).wait();
```

#### `setMaxCardAmount`

```ts
await (await service.setMaxCardAmount({ maxCardAmount: "1000" })).wait();
```

#### `setDailyCardPurchaseLimit`

```ts
await (await service.setDailyCardPurchaseLimit({ dailyCardPurchaseLimit: "5000" })).wait();
```

#### `setFee` (Odyssey chains only)

Updates the fee for a given amount range, or inserts a new tier if the range doesn't exist.

```ts
await (await service.setFee({ minAmount: "0", maxAmount: "500", feePercent: "1.5" })).wait();
```

#### `setFeeTiers`

Replaces all fee tiers.

```ts
await (
	await service.setFeeTiers({
		feeTiers: [
			{ feePercent: "1.0", minAmount: "0", maxAmount: "200" },
			{ feePercent: "1.5", minAmount: "200", maxAmount: "500" },
			{ feePercent: "2.0", minAmount: "500", maxAmount: "9999" },
		],
	})
).wait();
```

#### `setCustomFee`

Sets a custom fee percentage for a specific token.

```ts
await (await service.setCustomFee({ tokenAddress: "0x...", fee: "3.0" })).wait();
```

#### `setReloadableFee` (Non-Odyssey chains only)

Sets the fee for reloadable (carbon) cards.

```ts
await (await service.setReloadableFee({ fee: "1.0" })).wait();
```

---

## Using Contract Factories

The SDK exports Typechain-generated factory classes for creating contract instances directly.

```ts
import { Token__factory, ZebecCard__factory } from "@zebec-network/evm-card-sdk";
import { ethers } from "ethers";

// Create an ERC-20 token contract instance
const token = Token__factory.connect(tokenAddress, signer);
const balance = await token.balanceOf(walletAddress);

// Create a ZebecCard interface for parsing logs
function parseLogs(logs: readonly ethers.Log[]) {
	const iface = ZebecCard__factory.createInterface();
	return logs.map((l) => iface.parseLog(l)).filter(Boolean) as ethers.LogDescription[];
}
```

---

## Parsing Contract Events

Use the ZebecCard interface to decode transaction receipt logs.

```ts
import { ethers } from "ethers";
import { ZebecCard__factory } from "@zebec-network/evm-card-sdk";

const provider = new ethers.JsonRpcProvider(rpcUrl);

function parseLogs(logs: readonly ethers.Log[]) {
	const iface = ZebecCard__factory.createInterface();
	return logs.map((l) => iface.parseLog(l)).filter(Boolean) as ethers.LogDescription[];
}

// Deposited event
const receipt = await provider.getTransactionReceipt(txHash);
const events = parseLogs(receipt!.logs);
const deposited = events.find((e) => e.name === "Deposited");
deposited?.args.forEach((arg, i) => console.log(`arg ${i}:`, arg));

// Withdrawn event
const withdrawn = events.find((e) => e.name === "Withdrawn");

// CardPurchased event
const cardPurchased = events.find((e) => e.name === "CardPurchased");

// Swapped event
const swapped = events.find((e) => e.name === "Swapped");
```

---

## Exported Constants & Helpers

```ts
import {
	SupportedChain,
	ODYSSEY_CHAIN_IDS,
	ZEBEC_CARD_ADDRESS,
	USDC_ADDRESS,
	WETH_ADDRESS,
	ATOKEN_ADDRESS,
	DEFAULT_GAS_LIMIT,
	parseSupportedChain,
} from "@zebec-network/evm-card-sdk";

// Get ZebecCard contract address for a chain
const contractAddress = ZEBEC_CARD_ADDRESS[SupportedChain.Sepolia];

// Parse a raw chain ID to SupportedChain enum (throws if unsupported)
const chain = parseSupportedChain(11155111); // SupportedChain.Sepolia

// Check if a chain is an Odyssey chain
const isOdyssey = ODYSSEY_CHAIN_IDS.includes(chainId);
```

---

## Exported ABIs

Raw ABIs are available for use with other libraries:

```ts
import {
	ZEBEC_CARD_ABI,
	ERC20_TOKEN_ABI,
	WETH_ABI,
	AGGREGATOR_ROUTER_V6_ABI,
} from "@zebec-network/evm-card-sdk";
```

---

## TypeScript Types

```ts
import type {
	CardType,
	CardConfig,
	FeeTier,
	CardPurchaseOfDay,
	SwapData,
	SwapAndBuyCardParams,
	SwapAndBuyCardParamsOdyssey,
} from "@zebec-network/evm-card-sdk";
```

| Type                          | Description                                      |
| ----------------------------- | ------------------------------------------------ |
| `CardType`                    | `"silver" \| "carbon"`                           |
| `CardConfig`                  | Full contract configuration object               |
| `FeeTier`                     | `{ feePercent, minAmount, maxAmount }`           |
| `CardPurchaseOfDay`           | `{ totalCardPurchased, cardPurchasedTimestamp }` |
| `SwapData`                    | Swap quote data from the Zebec backend           |
| `SwapAndBuyCardParams`        | Parameters for `swapAndBuyCardDirect`            |
| `SwapAndBuyCardParamsOdyssey` | Parameters for `swapAndBuyCardOdyssey`           |
