Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 54 additions & 6 deletions src/account/streamAccount.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ok, err, SorokitErrorCode } from "../shared/response";
import type { SorokitResult } from "../shared/response";
import { sleep, toMessage } from "../shared";
import type { SorokitLogger } from "../shared/logger";
import type { AccountInfo } from "./types";
import { getAccount } from "./getAccount";

Expand Down Expand Up @@ -48,15 +49,32 @@ export async function* streamAccount(
publicKey: string,
config?: AccountStreamConfig,
signal?: AbortSignal,
logger?: SorokitLogger,
): AsyncGenerator<SorokitResult<AccountInfo>> {
const intervalMs = Math.max(config?.intervalMs ?? 5_000, 1_000);
const maxPolls = config?.maxPolls;
const emitOnStart = config?.emitOnStart ?? true;

let polls = 0;

logger?.debug("account.stream", {
operation: "account.stream",
status: "start",
publicKey,
intervalMs,
maxPolls,
});

while (true) {
if (signal?.aborted) return;
if (signal?.aborted) {
logger?.debug("account.stream", {
operation: "account.stream",
status: "ok",
reason: "aborted",
polls,
});
return;
}

// Respect maxPolls limit
if (maxPolls !== undefined && polls >= maxPolls) return;
Expand All @@ -73,14 +91,44 @@ export async function* streamAccount(
if (signal?.aborted) return;

try {
logger?.debug("account.stream.poll", {
operation: "account.stream.poll",
status: "start",
publicKey,
poll: polls + 1,
});

const result = await getAccount(horizonUrl, publicKey);

if (result.status === "ok") {
logger?.debug("account.stream.poll", {
operation: "account.stream.poll",
status: "ok",
publicKey,
poll: polls + 1,
});
} else {
logger?.warn("account.stream.poll", {
operation: "account.stream.poll",
status: "error",
publicKey,
poll: polls + 1,
errorCode: result.error.code,
errorMessage: result.error.message,
});
}

yield result;
} catch (cause) {
yield err(
SorokitErrorCode.ACCOUNT_FETCH_FAILED,
`Account stream poll failed: ${toMessage(cause)}`,
cause,
);
const message = `Account stream poll failed: ${toMessage(cause)}`;
logger?.warn("account.stream.poll", {
operation: "account.stream.poll",
status: "error",
publicKey,
poll: polls + 1,
errorMessage: message,
});
yield err(SorokitErrorCode.ACCOUNT_FETCH_FAILED, message, cause);
}

polls++;
Expand Down
158 changes: 86 additions & 72 deletions src/client/createSorokitClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ import { prepareContractCall } from "../soroban/prepareCall";
import { simulateTransaction } from "../soroban/simulateTransaction";
import { executeContract } from "../soroban/executeContract";
import { invokeContract } from "../soroban/invokeContract";
import { createLogger } from "../shared/logger";
import { createLogger, withLogging } from "../shared/logger";
import { formatAddress } from "../shared/utils";
import { ok } from "../shared/response";
import type { SorokitResult } from "../shared/response";
import type { SorokitLogger } from "../shared/logger";
import type { LogLevel, SorokitLogger } from "../shared/logger";
import type { SorokitCache } from "../shared/cache";
import type { ResolvedNetworkConfig } from "../shared/types";
import type { NetworkType } from "../network/config";
Expand Down Expand Up @@ -78,7 +78,15 @@ export interface SorokitClientConfig {
rpcUrl?: string;
/** Optional cache implementation — core is stateless by default */
cache?: SorokitCache;
/** Enable debug logging to console. Default: false */
/**
* Minimum log level to emit. Default: "off"
* Set to "debug" for verbose tracing of all SDK operations.
*/
logLevel?: LogLevel;
/**
* Enable debug logging to console. Equivalent to `logLevel: "debug"`.
* @deprecated Prefer `logLevel: "debug"`
*/
debug?: boolean;
/** Custom logger — overrides the built-in console logger */
logger?: SorokitLogger;
Expand Down Expand Up @@ -254,7 +262,11 @@ export function createSorokitClient(

const networkConfig = networkResult.data;
const { horizonUrl, rpcUrl, networkPassphrase } = networkConfig;
const logger = createLogger(config.debug ?? false, config.logger);
const logger =
config.logger ??
createLogger({
logLevel: config.logLevel ?? (config.debug ? "debug" : "off"),
});
const defaultPollConfig = config.sorobanPoll;
const feeEstimateOptions: FeeEstimateOptions = {
...(config.cache !== undefined ? { cache: config.cache } : {}),
Expand All @@ -263,7 +275,9 @@ export function createSorokitClient(
: {}),
};

logger.debug("Client created", {
logger.info("client.create", {
operation: "client.create",
status: "ok",
network: config.network,
horizonUrl,
rpcUrl,
Expand All @@ -273,40 +287,39 @@ export function createSorokitClient(
networkConfig,

wallet: {
connect: (adapter) => {
logger.debug("wallet.connect", { walletType: adapter.walletType });
return connectWallet(adapter);
},
disconnect: (adapter) => {
logger.debug("wallet.disconnect", { walletType: adapter.walletType });
return disconnectWallet(adapter);
},
signTransaction: (adapter, input) => {
logger.debug("wallet.signTransaction", {
walletType: adapter.walletType,
});
return signTransaction(adapter, input);
},
connect: (adapter) =>
withLogging(logger, "wallet.connect", { walletType: adapter.walletType }, () =>
connectWallet(adapter),
),
disconnect: (adapter) =>
withLogging(logger, "wallet.disconnect", { walletType: adapter.walletType }, () =>
disconnectWallet(adapter),
),
signTransaction: (adapter, input) =>
withLogging(
logger,
"wallet.signTransaction",
{ walletType: adapter.walletType },
() => signTransaction(adapter, input),
),
emptyState: () => emptyWalletState(),
},

account: {
get: (publicKey) => {
logger.debug("account.get", { publicKey });
return getAccount(horizonUrl, publicKey);
},
getBalances: (publicKey) => {
logger.debug("account.getBalances", { publicKey });
return getBalances(horizonUrl, publicKey);
},
getAssetBalances: (publicKey, filter) => {
logger.debug("account.getAssetBalances", { publicKey, filter });
return getAssetBalances(horizonUrl, publicKey, filter);
},
stream: (publicKey, config, signal) => {
logger.debug("account.stream", { publicKey });
return streamAccount(horizonUrl, publicKey, config, signal);
},
get: (publicKey) =>
withLogging(logger, "account.get", { publicKey }, () =>
getAccount(horizonUrl, publicKey),
),
getBalances: (publicKey) =>
withLogging(logger, "account.getBalances", { publicKey }, () =>
getBalances(horizonUrl, publicKey),
),
getAssetBalances: (publicKey, filter) =>
withLogging(logger, "account.getAssetBalances", { publicKey, filter }, () =>
getAssetBalances(horizonUrl, publicKey, filter),
),
stream: (publicKey, streamConfig, signal) =>
streamAccount(horizonUrl, publicKey, streamConfig, signal, logger),
formatAddress: (publicKey, chars) => formatAddress(publicKey, chars),
},

Expand Down Expand Up @@ -363,47 +376,48 @@ export function createSorokitClient(
},

soroban: {
simulate: (transactionXdr) => {
logger.debug("soroban.simulate");
return simulateTransaction(rpcUrl, networkPassphrase, transactionXdr);
},
prepare: (params) => {
logger.debug("soroban.prepare", {
contractId: params.contractId,
method: params.method,
});
return prepareContractCall(rpcUrl, networkConfig, horizonUrl, params);
},
execute: (signedXdr, pollConfig) => {
logger.debug("soroban.execute");
return executeContract(
simulate: (transactionXdr) =>
withLogging(logger, "soroban.simulate", undefined, () =>
simulateTransaction(rpcUrl, networkPassphrase, transactionXdr),
),
prepare: (params) =>
withLogging(
logger,
"soroban.prepare",
{ contractId: params.contractId, method: params.method },
() => prepareContractCall(rpcUrl, networkConfig, horizonUrl, params),
),
execute: (signedXdr, pollConfig) =>
executeContract(
rpcUrl,
networkConfig,
signedXdr,
pollConfig ?? defaultPollConfig,
);
},
invoke: (params, signFn, pollConfig) => {
logger.debug("soroban.invoke", {
contractId: params.contractId,
method: params.method,
});
return invokeContract(
rpcUrl,
networkConfig,
horizonUrl,
params,
signFn,
pollConfig ?? defaultPollConfig,
);
},
read: (params) => {
logger.debug("soroban.read", {
contractId: params.contractId,
method: params.method,
});
return readContract(rpcUrl, horizonUrl, networkConfig, params);
},
logger,
),
invoke: (params, signFn, pollConfig) =>
withLogging(
logger,
"soroban.invoke",
{ contractId: params.contractId, method: params.method },
() =>
invokeContract(
rpcUrl,
networkConfig,
horizonUrl,
params,
signFn,
pollConfig ?? defaultPollConfig,
logger,
),
),
read: (params) =>
withLogging(
logger,
"soroban.read",
{ contractId: params.contractId, method: params.method },
() => readContract(rpcUrl, horizonUrl, networkConfig, params),
),
},

network: {
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,5 @@ export type {
// ─── Response system ──────────────────────────────────────────────────────────
export type { SorokitResult, SorokitError } from "./shared/response";
export { SorokitErrorCode, ok, err, isOk, isErr } from "./shared/response";
export type { SorokitLogger } from "./shared/logger";
export type { SorokitLogger, LogLevel, LoggerConfig } from "./shared/logger";
export type { SorokitCache } from "./shared/cache";
Loading