# API ยอดคงเหลือโทเค็นของกระเป๋าเงิน: สินทรัพย์ ERC-20 และประวัติการโอน

> Source: https://docs.blockvectra.com/th/guides/wallet-assets/

สร้างหน้าสินทรัพย์ของกระเป๋าเงินด้วย API ข้อมูลกระเป๋าเงินบล็อกเชน: ใช้ [Token Balances API](https://blockvectra.com/en/data/balances/) สำหรับการถือครอง ERC-20 ที่ไม่เป็นศูนย์ และใช้ [Token Transfers API](https://blockvectra.com/en/data/transfers/) สำหรับประวัติของกระเป๋าเงิน ทั้งนักพัฒนาและ AI agent ต่างใช้คำขอที่ผ่านการยืนยันตัวตนแบบเดียวกัน ก่อนสืบค้น ให้อ่าน [GET /v1/status](https://api.blockvectra.com/v1/data) และตรวจสอบ `data_features` และ `data_status` ของเชนที่เลือก; ความครอบคลุมของยอดคงเหลือจะแตกต่างกันไปตามแต่ละเชน ดูพารามิเตอร์คำขอและสคีมาการตอบกลับได้ใน [เอกสารอ้างอิง Data API](https://docs.blockvectra.com/en/api/data/)

## งานที่คู่มือนี้จะช่วยคุณดำเนินการ

* [อ่านยอดคงเหลือโทเค็นของกระเป๋าเงิน](#request-1-address-balances) ด้วยคีย์และแบ่งหน้าการถือครอง ERC-20 ที่ไม่เป็นศูนย์
* [อ่านประวัติการโอนของกระเป๋าเงิน](#request-2-address-transfers) ภายในหน้าต่างบล็อกที่กำหนด และติดตามเคอร์เซอร์สำหรับแอดเดรสที่เลือก
* [เติมข้อมูลเมทาดาตาของโทเค็น](#request-3-token-metadata-and-tokensbatch) เพื่อแสดงชื่อและสัญลักษณ์ควบคู่ไปกับยอดคงเหลือจำนวนเต็มดิบ โดยรักษาฟิลด์ที่ขาดหายไว้

## ข้อมูลสามประเภทที่หน้าสินทรัพย์ของกระเป๋าเงินต้องการ

หน้าสินทรัพย์ของกระเป๋าเงินสามารถแสดงยอดคงเหลือโทเค็น ERC-20, ประวัติการโอนโทเค็น และเมทาดาตาของโทเค็นของแอดเดรสได้ โดย Data API มี endpoint สำหรับแต่ละรายการ:

* **ยอดคงเหลือ**: `GET /{chain}/addresses/{address}/balances` ส่งคืนยอดคงเหลือ ERC-20 ที่ไม่เป็นศูนย์ของแอดเดรส เรียงลำดับตามแอดเดรส `token` จากน้อยไปมาก พร้อมทั้งระบุ `symbol` และ `decimals` ของโทเค็นหากมีข้อมูล แอดเดรสที่ไม่มีการถือครองโทเค็นจะส่งคืน `200` พร้อม `data: []`
* **การโอน**: `GET /{chain}/addresses/{address}/transfers` ส่งคืนการโอนโทเค็นที่เกี่ยวข้องกับแอดเดรสภายในหน้าต่างบล็อกที่จำเป็น เรียงลำดับตาม `(block_number, log_index)` จากมากไปน้อย
* **เมทาดาตาของโทเค็น**: `GET /{chain}/tokens/{token}` อ่านชื่อ, สัญลักษณ์, decimals และอุปทานรวมของหนึ่งโทเค็นตามแอดเดรสสัญญา; `POST /{chain}/tokens:batch` อ่านเมทาดาตาเดียวกันนี้สำหรับสูงสุด 100 แอดเดรสในคำขอเดียว

ทั้งสามรายการใช้ `https://api.blockvectra.com/v1/data` เป็น base URL และใช้ส่วนหัวคำขอ `x-api-key` โดยใช้ `robinhood_mainnet` เป็นเชนตัวอย่าง ทั้งหมดอยู่ภายใต้ความสามารถ `balances`, `transfers` และ `token_metadata` ตามลำดับ; สำหรับเชนที่ให้บริการแต่ละความสามารถ โปรดดูหน้า [เชนที่รองรับ](https://docs.blockvectra.com/en/chains/) บนเชนที่ไม่มีความสามารถดังกล่าว endpoint จะส่งคืน `422 no_coverage`

## คำขอที่ 1: ยอดคงเหลือของแอดเดรส

endpoint นี้รับพารามิเตอร์น้อยกว่า จึงเหมาะเป็นคำขอแรกสำหรับหน้าสินทรัพย์:

* `{chain}` (พารามิเตอร์พาธ, จำเป็น): ตัวระบุเชน ซึ่งเป็นค่า `chain` ของรายการใน `GET /chains` (ตัวอย่างเช่น `robinhood_mainnet`) การจับคู่จะตรงกันทุกตัวอักษรและคำนึงถึงตัวพิมพ์ใหญ่-เล็ก; ไม่ยอมรับนามแฝงหรือ chain ID แบบตัวเลข
* `{address}` (พารามิเตอร์พาธ, จำเป็น): แอดเดรสขนาด 20 ไบต์; คำนำหน้า `0x` เป็นตัวเลือก และยอมรับตัวพิมพ์ใหญ่หรือเล็กก็ได้
* `limit` (พารามิเตอร์แบบสอบถาม, ไม่บังคับ): ขนาดหน้า ค่าเริ่มต้นคือ 50; ค่าที่มากกว่า 500 จะถูกปรับลดลงเหลือ 500; การส่งค่า `0` หรือไม่ใช่จำนวนเต็มจะส่งคืน `400 bad_request`
* `cursor` (พารามิเตอร์แบบสอบถาม, ไม่บังคับ): `next_cursor` ของการตอบกลับครั้งก่อนหน้า ซึ่งส่งกลับมาโดยไม่เปลี่ยนแปลงเพื่อดึงข้อมูลหน้าถัดไป โดยเคอร์เซอร์จะใช้ได้เฉพาะกับเชน, endpoint และพารามิเตอร์แบบสอบถามที่ออกเคอร์เซอร์นั้นเท่านั้น; การนำไปใช้ซ้ำที่อื่นจะส่งคืน `400 bad_request`

การแบ่งหน้าเป็นแบบ keyset: `next_cursor` จะปรากฏเฉพาะเมื่อมีหน้าถัดไป ในหน้าสุดท้ายคีย์นี้จะไม่มีอยู่เลย โดยไม่เคยเป็น `null`

**cURL**

```bash
export BLOCKVECTRA_API_KEY=rgw_your_api_key

curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/balances?limit=50" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"
```


  **TypeScript**

```ts
const address = "0x1111111111111111111111111111111111111111";
const url = new URL(
  `https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/${address}/balances`,
);
url.searchParams.set("limit", "50");

const res = await fetch(url, {
  headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
});
const balanceBody = await res.json();
console.log(balanceBody.data, balanceBody.meta);

// npx tsx example.mts
```


  **Python**

```python
import os
import requests

address = "0x1111111111111111111111111111111111111111"
res = requests.get(
    f"https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/{address}/balances",
    params={"limit": 50},
    headers={"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]},
)
res.raise_for_status()
balance_body = res.json()
print(balance_body["data"], balance_body["meta"])
```


โครงสร้างการตอบกลับคือ `AddressBalanceListEnvelope` ซึ่งประกอบด้วย `data` และ `meta` โดยแต่ละรายการใน `data` คือ `AddressBalance`:

| ฟิลด์      | ชนิด                  | คำอธิบาย                                                                                                                          |
| ---------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `token`    | `string` (address)    | แอดเดรสสัญญาโทเค็น; รูปแบบมาตรฐานคือ `0x` ตามด้วยเลขฐานสิบหกตัวพิมพ์เล็ก 40 หลัก                                                  |
| `balance`  | `string` (decimal)    | ยอดคงเหลือจำนวนเต็มดิบ ซึ่งอาจเกิน `2^53` โดยส่งคืนเป็นสตริงฐานสิบธรรมดา — ไม่เป็นตัวเลข JSON, สัญกรณ์วิทยาศาสตร์ หรือเลขฐานสิบหก |
| `symbol`   | `string` หรือ `null`  | สัญลักษณ์โทเค็น หรือ `null` เมื่อไม่มีข้อมูล                                                                                      |
| `decimals` | `integer` หรือ `null` | ทศนิยมของโทเค็น `0`–`255` หรือ `null` เมื่อไม่มีข้อมูล                                                                            |

## คำขอที่ 2: การโอนของแอดเดรส

endpoint สำหรับการโอนจำเป็นต้องระบุหน้าต่างบล็อกอย่างชัดเจน: ทั้ง `from_block` และ `to_block` จำเป็นต้องระบุ และต้องเป็นไปตามเงื่อนไข `from_block <= to_block` โดยรับพารามิเตอร์เพิ่มเติมอีกสองสามรายการ:

* `standard` (พารามิเตอร์แบบสอบถาม, จำเป็น): `erc20` หรือ `erc721` คำค้นหาในขอบเขตแอดเดรสไม่ครอบคลุม `erc1155`; การส่งค่าดังกล่าวจะส่งคืน `422 no_coverage`
* `direction` (พารามิเตอร์แบบสอบถาม, ไม่บังคับ): `in`, `out` หรือ `any`; ค่าเริ่มต้นคือ `any` และกรองตามทิศทางที่สัมพันธ์กับแอดเดรส
* `token` (พารามิเตอร์แบบสอบถาม, ไม่บังคับ): จำกัดผลลัพธ์ให้อยู่ในสัญญาโทเค็นเดียว
* `clamp` (พารามิเตอร์แบบสอบถาม, ไม่บังคับ): มีเพียงสตริง `true` ตรงตัวเท่านั้นที่จะเปิดใช้งาน; ค่าอื่นๆ ทั้งหมดจะถือว่าเป็น `false`

ขอบเขตหน้าต่างและสถานะสิ้นสุด: ค่า `to_block` ที่ชัดเจนซึ่งสูงกว่า `as_of_block` จะส่งคืน `409 not_indexed_yet` เว้นแต่ว่า `clamp=true` จะตัดค่าลงมาอยู่ที่ `as_of_block`; หน้าต่างที่กว้างกว่าขีดจำกัดของเชน (`limits.max_window_blocks` จาก `GET /chains`) จะส่งคืน `409 window_too_large` เว้นแต่ว่า `clamp=true` จะตัดทอนจากฝั่งที่เก่ากว่า (เพิ่มค่า `from_block` และคงค่า `to_block` ไว้) หาก `from_block` เองเกิน `as_of_block` ไปแล้ว จะยังคงส่งคืน `409` อย่างเข้มงวดแม้จะใช้ `clamp=true` ก็ตาม เมื่อหน้าต่างถูก clamp หรือครอบคลุมเพียงบางส่วน ค่า `meta.coverage` ในการตอบกลับจะเป็น `"partial"`; มิฉะนั้นจะเป็น `"full"`

ในระเบียนการโอน รายการ ERC-20 จะเพิ่ม `amount`; รายการ ERC-721 จะเพิ่ม `token_id` โดยทั้งสองจะประกอบด้วย `token`, `standard`, `from`, `to`, `block_number`, `block_timestamp`, `tx_hash`, `tx_index` และ `log_index`

**cURL**

```bash
export BLOCKVECTRA_API_KEY=rgw_your_api_key

# 1) Read as_of_block from the balances response.
AS_OF_BLOCK=$(curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/balances" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" | grep -o '"as_of_block":[0-9]*' | cut -d: -f2)

# 2) clamp=true truncates a too-wide window, or a to_block above as_of_block, instead of returning 409.
curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/transfers?standard=erc20&from_block=0&to_block=$AS_OF_BLOCK&direction=any&clamp=true" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"
```


  **TypeScript**

```ts
const address = "0x1111111111111111111111111111111111111111";

// 1) Read as_of_block from any previous response's meta.
const head = await fetch(
  `https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/${address}/balances`,
  { headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! } },
).then((r) => r.json());

// 2) Use as_of_block as the transfer window's upper bound.
const url = new URL(
  `https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/${address}/transfers`,
);
url.searchParams.set("standard", "erc20");
url.searchParams.set("from_block", "0");
url.searchParams.set("to_block", String(head.meta.as_of_block));
url.searchParams.set("direction", "any");
url.searchParams.set("clamp", "true");

const res = await fetch(url, {
  headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
});
const body = await res.json();
console.log(body.data, body.meta);

// npx tsx example.mts
```


  **Python**

```python
import os
import requests

address = "0x1111111111111111111111111111111111111111"
headers = {"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]}

# 1) Read as_of_block from any previous response's meta.
head = requests.get(
    f"https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/{address}/balances",
    headers=headers,
).json()

# 2) Use as_of_block as the transfer window's upper bound.
res = requests.get(
    f"https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/{address}/transfers",
    params={
        "standard": "erc20",
        "from_block": 0,
        "to_block": head["meta"]["as_of_block"],
        "direction": "any",
        "clamp": "true",
    },
    headers=headers,
)
res.raise_for_status()
body = res.json()
print(body["data"], body["meta"])
```


## การแบ่งหน้าเพื่อดึงข้อมูลการโอนทั้งหมด

`next_cursor` ของ endpoint การโอนของแอดเดรสเป็นแบบคาดการณ์ในแง่ดี: ค่านี้จะปรากฏเฉพาะเมื่อหน้านั้นส่งคืนจำนวนแถวเท่ากับ `limit` พอดี ดังนั้นหน้าที่มี `next_cursor` อาจกลายเป็นหน้าสุดท้ายได้ อย่าหยุดทำงานเมื่อพบหน้าที่ว่างเปล่า; ให้ติดตาม `next_cursor` ไปเรื่อยๆ จนกระทั่งไม่มีคีย์นี้ปรากฏ

โค้ดด้านล่างนี้จะดึงข้อมูลการโอนทุกรายการในหน้าต่างบล็อก:

**TypeScript**

```ts
const address = "0x1111111111111111111111111111111111111111";
const head = await fetch(
  `https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/${address}/balances`,
  { headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! } },
).then((r) => r.json());
const asOfBlock = head.meta.as_of_block;
const transfers: unknown[] = [];
let cursor: string | undefined;

do {
  const url = new URL(
    `https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/${address}/transfers`,
  );
  url.searchParams.set("standard", "erc20");
  url.searchParams.set("from_block", "0");
  url.searchParams.set("to_block", String(asOfBlock));
  url.searchParams.set("limit", "500");
  // clamp truncates from the older end
  url.searchParams.set("clamp", "true");
  if (cursor) url.searchParams.set("cursor", cursor);

  const res = await fetch(url, {
    headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
  });
  const page = await res.json();
  transfers.push(...page.data);
  cursor = page.next_cursor; // absent on the last page
} while (cursor);
```


  **Python**

```python
import os
import requests

address = "0x1111111111111111111111111111111111111111"
head = requests.get(
    f"https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/{address}/balances",
    headers={"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]},
).json()
as_of_block = head["meta"]["as_of_block"]
transfers = []
cursor = None

while True:
    params = {
        "standard": "erc20",
        "from_block": 0,
        "to_block": as_of_block,
        "limit": 500,
        # clamp truncates from the older end
        "clamp": "true",
    }
    if cursor:
        params["cursor"] = cursor
    res = requests.get(
        f"https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/{address}/transfers",
        params=params,
        headers={"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]},
    )
    res.raise_for_status()
    page = res.json()
    transfers.extend(page["data"])
    cursor = page.get("next_cursor")  # absent on the last page
    if not cursor:
        break
```


## คำขอที่ 3: เมทาดาตาของโทเค็นและ tokens:batch

อ่านโทเค็นเดี่ยวด้วย `GET /{chain}/tokens/{token}`; พาธนี้รับเฉพาะ `{chain}` และ `{token}` โดยไม่มีการแบ่งหน้า โครงสร้างการตอบกลับคือ `TokenEnvelope` และ `data` คือ `Token`:

| ฟิลด์                 | ชนิด                  | คำอธิบาย                                                                                                                                                            |
| --------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address`             | `string` (address)    | แอดเดรสสัญญาโทเค็น                                                                                                                                                  |
| `standard`            | `string`              | `erc20`, `erc721` หรือ `unknown`                                                                                                                                    |
| `name`                | `string` หรือ `null`  | ชื่อโทเค็น หรือ `null` เมื่อไม่มีข้อมูล                                                                                                                             |
| `symbol`              | `string` หรือ `null`  | สัญลักษณ์โทเค็น หรือ `null` เมื่อไม่มีข้อมูล                                                                                                                        |
| `decimals`            | `integer` หรือ `null` | ทศนิยมของโทเค็น `0`–`255` หรือ `null` เมื่อไม่มีข้อมูล                                                                                                              |
| `total_supply`        | `string` หรือ `null`  | อุปทานรวมดิบ; API ไม่ได้ใช้การปรับสเกล `decimals` มีค่าเป็น `null` เมื่อไม่มีข้อมูล                                                                                 |
| `first_seen_block`    | `integer` (int64)     | ความสูงของบล็อกที่พบโทเค็นนี้เป็นครั้งแรก                                                                                                                           |
| `metadata_updated_at` | `string` (timestamp)  | เวลา UTC เมื่อเมทาดาตาได้รับการอัปเดตล่าสุด                                                                                                                         |
| `metadata_block`      | `integer` (int64)     | ความสูงของบล็อกที่มีการอ่านเมทาดาตา                                                                                                                                 |
| `metadata_status`     | `string`              | `ok`, `partial` หรือ `unavailable`                                                                                                                                  |
| `metadata_issues`     | `object`              | บันทึกปัญหาแยกตามฟิลด์ โดยมีคีย์เป็น `name`, `symbol`, `decimals`, `total_supply` พร้อมค่า `reverted`, `no_data`, `invalid_encoding` หรือ `temporarily_unavailable` |

`{token}` ที่ไม่ใช่แอดเดรสขนาด 20 ไบต์ที่ถูกต้องจะส่งคืน `400 bad_request`; `{token}` ที่ไม่รู้จักจะส่งคืน `404 not_found`; `{chain}` ที่ไม่รู้จักจะส่งคืน `404 unknown_chain`

endpoint ยอดคงเหลือมี `symbol` และ `decimals` รวมอยู่ด้วยแล้วในกรณีที่มีข้อมูล แต่ทั้งสองค่าอาจเป็น `null` ได้ หากต้องการเติมชื่อและ decimals สำหรับทุกโทเค็นในกระเป๋าเงิน ให้ใช้ `POST /{chain}/tokens:batch`:

* เนื้อหาคำขอคือ `{"addresses": [...]}` โดยมีแอดเดรสได้สูงสุด 100 แอดเดรสต่อหนึ่งคำขอ; รายการที่มากกว่า 100 รายการ หรือรายการที่ไม่ใช่แอดเดรสขนาด 20 ไบต์ที่ถูกต้อง จะส่งคืน `400 bad_request` (จะล้มเหลวทันทีที่พบแอดเดรสแรกที่ไม่ถูกต้อง)
* แอดเดรสที่ไม่พบจะไม่ทำให้เกิดข้อผิดพลาด; แอดเดรสเหล่านั้นจะแสดงอยู่ใน `data.missing` ขณะที่ `data.tokens` จะมีเฉพาะโทเค็นที่พบเมทาดาตาเท่านั้น
* แอดเดรสที่ซ้ำกันจะถูกขจัดรายการซ้ำออกทั้งใน `tokens` และ `missing` โดยคงลำดับการปรากฏครั้งแรกในคำขอไว้

**cURL**

```bash
export BLOCKVECTRA_API_KEY=rgw_your_api_key

# Single token
curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens/0x1111111111111111111111111111111111111111" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"

# Batch: up to 100 addresses per request
curl -s -X POST "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens:batch" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"addresses":["0x1111111111111111111111111111111111111111","0x2222222222222222222222222222222222222222"]}'
```


  **TypeScript**

```ts
// Single token
const single = await fetch(
  "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens/0x1111111111111111111111111111111111111111",
  { headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! } },
).then((r) => r.json());
console.log(single.data);

// Batch: group by 100 addresses to enrich the tokens from the balances response
const BATCH_SIZE = 100;
const addresses = balanceBody.data.map((item: { token: string }) => item.token);
const tokens = new Map<string, unknown>();
const missing: string[] = [];

for (let i = 0; i < addresses.length; i += BATCH_SIZE) {
  const res = await fetch("https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens:batch", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": process.env.BLOCKVECTRA_API_KEY!,
    },
    body: JSON.stringify({ addresses: addresses.slice(i, i + BATCH_SIZE) }),
  });
  const body = await res.json();
  for (const token of body.data.tokens) tokens.set(token.address, token);
  missing.push(...body.data.missing);
}

// npx tsx example.mts
```


  **Python**

```python
import os
import requests

headers = {"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]}

# Single token
single = requests.get(
    "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens/0x1111111111111111111111111111111111111111",
    headers=headers,
).json()
print(single["data"])

# Batch: group by 100 addresses to enrich the tokens from the balances response
BATCH_SIZE = 100
addresses = [item["token"] for item in balance_body["data"]]
tokens = {}
missing = []

for i in range(0, len(addresses), BATCH_SIZE):
    res = requests.post(
        "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens:batch",
        json={"addresses": addresses[i : i + BATCH_SIZE]},
        headers={**headers, "Content-Type": "application/json"},
    )
    res.raise_for_status()
    body = res.json()
    for token in body["data"]["tokens"]:
        tokens[token["address"]] = token
    missing.extend(body["data"]["missing"])
```


## การปรับสเกลจำนวนเงินด้วย decimals

ฟิลด์ยอดคงเหลือ `balance` และฟิลด์การโอน ERC-20 `amount` เป็นจำนวนเต็มดิบที่แสดงผลเป็นสตริงฐานสิบ (`UInt256String`); อุปทานรวม `total_supply` ของโทเค็นก็เป็นจำนวนเต็มดิบบนเชนโดยไม่ได้ใช้การปรับสเกล `decimals` เช่นกัน หากต้องการแสดงปริมาณในรูปแบบที่มนุษย์อ่านได้ ให้หารด้วย `decimals` ของโทเค็นนั้น

* `decimals` มาจาก `symbol`/`decimals` ของรายการยอดคงเหลือเอง หรือมาจาก `GET /{chain}/tokens/{token}` และ `POST /{chain}/tokens:batch`; โดยสามารถเป็น `null` ได้
* ค่าเหล่านี้อาจเกิน `2^53` ดังนั้นอย่าคำนวณเลขคณิตด้วยตัวเลข JSON: ให้ใช้ `BigInt` ใน TypeScript และ `Decimal` ใน Python โดยแปลงค่าสตริงฐานสิบตามสภาพเดิมเพื่อหลีกเลี่ยงการสูญเสียความแม่นยำ

**TypeScript**

```ts
function toDisplayAmount(raw: string, decimals: number | null): string {
  if (decimals === null) return raw; // no decimals metadata: keep the raw integer
  const value = BigInt(raw);
  const base = 10n ** BigInt(decimals);
  const whole = value / base;
  const fraction = (value % base)
    .toString()
    .padStart(decimals, "0")
    .replace(/0+$/, "");
  return fraction ? `${whole}.${fraction}` : whole.toString();
}

// balance.balance is a raw decimal string; decimals comes from the same item or tokens:batch.
const display = toDisplayAmount(balance.balance, balance.decimals);
```


  **Python**

```python
from decimal import Decimal


def to_display_amount(raw: str, decimals: int | None) -> str:
    if decimals is None:
        return raw  # no decimals metadata: keep the raw integer
    value = Decimal(raw)  # parse the decimal string exactly
    return format(value.scaleb(-decimals).normalize(), "f")


# balance["balance"] is a raw decimal string; decimals comes from the same item or tokens:batch.
display = to_display_amount(balance["balance"], balance["decimals"])
```


## ความสดใหม่ของข้อมูล

ทุกการตอบกลับที่สำเร็จในระดับเชนจะมี `meta` รวมอยู่ด้วย:

* `as_of_block`: บล็อกใหม่ล่าสุดของเชนที่เขียนข้อมูลเสร็จสมบูรณ์แล้ว โดย endpoint ในระดับบล็อกจะให้บริการข้อมูลจนถึงความสูงนี้
* `safe_block`: เครื่องหมายระบุแท็กบล็อกฉันทามติ `safe` ของโหนด (เป็น `null` เมื่อยังไม่ทราบ) ค่านี้จะไม่ต่ำกว่า `finalized_block` และไม่ตัดทอน ปฏิเสธ หรือทำให้การตอบกลับล่าช้า
* `finalized_block`: เครื่องหมายระบุแท็กบล็อกฉันทามติ `finalized` ของโหนด (เป็น `null` เมื่อยังไม่ทราบ) ค่านี้ไม่ตัดทอน ปฏิเสธ หรือทำให้การตอบกลับล่าช้า; ไคลเอนต์สามารถตัดสินใจเลือกระดับความปลอดภัยที่ต้องการจากเครื่องหมายนี้ได้เอง (เช่น สถานะการยืนยัน)
* `coverage`: `"full"` หรือ `"partial"` endpoint การโอนของแอดเดรสและ endpoint ที่คล้ายกันจะรายงาน `"partial"` เมื่อ `clamp` ได้ตัดทอนหน้าต่างที่ให้บริการให้แคบลง หรือเมื่อหน้าต่างเริ่มต้นก่อนบล็อกแรกที่ทำดัชนีของเชน
* `refreshed_at`: เวลาที่ข้อมูลเบื้องหลังการตอบกลับได้รับการอัปเดตล่าสุด (UTC) อาจเป็น `null`: `null` หมายความว่าไม่ทราบเวลาอัปเดตของข้อมูลนี้และควรได้รับการปฏิบัติเสมือนข้อมูลล้าสมัย; ส่วน endpoint ที่อิงตามบล็อกจะส่งคืนค่าเสมอ
* นอกจากนี้ยังระบุซ้ำถึง `chain`, `chain_slug` และ `chain_external_id`

รูปแบบที่ใช้กันทั่วไป: ให้อ่าน `meta.as_of_block` จากการตอบกลับแรกใดๆ เพื่ออ่านข้อมูลจนถึงบล็อกที่ทำดัชนีใหม่ล่าสุด และตรวจสอบ `meta.safe_block` / `meta.finalized_block` หากคุณต้องการแสดงสถานะที่ได้รับการยืนยันแล้ว

## ประมาณการ CU สำหรับการโหลดหน้าหนึ่งครั้ง

ทุกเมธอดจะถูกคิดค่าบริการตามค่าน้ำหนัก CU ซึ่งอ่านมาจาก API แผนของแพลตฟอร์ม:

**น้ำหนัก CU ต่อการเรียก**

| เมธอด | CU ต่อการเรียก |
| --- | --- |
| `data.address_balances` | 25 |
| `data.address_transfers` | 25 |
| `data.tokens_batch` | 10 |

**การโหลดหน้าหนึ่งครั้ง (โดยประมาณ)**

คำขอ balances 1 ครั้ง + หน้า transfer 3 หน้า + คำขอ `tokens:batch` 1 ครั้ง, รวมทั้งหมด 5 การเรียก, ประมาณ 110 CU. การใช้งานจริงขึ้นอยู่กับจำนวนหน้าและโทเค็น

สำหรับการตัดสินใจเกี่ยวกับการเรียกเก็บเงินและการตอบกลับข้อผิดพลาดที่ไม่คิดค่าบริการ โปรดดู [กฎการเรียกเก็บเงิน](https://docs.blockvectra.com/en/guides/billing-rules/) หากสิ่งที่คุณต้องการไม่ใช่ประวัติการโอนที่ทำดัชนีไว้ แต่เป็น log จากบล็อกล่าสุด โปรดอ่าน [ข้อมูลโหนดล่าสุดเทียบกับประวัติที่ทำดัชนี](https://docs.blockvectra.com/en/guides/logs-vs-transfers/) ก่อนที่จะตัดสินใจว่าจะเปลี่ยนไปใช้ `eth_getLogs` หรือไม่

## ขั้นตอนถัดไป

* [เลือกดูไดเรกทอรีชุดข้อมูล](https://blockvectra.com/en/data/) เพื่อดูชุดข้อมูลทั้งหมดที่ BlockVectra จัดทำดัชนี
* [ดูแพ็กเกจฟรีและการกำหนดราคา](https://blockvectra.com/en/pricing/#free) เพื่อตรวจสอบสิทธิประโยชน์ในบัญชีของคุณ
* [เข้าสู่ระบบคอนโซล](https://console.blockvectra.com/login/?next=%2Fkeys%2F) เพื่อสร้าง API key
