Querying Balances and Assets
The Permissioned Asset Standard (PAS) does not expose dedicated read APIs in the SDK. This is by design. PAS stores assets in standard Sui objects with deterministic addresses, so you query them using the same RPC methods you use for any other address-owned data (for example, balance queries and owned object queries).
A wallet address on Sui can hold 2 kinds of balances:
-
Regular balances: Owned directly by the wallet address
-
PAS balances: Managed by PAS on behalf of the wallet address
From your perspective, both belong to the same wallet. PAS balances, however, are held in a derived Account object that has its own address, so transfers always target the Account address, not the wallet address. The SDK resolves the underlying Account from the wallet address transparently.
How do I read PAS balances for an address?
To read a wallet's PAS balances:
- Create a client and extend it with the PAS SDK: import
SuiGrpcClientfrom@mysten/sui/grpcandpasfrom@mysten/pas, then call.$extend(pas())on the client. - Derive the internal Account address from the wallet address with
client.pas.deriveAccountAddress(the derivation is deterministic). - Query the derived address with core SDK balance methods (
listBalancesorgetBalance).
The following diagram shows the derivation flow:
Wallet Address (for example, 0xAlice)
│
▼
┌──────────────────────────────┐
│ client.pas │
│ .deriveAccountAddress( │
│ '0xAlice' │
│ ) │
└──────────────────────────────┘
│
▼
Account Address (deterministic, same every time)
In the diagram and examples, 0xAlice is a placeholder; substitute a full Sui wallet address.
The derived address is different per network (Mainnet compared to Testnet) because the PAS namespace object has a different ID on each network. The SDK handles this based on the client network configuration.
The following example shows how to query both regular and PAS-managed balances:
import { SuiGrpcClient } from '@mysten/sui/grpc';
import { pas } from '@mysten/pas';
const client = new SuiGrpcClient({ network: 'mainnet' }).$extend(pas());
const walletAddress = '0xAlice'; // placeholder: use a full Sui address
// Regular balances — query the wallet address directly
const { balances: regularBalances } = await client.listBalances({
owner: walletAddress,
});
// PAS-managed balances — derive the Account address, then query it
const { balances: pasBalances } = await client.listBalances({
owner: client.pas.deriveAccountAddress(walletAddress),
});
// Single asset type
const { balance } = await client.getBalance({
owner: client.pas.deriveAccountAddress(walletAddress),
// placeholder: replace '0xabc...' with the full package address
coinType: '0xabc...::my_coin::MY_COIN',
});
How do I check whether a wallet has a PAS Account?
getObject throws an ObjectError when the object does not exist, so it never returns a missing Account as a value to test. Use getObjects, which returns the error in place of the object instead of throwing:
const accountAddress = client.pas.deriveAccountAddress(walletAddress);
const {
objects: [account],
} = await client.getObjects({ objectIds: [accountAddress] });
if (account instanceof Error) {
// No PAS Account for this wallet yet
}
Non-policy assets in Accounts
An Account can hold asset types that a PAS policy does not govern. For example, Balance<SUI> might end up in an Account through a direct balance::send_funds call. When you call listBalances, these show up alongside policy-managed assets.
Key points
- Wallet-centric: PAS balances belong to the wallet address. The Account is an internal detail that the SDK handles.
- Deterministic: The same wallet address always resolves to the same derived Account address.
- Standard RPC:
getBalanceandlistBalanceswork on the derived address. No custom endpoints are needed. - No indexing needed: Derivation is client-side. No event subscriptions or custom indexers are required.