Skip to main content

ListOwnedObjects

Discover All Assets Owned by an Address#

The ListOwnedObjects method returns a paginated list of all objects owned by a specific Sui address. This is fundamental for wallet applications, portfolio dashboards, and any interface that needs to display a user's blockchain assets including NFTs, coins, and custom objects.

Overview#

Every user on Sui owns a collection of objects—from SUI coins and custom tokens to NFTs and smart contract instances. The ListOwnedObjects method provides efficient enumeration of these assets with built-in pagination for handling large collections. Applications use this to build asset lists, calculate portfolio values, and enable user interactions with their blockchain holdings.

Method Signature#

Service: sui.rpc.v2beta2.LiveDataService Method: ListOwnedObjects Type: Unary RPC with pagination

Parameters#

ParameterTypeRequiredDescription
ownerstringYesAddress to query (66-char hex with 0x prefix)
page_sizeuint32NoObjects per page (default: 50, max: 200)
page_tokenstringNoContinuation token from previous response
read_maskFieldMaskNoFields to include for each object
object_typestringNoFilter by object type

Field Mask Options#

PathDescription
object_idObject identifier
versionVersion number
digestObject digest
ownerOwnership details
object_typeType classification
has_public_transferTransfer capability
contentsObject data fields

Response Structure#

message ListOwnedObjectsResponse {
repeated Object objects = 1;
string next_page_token = 2;
}

Code Examples#

import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';

const ENDPOINT = 'api-sui-mainnet-full.n.dwellir.com';
const API_TOKEN = 'your_api_token_here';

const packageDefinition = protoLoader.loadSync(
'./protos/live_data.proto',
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: ['./protos']
}
);

const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as any;
const credentials = grpc.credentials.createSsl();
const client = new protoDescriptor.sui.rpc.v2beta2.LiveDataService(
ENDPOINT,
credentials
);

const metadata = new grpc.Metadata();
metadata.add('x-api-key', API_TOKEN);

// List objects with pagination
async function listOwnedObjects(
owner: string,
pageSize: number = 50,
pageToken?: string
): Promise<{ objects: any[]; nextPageToken: string }> {
return new Promise((resolve, reject) => {
const request: any = {
owner: owner,
page_size: pageSize,
read_mask: {
paths: [
'object_id',
'version',
'digest',
'owner',
'object_type',
'has_public_transfer'
]
}
};

if (pageToken) {
request.page_token = pageToken;
}

client.ListOwnedObjects(request, metadata, (error: any, response: any) => {
if (error) {
console.error('ListOwnedObjects error:', error.message);
reject(error);
return;
}

resolve({
objects: response.objects || [],
nextPageToken: response.next_page_token || ''
});
});
});
}

// Fetch all owned objects (handles pagination automatically)
async function fetchAllOwnedObjects(owner: string): Promise<any[]> {
const allObjects: any[] = [];
let pageToken: string | undefined = undefined;
let pageCount = 0;

do {
const { objects, nextPageToken } = await listOwnedObjects(
owner,
50,
pageToken
);

allObjects.push(...objects);
pageToken = nextPageToken;
pageCount++;

console.log(`Fetched page ${pageCount}: ${objects.length} objects`);

} while (pageToken);

console.log(`\n✓ Total objects: ${allObjects.length}`);

return allObjects;
}

// Categorize objects by type
interface CategorizedAssets {
nfts: any[];
coins: any[];
packages: any[];
other: any[];
}

async function categorizeUserAssets(owner: string): Promise<CategorizedAssets> {
const objects = await fetchAllOwnedObjects(owner);

const assets: CategorizedAssets = {
nfts: [],
coins: [],
packages: [],
other: []
};

objects.forEach(obj => {
const type = obj.object_type.toLowerCase();

if (type.includes('::coin::coin')) {
assets.coins.push(obj);
} else if (type.includes('::package::')) {
assets.packages.push(obj);
} else if (obj.has_public_transfer && !type.includes('::coin::')) {
assets.nfts.push(obj);
} else {
assets.other.push(obj);
}
});

console.log('\nAsset Summary:');
console.log('==============');
console.log(`Coins: ${assets.coins.length}`);
console.log(`NFTs: ${assets.nfts.length}`);
console.log(`Packages: ${assets.packages.length}`);
console.log(`Other: ${assets.other.length}`);

return assets;
}

// Filter by object type
async function listObjectsByType(
owner: string,
objectType: string
): Promise<any[]> {
return new Promise((resolve, reject) => {
const request = {
owner: owner,
object_type: objectType,
page_size: 100,
read_mask: {
paths: ['object_id', 'object_type', 'contents']
}
};

client.ListOwnedObjects(request, metadata, (error: any, response: any) => {
if (error) reject(error);
else resolve(response.objects || []);
});
});
}

// Usage examples
const userAddress = '0xac5bceec1b789ff840d7d4e6ce4ce61c90d190a7f8c4f4ddf0bff6ee2413c33c';

// List first page
const firstPage = await listOwnedObjects(userAddress);
console.log(`Page 1: ${firstPage.objects.length} objects`);

// Fetch all objects
const allObjects = await fetchAllOwnedObjects(userAddress);

// Categorize assets
const categorized = await categorizeUserAssets(userAddress);

// Filter by type
const suiCoins = await listObjectsByType(userAddress, '0x2::coin::Coin<0x2::sui::SUI>');
console.log(`SUI coins: ${suiCoins.length}`);

Use Cases#

1. Wallet Asset Display#

Build comprehensive wallet interfaces:

interface WalletAsset {
id: string;
type: string;
category: 'coin' | 'nft' | 'other';
displayName: string;
imageUrl?: string;
value?: string;
}

async function buildWalletView(owner: string): Promise<WalletAsset[]> {
const objects = await fetchAllOwnedObjects(owner);

return objects.map(obj => ({
id: obj.object_id,
type: obj.object_type,
category: categorizeObject(obj),
displayName: extractDisplayName(obj),
imageUrl: extractImage(obj),
value: extractValue(obj)
}));
}

function categorizeObject(obj: any): 'coin' | 'nft' | 'other' {
if (obj.object_type.includes('::coin::Coin')) return 'coin';
if (obj.has_public_transfer) return 'nft';
return 'other';
}

Display user's NFT collection:

interface NFTDisplay {
id: string;
name: string;
image: string;
collection: string;
attributes: Record<string, any>;
}

async function loadNFTGallery(owner: string): Promise<NFTDisplay[]> {
const allObjects = await fetchAllOwnedObjects(owner);

const nfts = allObjects.filter(obj =>
obj.has_public_transfer &&
!obj.object_type.includes('::coin::')
);

return nfts.map(nft => ({
id: nft.object_id,
name: nft.contents?.fields?.name || 'Unnamed',
image: nft.contents?.fields?.url || '',
collection: extractCollection(nft.object_type),
attributes: nft.contents?.fields || {}
}));
}

function extractCollection(objectType: string): string {
const parts = objectType.split('::');
return parts[parts.length - 1];
}

3. Portfolio Value Calculation#

Calculate total portfolio value:

async function calculatePortfolioValue(owner: string): Promise<number> {
const objects = await fetchAllOwnedObjects(owner);

const coins = objects.filter(obj =>
obj.object_type.includes('::coin::Coin')
);

let totalValue = 0;

for (const coin of coins) {
const coinType = extractCoinType(coin.object_type);
const balance = await getBalance(owner, coinType);
const price = await getCoinPrice(coinType);

totalValue += (parseInt(balance) / 1e9) * price;
}

return totalValue;
}

4. Asset Transfer Preparation#

Prepare objects for batch transfer:

async function prepareTransferables(owner: string): Promise<string[]> {
const objects = await fetchAllOwnedObjects(owner);

return objects
.filter(obj => obj.has_public_transfer)
.map(obj => obj.object_id);
}

Pagination Best Practices#

Efficient Pagination#

Handle large collections efficiently:

async function* paginatedObjectStream(
owner: string,
pageSize: number = 50
): AsyncGenerator<any[], void, unknown> {
let pageToken: string | undefined = undefined;

do {
const { objects, nextPageToken } = await listOwnedObjects(
owner,
pageSize,
pageToken
);

yield objects;
pageToken = nextPageToken;

} while (pageToken);
}

// Usage
for await (const objectsPage of paginatedObjectStream(userAddress)) {
console.log(`Processing ${objectsPage.length} objects...`);
// Process page
}

Progressive Loading#

Load assets progressively for better UX:

async function loadAssetsProgressively(
owner: string,
onPageLoaded: (objects: any[], totalSoFar: number) => void
): Promise<any[]> {
const allObjects: any[] = [];
let pageToken: string | undefined = undefined;

do {
const { objects, nextPageToken } = await listOwnedObjects(
owner,
50,
pageToken
);

allObjects.push(...objects);
onPageLoaded(objects, allObjects.length);

pageToken = nextPageToken;

} while (pageToken);

return allObjects;
}

// Usage
await loadAssetsProgressively(userAddress, (page, total) => {
console.log(`Loaded ${page.length} more. Total: ${total}`);
updateUI(page); // Update UI incrementally
});

Performance Optimization#

Caching Strategy#

Cache object lists with TTL:

interface CachedObjectList {
objects: any[];
timestamp: number;
owner: string;
}

const cache = new Map<string, CachedObjectList>();
const CACHE_TTL = 30000; // 30 seconds

async function getCachedOwnedObjects(owner: string): Promise<any[]> {
const cached = cache.get(owner);

if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
console.log('✓ Using cached data');
return cached.objects;
}

const objects = await fetchAllOwnedObjects(owner);

cache.set(owner, {
objects,
timestamp: Date.now(),
owner
});

return objects;
}

Error Handling#

async function safeListOwnedObjects(owner: string): Promise<any[]> {
try {
return await fetchAllOwnedObjects(owner);
} catch (error: any) {
if (error.code === grpc.status.INVALID_ARGUMENT) {
console.error('Invalid address format');
return [];
}

if (error.code === grpc.status.NOT_FOUND) {
console.log('Address has no objects');
return [];
}

throw error;
}
}

Need help with asset enumeration? Contact support@dwellir.com or check the gRPC overview.