Skip to content
Open
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
66 changes: 41 additions & 25 deletions src/features/multichain/routines/executors/pay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,6 @@ async function handleXRPLPay(
log.debug("[XMScript Parser] Ripple Pay: connected to the XRP network")

try {
// Validate signedPayloads exists and has at least one element
if (!operation.task.signedPayloads || operation.task.signedPayloads.length === 0) {
return {
result: "error",
error: `Missing signed payloads for XRPL operation (${operation.chain}.${operation.subchain})`,
}
}

const signedTx = operation.task.signedPayloads[0]

// Extract tx_blob - handle both string and object formats
Expand All @@ -261,11 +253,8 @@ async function handleXRPLPay(
// Submit transaction and wait for validation
const res = await xrplInstance.provider.submitAndWait(txBlob)

// Extract transaction result - handle different response formats
const meta = res.result.meta
const txResult = (typeof meta === "object" && meta !== null && "TransactionResult" in meta
? (meta as { TransactionResult: string }).TransactionResult
: (res.result as any).engine_result) as string | undefined
const meta = res.result.meta as any
const txResult = meta?.TransactionResult || (res.result as any).engine_result
const txHash = res.result.hash
const resultMessage = ((res.result as any).engine_result_message || "") as string

Expand All @@ -277,35 +266,62 @@ async function handleXRPLPay(
}
}

// XRPL transaction result code prefixes and their meanings
const xrplErrorMessages: Record<string, string> = {
tec: "Transaction failed (fee charged)", // tecUNFUNDED_PAYMENT, tecINSUF_FEE, tecPATH_DRY
tem: "Malformed transaction", // temREDUNDANT, temBAD_FEE, temINVALID
ter: "Transaction provisional/queued", // terQUEUED
tef: "Transaction rejected", // tefPAST_SEQ, tefMAX_LEDGER, tefFAILURE
// tec* codes: Transaction failed but fee was charged
// The transaction was applied to ledger but did not achieve its intended purpose
// Example: tecUNFUNDED_PAYMENT, tecINSUF_FEE, tecPATH_DRY
if (txResult?.startsWith('tec')) {
return {
result: "error",
error: `Transaction failed (fee charged): ${txResult} - ${resultMessage}`,
hash: txHash,
extra: { code: txResult, validated: res.result.validated }
}
}

// tem* codes: Malformed transaction (not applied to ledger)
// Example: temREDUNDANT (sending to self), temBAD_FEE, temINVALID
if (txResult?.startsWith('tem')) {
return {
result: "error",
error: `Malformed transaction: ${txResult} - ${resultMessage}`,
hash: txHash,
extra: { code: txResult, validated: res.result.validated }
}
}

const errorPrefix = txResult?.substring(0, 3)
if (errorPrefix && xrplErrorMessages[errorPrefix]) {
// ter* codes: Provisional/retryable result (not final)
// Example: terQUEUED (transaction queued for future ledger)
if (txResult?.startsWith('ter')) {
return {
result: "error",
error: `${xrplErrorMessages[errorPrefix]}: ${txResult} - ${resultMessage}`,
error: `Transaction provisional/queued: ${txResult} - ${resultMessage}`,
hash: txHash,
extra: { code: txResult, validated: res.result.validated },
extra: { code: txResult, validated: res.result.validated }
}
}
Comment on lines +292 to +301
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider differentiating ter* provisional results from actual errors.

The comment correctly notes that ter* codes are "provisional/retryable" and "not final," yet the code returns result: "error". For terQUEUED or similar codes, the transaction may still succeed in a future ledger. Treating this as an error could mislead callers into thinking the transaction definitively failed.

Consider returning a distinct status (e.g., result: "pending") or documenting that callers should handle ter* codes specially.


// tef* codes: Local failure (not applied to ledger)
// Example: tefPAST_SEQ, tefMAX_LEDGER, tefFAILURE
if (txResult?.startsWith('tef')) {
return {
result: "error",
error: `Transaction rejected: ${txResult} - ${resultMessage}`,
hash: txHash,
extra: { code: txResult, validated: res.result.validated }
}
}

return {
result: "error",
error: `Unknown transaction result: ${txResult} - ${resultMessage}`,
hash: txHash,
extra: { code: txResult, validated: res.result.validated },
extra: { code: txResult, validated: res.result.validated }
}
} catch (error) {
console.log("[XMScript Parser] Ripple Pay: error:", error)
return {
result: "error",
error: error instanceof Error ? error.message : String(error),
error: error.toString(),
}
}
}