@ledgerhq/coin-stellar
Version:
Ledger Stellar Coin integration
139 lines • 6.47 kB
JavaScript
;
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
// SPDX-License-Identifier: Apache-2.0
Object.defineProperty(exports, "__esModule", { value: true });
exports.isHorizonErrorBody = isHorizonErrorBody;
exports.getHorizonErrorBody = getHorizonErrorBody;
exports.documentationSummaryFromHorizonBody = documentationSummaryFromHorizonBody;
const stellar_sdk_1 = require("@stellar/stellar-sdk");
const GENERIC_TRANSACTION_FAILED_DETAIL = /The transaction failed when submitted to the Stellar network/i;
/** Horizon `extras.result_codes.transaction` → one-line description (Stellar docs). */
const HORIZON_TRANSACTION_RESULT_CODE_DOCUMENTATION = {
tx_success: 'The transaction succeeded.',
tx_failed: 'One of the operations failed (none were applied).',
tx_too_early: 'The ledger closeTime was before the minTime.',
tx_too_late: 'The ledger closeTime was after the maxTime.',
tx_missing_operation: 'No operation was specified.',
tx_bad_seq: 'Sequence number does not match source account.',
tx_bad_auth: 'Too few valid signatures / wrong network.',
tx_insufficient_balance: 'Fee would bring account below reserve.',
tx_no_source_account: 'Source account not found.',
tx_insufficient_fee: 'Fee is too small.',
tx_bad_auth_extra: 'Unused signatures attached to transaction.',
tx_internal_error: 'An unknown error occurred.',
tx_fee_bump_inner_success: 'Fee bump inner transaction succeeded.',
tx_fee_bump_inner_failed: 'Fee bump inner transaction failed.',
tx_not_supported: 'Transaction type not supported.',
tx_bad_sponsorship: 'Sponsorship is invalid.',
tx_bad_min_seq_age_or_gap: 'Minimum sequence age or gap precondition failed.',
tx_malformed: 'Transaction is malformed.',
tx_soroban_invalid: 'Soroban transaction is invalid.',
};
/** Horizon `extras.result_codes.operations[]` → one-line description (Stellar docs). */
const HORIZON_OPERATION_RESULT_CODE_DOCUMENTATION = {
op_inner: 'The inner object result is valid and the operation was a success.',
op_bad_auth: 'There are too few valid signatures, or the transaction was submitted to the wrong network.',
op_no_source_account: 'The source account was not found.',
op_no_account: 'The source account was not found.',
op_not_supported: 'The operation is not supported at this time.',
op_too_many_subentries: 'Max number of subentries (1000) already reached.',
op_exceeded_work_limit: 'Operation did too much work.',
op_malformed: 'The operation input is invalid.',
op_underfunded: 'The source account does not have enough balance to send the payment amount while maintaining its minimum reserve.',
op_src_no_trust: 'The source account does not have a trustline for the asset it is trying to send.',
op_src_not_authorized: 'The source account is not authorized to send this asset.',
op_no_destination: 'The destination account does not exist.',
op_no_trust: 'The destination account does not have a trustline for the asset being sent.',
op_not_authorized: 'The destination account is not authorized to hold this asset.',
op_line_full: 'The destination account does not have sufficient limits to receive the amount and still satisfy its buying liabilities.',
op_no_issuer: 'The issuer of the asset does not exist.',
};
/** Operation result codes that mark success for a prior op in a multi-op failure list. */
const HORIZON_OPERATION_SUCCESS_RESULT_CODES = new Set(['op_inner']);
function failingOperationCode(operations) {
return operations?.find((code) => !HORIZON_OPERATION_SUCCESS_RESULT_CODES.has(code));
}
function isHorizonErrorBody(data) {
return !!(data &&
typeof data === 'object' &&
'title' in data &&
typeof data.title === 'string' &&
'status' in data &&
typeof data.status === 'number');
}
/**
* Horizon failures are usually {@link BadResponseError} with `response` = problem+json body.
* Axios rejects first; the SDK forwards it unchanged, so the body lives on `error.response.data`.
*/
function getHorizonErrorBody(error) {
if (error instanceof stellar_sdk_1.BadResponseError && isHorizonErrorBody(error.response)) {
return error.response;
}
if (error && typeof error === 'object' && 'response' in error) {
const data = error.response?.data;
if (isHorizonErrorBody(data)) {
return data;
}
}
return null;
}
function lookupOperationDocumentation(operationCode) {
if (!operationCode) {
return undefined;
}
return HORIZON_OPERATION_RESULT_CODE_DOCUMENTATION[operationCode];
}
function lookupTransactionDocumentation(transactionCode) {
if (!transactionCode) {
return undefined;
}
return HORIZON_TRANSACTION_RESULT_CODE_DOCUMENTATION[transactionCode];
}
function detailDocumentation(body) {
const detail = body.detail?.trim();
if (!detail || GENERIC_TRANSACTION_FAILED_DETAIL.test(detail)) {
return undefined;
}
return detail;
}
function extrasReasonDocumentation(body) {
const reason = body.extras?.reason?.trim();
if (!reason) {
return undefined;
}
const invalidField = body.extras?.invalid_field?.trim();
return invalidField ? `${reason} (field: ${invalidField})` : reason;
}
/**
* Best-effort human-readable summary for logs (`errorExtras.documentationSummary`).
* Prefers operation result codes, then transaction codes, then Horizon `detail` / `extras.reason`.
*/
function documentationSummaryFromHorizonBody(body) {
const transactionCode = body.extras?.result_codes?.transaction;
const operationCode = failingOperationCode(body.extras?.result_codes?.operations);
const operationSummary = lookupOperationDocumentation(operationCode);
if (operationSummary) {
return operationSummary;
}
const transactionSummary = lookupTransactionDocumentation(transactionCode);
if (transactionSummary && transactionCode !== 'tx_failed') {
return transactionSummary;
}
const detail = detailDocumentation(body);
if (detail) {
return detail;
}
const extrasReason = extrasReasonDocumentation(body);
if (extrasReason) {
return extrasReason;
}
if (transactionSummary) {
return transactionSummary;
}
const title = body.title?.trim();
if (title) {
return title;
}
return 'Unknown Horizon error.';
}
//# sourceMappingURL=horizonErrorBody.js.map