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

# 如何使用 getTokenAccountsByOwner

> 了解 getTokenAccountsByOwner 的使用案例、代码示例、请求参数、响应结构和提示。

[RPC 方法](https://www.helius.dev/docs/api-reference/rpc/http/gettokenaccountsbyowner) 用于检索由特定公钥拥有的所有 SPL [代币账户](https://www.helius.dev/blog/how-to-get-token-holders-on-solana)。这是钱包和应用程序需要显示用户代币持有或与其各个代币账户交互的基本方法。

您必须通过特定的代币 `mint` 或 `programId` （例如，SPL 代币程序或 Token-2022 程序）来过滤查询。

对于拥有大量代币组合的钱包，考虑使用 [/api-reference/rpc/http/gettokenaccountsbyownerv2)](https://www.helius.dev/docs/api-reference/rpc/http/gettokenaccountsbyowner) 提供支持可配置页面大小的游标分页，每个请求最多 10,000 个账户。

## 常见用例

* **显示用户投资组合：** 获取给定用户钱包地址的所有代币账户（以及余额），以显示其完整的代币投资组合。
* **应用逻辑：** 在发起转账或其他交互之前，识别用户特定代币的账户。
* **验证：** 检查某个所有者拥有的特定类型代币的账户。
* **索引代币持有者：** 虽然对于全局索引效率较低，但可以用于查找已知所有者集合的账户。

## 请求参数

1. **`ownerPubkey`** (string, required): 要检索其代币账户的账户所有者的 base-58 编码公钥。

2. **`filter`** (object, required): 一个 JSON 对象，**必须**指定 /\* INLINE\_CODE\_PLACEHOLDER\_a4fd5a3d8817d1fb3b3b2abedaceb3fe */ 或 /* INLINE\_CODE\_PLACEHOLDER\_d0e7ff6d24268cce4eba6844d37f400f \*/:
   * **`mint`** (string): 特定代币铸币的 base-58 编码公钥。如果提供，将仅返回由 `ownerPubkey` 拥有的此铸币的代币账户。
   * **`programId`** (string): 管理账户的代币程序的 base-58 编码公钥。常见值有：
     * SPL 代币程序: `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`
     * Token-2022 程序: `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb`

3. **`options`** (object, optional): 一个可选的配置对象，可以包括：
   * **`commitment`** (string, optional): 指定 [承诺级别](https://www.helius.dev/blog/solana-commitment-levels)。
   * **`encoding`** (string, optional): 账户数据的编码。建议使用 `"jsonParsed"`。其他选项： `"base64"`, `"base64+zstd"`。默认为 `"base64"`。
   * **`dataSlice`** (object, optional): 要检索账户数据的特定切片（`offset`: usize, `length`: usize）。仅适用于 `base58`, `base64`, 或 `base64+zstd` 编码。
   * **`minContextSlot`** (u64, optional): 查询的最低槽位。

## 响应结构

JSON-RPC 响应中的 `result.value` 字段是一个对象数组。每个对象对应于一个由 `ownerPubkey` 拥有的 SPL Token 账户，并与 `filter` 匹配。

`value` 数组中的每个对象包含：

* **`pubkey`** (字符串): 令牌账户本身的 base-58 编码公钥。
* **`account`** (对象): 令牌账户的详细信息：
  * **`lamports`** (u64): 免租金的 Lamport 余额。
  * **`owner`** (字符串): 所有程序（例如，Token Program 公钥）。
  * **`data`**: 账户数据。如果使用 `"jsonParsed"` 编码，则包含：
    * **`program`** (字符串): 例如，`"spl-token"`。
    * **`parsed`**: 包含结构化信息的对象：
      * **`info`**: 详细信息如：
        * **`mint`** (字符串): 令牌的铸造地址。
        * **`owner`** (字符串): 令牌账户的所有者（应与请求中的 `ownerPubkey` 匹配）。
        * **`tokenAmount`** (对象): 令牌余额（`amount`, `decimals`, `uiAmount`, `uiAmountString`）。
        * **`state`** (字符串): 令牌账户的状态（例如，`"initialized"`）。
        * **`isNative`** (布尔值): 如果账户持有包装的 SOL。
        * **`delegate`** (字符串, 可选): 如果设置了委托，委托地址。
        * **`delegatedAmount`** (对象, 可选): 如果设置了委托，委托的金额。
      * **`type`** (字符串): 例如，`"account"`。
  * **`executable`** (布尔值): 账户是否可执行。
  * **`rentEpoch`** (u64): 下个时代租金到期时间。
  * **`space`** (u64, 如果不是 `jsonParsed`): 原始账户数据的字节长度。

**示例响应（使用 `jsonParsed` 编码，按 `programId` 过滤）：**

```json theme={"system"}
{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "slot": 183459000
    },
    "value": [
      {
        "pubkey": "AssociatedTokenAccountPubkey1...",
        "account": {
          "data": {
            "program": "spl-token",
            "parsed": {
              "info": {
                "isNative": false,
                "mint": "SomeTokenMintPubkey...",
                "owner": "OwnerPubkeyProvidedInRequest...",
                "state": "initialized",
                "tokenAmount": {
                  "amount": "1000000000", // 1 token if decimals is 9
                  "decimals": 9,
                  "uiAmount": 1.0,
                  "uiAmountString": "1.0"
                }
              },
              "type": "account"
            },
            "space": 165
          },
          "executable": false,
          "lamports": 2039280,
          "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
          "rentEpoch": 380
        }
      },
      {
        "pubkey": "AnotherAssociatedTokenAccountPubkey...",
        "account": {
          // ... similar structure for another token owned by the same owner
        }
      }
    ]
  },
  "id": 1
}
```

## 代码示例

<CodeGroup>
  ```bash cURL theme={"system"}
  # Replace <OWNER_PUBKEY> and <TOKEN_MINT_PUBKEY> or <TOKEN_PROGRAM_ID>

  # Example filtering by programId (SPL Token Program)
  curl -X POST -H "Content-Type: application/json" -d \
    '{
      "jsonrpc": "2.0",
      "id": 1,
      "method": "getTokenAccountsByOwner",
      "params": [
        "<OWNER_PUBKEY>",
        { "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" },
        { "encoding": "jsonParsed" }
      ]
    }' \
    <YOUR_RPC_URL>

  # Example filtering by a specific mint
  curl -X POST -H "Content-Type: application/json" -d \
    '{
      "jsonrpc": "2.0",
      "id": 1,
      "method": "getTokenAccountsByOwner",
      "params": [
        "<OWNER_PUBKEY>",
        { "mint": "<SPECIFIC_TOKEN_MINT_PUBKEY>" },
        { "encoding": "jsonParsed", "commitment": "confirmed" }
      ]
    }' \
    <YOUR_RPC_URL>
  ```

  ```javascript JavaScript (using @solana/web3.js) theme={"system"}
  const { Connection, PublicKey } = require('@solana/web3.js');

  async function findOwnerTokenAccounts(ownerAddress, filter, encoding = 'jsonParsed') {
    // Replace with your RPC endpoint
    const connection = new Connection('https://mainnet.helius-rpc.com/?api-key=<api-key>');
    const ownerPubKey = new PublicKey(ownerAddress);

    try {
      let actualFilter;
      if (filter.mint) {
        actualFilter = { mint: new PublicKey(filter.mint) };
      } else if (filter.programId) {
        actualFilter = { programId: new PublicKey(filter.programId) };
      } else {
        console.error("Filter must contain either 'mint' or 'programId'");
        return;
      }

      const accounts = await connection.getTokenAccountsByOwner(
        ownerPubKey,
        actualFilter,
        { encoding }
      );

      console.log(`Found ${accounts.value.length} token accounts for owner ${ownerAddress}:`);
      accounts.value.forEach(accInfo => {
        console.log(`  Token Account: ${accInfo.pubkey.toBase58()}`);
        if (encoding === 'jsonParsed' && accInfo.account.data.parsed) {
          console.log(`    Mint: ${accInfo.account.data.parsed.info.mint}`);
          console.log(`    Balance: ${accInfo.account.data.parsed.info.tokenAmount.uiAmountString}`);
        }
        // console.log(JSON.stringify(accInfo, null, 2)); // For full details
      });

    } catch (error) {
      console.error(`Error fetching token accounts for owner ${ownerAddress}:`, error);
    }
  }

  // Replace with an actual owner's public key
  const exampleOwner = 'HXtBm8XZbxaTt41uqaKhwUAa6Z1aPyvJdsZVENiWsetg'; // Example wallet address

  // Example 1: Find all SPL Token Program accounts owned by `exampleOwner`
  findOwnerTokenAccounts(exampleOwner, { programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' });

  // Example 2: Find USDC token accounts owned by `exampleOwner`
  // const usdcMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
  // findOwnerTokenAccounts(exampleOwner, { mint: usdcMint });

  // Example 3: Find Token-2022 Program accounts owned by `exampleOwner`
  // findOwnerTokenAccounts(exampleOwner, { programId: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb' });
  ```
</CodeGroup>

## 开发者提示

* **过滤要求：** 您*必须*在过滤器中提供 `mint` 或 `programId`。如果没有其中一个主要过滤器，不可能查询所有令牌类型的所有所有者令牌账户。
* **关联令牌账户：** 此方法将返回由公钥拥有的所有令牌账户，包括标准的关联令牌账户（ATAs）和他们可能拥有的任何其他 SPL 令牌账户（例如，来自旧的钱包实现或自定义设置）。
* **编码：** 强烈推荐为 `encoding` 选项使用 `"jsonParsed"`。它将二进制账户数据解码为更可用的 JSON 结构。
* **性能：** 如果所有者拥有大量令牌账户（尤其是仅按 `programId` 过滤时），响应可能会很大。对于这种情况，使用 [`getTokenAccountsByOwnerV2`](/docs/zh/api-reference/rpc/http/gettokenaccountsbyownerv2) 提供内置分页支持。
* **Token-2022（令牌扩展）：** 如果您使用 Token-2022 程序（支持传输费用、利息等扩展）创建的令牌，请确保使用正确的 `programId`：`TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb`。

本指南提供对 `getTokenAccountsByOwner` RPC 方法的全面理解，使您能够高效地检索任何 Solana 地址的令牌账户信息。

## 大型代币组合的分页

对于持有大量令牌的钱包，请使用 [`getTokenAccountsByOwnerV2`](/docs/zh/api-reference/rpc/http/gettokenaccountsbyownerv2) 提供：

* **基于游标的分页**：设置 `limit`（1-10,000）并使用 `paginationKey` 导航结果
* **增量更新**：使用 `changedSinceSlot` 仅获取自某个特定槽位以来修改的令牌账户
* **更好的性能**：防止超时并实现实时投资组合跟踪
* **分页行为**：只有在不返回令牌账户时才表示分页结束。由于过滤器，返回的账户可能少于限制 - 继续分页直到 `paginationKey` 为 null

```typescript theme={"system"}
// Example: Paginated query for all token accounts
const response = await fetch("https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: "1",
    method: "getTokenAccountsByOwnerV2",
    params: [
      "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
      { "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" },
      {
        encoding: "jsonParsed",
        limit: 1000
      }
    ]
  })
});

const data = await response.json();
console.log(`Found ${data.result.value.length} token accounts`);
if (data.result.paginationKey) {
  console.log("More results available, use paginationKey for next page");
  // Continue pagination even if fewer than limit accounts were returned
} else {
  console.log("End of pagination - no more token accounts available");
}
```

## 相关方法

<CardGroup cols={2}>
  <Card title="getTokenAccountsByOwnerV2" href="/docs/zh/api-reference/rpc/http/gettokenaccountsbyownerv2">
    针对大型投资组合的基于游标的分页导航版本
  </Card>

  <Card title="getTokenAccountBalance" href="/docs/zh/api-reference/rpc/http/gettokenaccountbalance">
    获取特定代币账户的余额
  </Card>
</CardGroup>
