Reading protocol state
Query Spield's live state — positions, prices, solvency, and balances — with free Soroban RPC simulations, no wallet or fees required.
Reading Spield is free. Contract reads are simulated against a Soroban RPC node — no
wallet, no signing, no fees. This page shows the pattern with @stellar/stellar-sdk (JS/TS).
Install
pnpm add @stellar/stellar-sdkThe read helper
Every read follows the same shape: build a dummy invocation, simulate it, and decode the result to a native JS value.
import {
rpc,
TransactionBuilder,
Contract,
Address,
nativeToScVal,
scValToNative,
BASE_FEE,
} from '@stellar/stellar-sdk';
const RPC_URL = 'https://mainnet.sorobanrpc.com';
const NETWORK_PASSPHRASE = 'Public Global Stellar Network ; September 2015';
const server = new rpc.Server(RPC_URL);
// A throwaway source account is fine for read-only simulation.
const SIM_SOURCE = 'GADUMMY...'; // any valid account id
export async function readContract<T = unknown>(
contractId: string,
method: string,
args: unknown[] = [],
): Promise<T> {
const account = await server.getAccount(SIM_SOURCE).catch(() => null);
const source = account ?? { accountId: () => SIM_SOURCE, sequenceNumber: () => '0', incrementSequenceNumber() {} } as never;
const contract = new Contract(contractId);
const tx = new TransactionBuilder(source as never, {
fee: BASE_FEE,
networkPassphrase: NETWORK_PASSPHRASE,
})
.addOperation(contract.call(method, ...(args as never[])))
.setTimeout(30)
.build();
const sim = await server.simulateTransaction(tx);
if (rpc.Api.isSimulationError(sim)) throw new Error(sim.error);
return scValToNative(sim.result!.retval) as T;
}Encoding arguments
Convert JS values to Soroban values (ScVal) for the call:
const u64 = (n: number | bigint) => nativeToScVal(BigInt(n), { type: 'u64' });
const i128 = (n: bigint) => nativeToScVal(n, { type: 'i128' });
const addr = (a: string) => new Address(a).toScVal();Examples
// returns [backing, principal, unclaimed] as bigints (base units)
const [backing, principal, unclaimed] =
await readContract<bigint[]>(WRAPPER_ID, 'solvency');
const solvent = backing >= principal; // always true by constructionconst pos = await readContract<Record<string, unknown>>(
WRAPPER_ID,
'position_value',
[u64(positionId)],
);
// { principal, claimable_yield, pt_amount, yt_amount, open }const ptPrice = await readContract<bigint>(MARKET_ID, 'pt_price'); // 12-dp fixed point
const impliedApy = await readContract<bigint>(MARKET_ID, 'implied_apy'); // 12-dp fraction
const pricer = Number(ptPrice) / 1e12; // e.g. 0.9987
const apyPct = (Number(impliedApy) / 1e12) * 100; // e.g. 1.56Market reads are panic-free: a thin/empty pool or a matured market returns 0, which you
should render as "no price / amount exceeds liquidity" rather than an error.
// SAC token balance for an account, in base units
const balance = await readContract<bigint>(
USDC_ID,
'balance',
[addr(accountId)],
);Read the token addresses from the engine (pt_token(), yt_token(), underlying()) so you
never hard-code them.
Discovering a user's positions
The engine doesn't keep an on-chain owner→positions index (that would require unbounded
iteration). To list a user's positions, scan position ids and keep the ones whose
get_position(id).owner matches, stopping after a run of misses:
async function getOwnerPositions(owner: string, maxScan = 64) {
const found = [];
let misses = 0;
for (let id = 0; id < maxScan && misses < 5; id++) {
try {
const pos = await readContract<Record<string, unknown>>(WRAPPER_ID, 'get_position', [u64(id)]);
misses = 0;
if (String(pos.owner) === owner) found.push(id);
} catch {
misses++; // non-existent id
}
}
return found;
}Or index events instead
For production dashboards, indexing events is more efficient than
scanning — you reconstruct ownership from mint / transfer / redeem events.
Next: submitting transactions.
Tokens & units
How Spield's tokens work as Stellar assets — PT, YT, and USDC as SACs, their 7-decimal base units, and the fixed-point convention for rates.
Integrating (writes)
Build, simulate, sign, and submit Spield transactions from your app — the standard Soroban write lifecycle with wallet signing.