Skip to content

Commit b9f4949

Browse files
authored
PKG -- [fcl-core] Add TransactionError type to better expose execution errors (#1893)
1 parent b84b34e commit b9f4949

File tree

9 files changed

+202
-9
lines changed

9 files changed

+202
-9
lines changed

.changeset/pink-students-divide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@onflow/typedefs": minor
3+
---
4+
5+
Add missing field to TransactionStatus type

.changeset/pre.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,5 @@
2525
"@onflow/util-template": "1.2.2",
2626
"@onflow/util-uid": "1.2.2"
2727
},
28-
"changesets": [
29-
"slow-lies-fix",
30-
"tasty-ducks-mix"
31-
]
28+
"changesets": ["slow-lies-fix", "tasty-ducks-mix"]
3229
}

.changeset/soft-tomatoes-brake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@onflow/typedefs": minor
3+
---
4+
5+
Add FvmErrorCode enum for categorizing transaction/script execution errors

.changeset/witty-pants-argue.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@onflow/fcl-core": minor
3+
---
4+
5+
Add custom error `TransactionError` type for failing transaction results

packages/fcl-core/src/transaction/index.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import {send as fclSend, decode, getTransactionStatus} from "@onflow/sdk"
1313
import {HTTPRequestError} from "@onflow/transport-http"
1414
import {grpc} from "@improbable-eng/grpc-web"
15+
import {TransactionError, parseTransactionErrorCode} from "./transaction-error"
1516

1617
const TXID_REGEXP = /^[0-9a-fA-F]{64}$/
1718

@@ -149,9 +150,19 @@ export function transaction(
149150
const suppress = opts.suppress || false
150151
return new Promise((resolve, reject) => {
151152
const unsub = subscribe((txStatus, error) => {
152-
if ((error || txStatus.statusCode) && !suppress) {
153-
reject(error || txStatus.errorMessage)
154-
unsub()
153+
if (!suppress) {
154+
if (error != null) {
155+
reject(error)
156+
unsub()
157+
return
158+
} else if (txStatus.statusCode === 1) {
159+
const transactionError = TransactionError.fromErrorMessage(
160+
txStatus.errorMessage
161+
)
162+
reject(transactionError)
163+
unsub()
164+
return
165+
}
155166
} else if (predicate(txStatus)) {
156167
resolve(txStatus)
157168
unsub()
@@ -176,3 +187,5 @@ transaction.isFinalized = isFinalized
176187
transaction.isExecuted = isExecuted
177188
transaction.isSealed = isSealed
178189
transaction.isExpired = isExpired
190+
191+
export {TransactionError}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {FvmErrorCode} from "@onflow/typedefs"
2+
import {TransactionError} from "./transaction-error"
3+
4+
describe("TransactionError", () => {
5+
describe("fromErrorMessage", () => {
6+
test("returns unknown error if no code exists", () => {
7+
const errorMessage = "Transaction rejected by the network"
8+
const error = TransactionError.fromErrorMessage(errorMessage)
9+
expect(error).toBeInstanceOf(TransactionError)
10+
expect(error.code).toEqual(FvmErrorCode.UNKNOWN_ERROR)
11+
expect(error.type).toEqual("UNKNOWN_ERROR")
12+
})
13+
14+
test("parses transaction error with code from status", () => {
15+
const errorMessage = "[Error Code: 1101] Some Cadence Error"
16+
const error = TransactionError.fromErrorMessage(errorMessage)
17+
expect(error).toBeInstanceOf(TransactionError)
18+
expect(error.code).toEqual(FvmErrorCode.CADENCE_RUNTIME_ERROR)
19+
expect(error.type).toEqual("CADENCE_RUNTIME_ERROR")
20+
})
21+
22+
test("uses first instance of error code in message", () => {
23+
const errorMessage =
24+
"[Error Code: 1102] Unsupported value... [Error Code: 1105] Something else to say"
25+
const error = TransactionError.fromErrorMessage(errorMessage)
26+
expect(error).toBeInstanceOf(TransactionError)
27+
expect(error.code).toEqual(FvmErrorCode.ENCODING_UNSUPPORTED_VALUE)
28+
expect(error.type).toEqual("ENCODING_UNSUPPORTED_VALUE")
29+
})
30+
31+
test("allows leading text before error code", () => {
32+
const errorMessage =
33+
"This is a message [Error Code: 1102] Unsupported value"
34+
const error = TransactionError.fromErrorMessage(errorMessage)
35+
expect(error).toBeInstanceOf(TransactionError)
36+
expect(error.code).toEqual(FvmErrorCode.ENCODING_UNSUPPORTED_VALUE)
37+
expect(error.type).toEqual("ENCODING_UNSUPPORTED_VALUE")
38+
})
39+
40+
test("returns unknown error for missing error message", () => {
41+
const errorMessage = ""
42+
const error = TransactionError.fromErrorMessage(errorMessage)
43+
expect(error).toBeInstanceOf(TransactionError)
44+
expect(error.code).toEqual(FvmErrorCode.UNKNOWN_ERROR)
45+
expect(error.type).toEqual("UNKNOWN_ERROR")
46+
})
47+
})
48+
})
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import {FvmErrorCode} from "@onflow/typedefs"
2+
3+
const ERROR_CODE_REGEX = /\[Error Code: (\d+)\]/
4+
5+
export class TransactionError extends Error {
6+
public code: FvmErrorCode
7+
public type: string
8+
9+
private constructor(message: string, code: FvmErrorCode) {
10+
super(message)
11+
this.code = code
12+
this.type = FvmErrorCode[code]
13+
}
14+
15+
static fromErrorMessage(errorMessage: string): TransactionError {
16+
const match = errorMessage.match(ERROR_CODE_REGEX)
17+
const code = match ? parseInt(match[1], 10) : undefined
18+
19+
return new TransactionError(
20+
errorMessage,
21+
code || FvmErrorCode.UNKNOWN_ERROR
22+
)
23+
}
24+
}

packages/typedefs/src/fvm-errors.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
export enum FvmErrorCode {
2+
// We use -1 for unknown error in FCL because FVM defines error codes as uint16
3+
// This means we have no risk of collision with FVM error codes
4+
UNKNOWN_ERROR = -1,
5+
// tx validation errors 1000 - 1049
6+
// Deprecated: no longer in use
7+
TX_VALIDATION_ERROR = 1000,
8+
// Deprecated: No longer used.
9+
INVALID_TX_BYTE_SIZE_ERROR = 1001,
10+
// Deprecated: No longer used.
11+
INVALID_REFERENCE_BLOCK_ERROR = 1002,
12+
// Deprecated: No longer used.
13+
EXPIRED_TRANSACTION_ERROR = 1003,
14+
// Deprecated: No longer used.
15+
INVALID_SCRIPT_ERROR = 1004,
16+
// Deprecated: No longer used.
17+
INVALID_GAS_LIMIT_ERROR = 1005,
18+
INVALID_PROPOSAL_SIGNATURE_ERROR = 1006,
19+
INVALID_PROPOSAL_SEQ_NUMBER_ERROR = 1007,
20+
INVALID_PAYLOAD_SIGNATURE_ERROR = 1008,
21+
INVALID_ENVELOPE_SIGNATURE_ERROR = 1009,
22+
23+
// base errors 1050 - 1100
24+
// Deprecated: No longer used.
25+
FVM_INTERNAL_ERROR = 1050,
26+
VALUE_ERROR = 1051,
27+
INVALID_ARGUMENT_ERROR = 1052,
28+
INVALID_ADDRESS_ERROR = 1053,
29+
INVALID_LOCATION_ERROR = 1054,
30+
ACCOUNT_AUTHORIZATION_ERROR = 1055,
31+
OPERATION_AUTHORIZATION_ERROR = 1056,
32+
OPERATION_NOT_SUPPORTED_ERROR = 1057,
33+
BLOCK_HEIGHT_OUT_OF_RANGE_ERROR = 1058,
34+
35+
// execution errors 1100 - 1200
36+
// Deprecated: No longer used.
37+
EXECUTION_ERROR = 1100,
38+
CADENCE_RUNTIME_ERROR = 1101,
39+
// Deprecated: No longer used.
40+
ENCODING_UNSUPPORTED_VALUE = 1102,
41+
STORAGE_CAPACITY_EXCEEDED = 1103,
42+
// Deprecated: No longer used.
43+
GAS_LIMIT_EXCEEDED_ERROR = 1104,
44+
EVENT_LIMIT_EXCEEDED_ERROR = 1105,
45+
LEDGER_INTERACTION_LIMIT_EXCEEDED_ERROR = 1106,
46+
STATE_KEY_SIZE_LIMIT_ERROR = 1107,
47+
STATE_VALUE_SIZE_LIMIT_ERROR = 1108,
48+
TRANSACTION_FEE_DEDUCTION_FAILED_ERROR = 1109,
49+
COMPUTATION_LIMIT_EXCEEDED_ERROR = 1110,
50+
MEMORY_LIMIT_EXCEEDED_ERROR = 1111,
51+
COULD_NOT_DECODE_EXECUTION_PARAMETER_FROM_STATE = 1112,
52+
SCRIPT_EXECUTION_TIMED_OUT_ERROR = 1113,
53+
SCRIPT_EXECUTION_CANCELLED_ERROR = 1114,
54+
EVENT_ENCODING_ERROR = 1115,
55+
INVALID_INTERNAL_STATE_ACCESS_ERROR = 1116,
56+
// 1117 was never deployed and is free to use
57+
INSUFFICIENT_PAYER_BALANCE = 1118,
58+
59+
// accounts errors 1200 - 1250
60+
// Deprecated: No longer used.
61+
ACCOUNT_ERROR = 1200,
62+
ACCOUNT_NOT_FOUND_ERROR = 1201,
63+
ACCOUNT_PUBLIC_KEY_NOT_FOUND_ERROR = 1202,
64+
ACCOUNT_ALREADY_EXISTS_ERROR = 1203,
65+
// Deprecated: No longer used.
66+
FROZEN_ACCOUNT_ERROR = 1204,
67+
// Deprecated: No longer used.
68+
ACCOUNT_STORAGE_NOT_INITIALIZED_ERROR = 1205,
69+
ACCOUNT_PUBLIC_KEY_LIMIT_ERROR = 1206,
70+
71+
// contract errors 1250 - 1300
72+
// Deprecated: No longer used.
73+
CONTRACT_ERROR = 1250,
74+
CONTRACT_NOT_FOUND_ERROR = 1251,
75+
// Deprecated: No longer used.
76+
CONTRACT_NAMES_NOT_FOUND_ERROR = 1252,
77+
78+
// fvm std lib errors 1300-1400
79+
EVM_EXECUTION_ERROR = 1300,
80+
}

packages/typedefs/src/index.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,13 +330,17 @@ export type TransactionStatus = {
330330
*/
331331
blockId: string
332332
/**
333-
* - The status code of the transaction.
333+
* - The execution status of the transaction
334334
*/
335-
status: number
335+
status: TransactionExecutionStatus
336336
/**
337337
* - The status as as descriptive text (e.g. "FINALIZED").
338338
*/
339339
statusString: string
340+
/**
341+
* - The result of the transaction, if executed (i.e. 0 for success, 1 for failure)
342+
*/
343+
statusCode: 0 | 1
340344
/**
341345
* - The error message of the transaction.
342346
*/
@@ -347,6 +351,17 @@ export type TransactionStatus = {
347351
events: Array<Event>
348352
}
349353
/**
354+
* The execution status of the transaction.
355+
*/
356+
export enum TransactionExecutionStatus {
357+
UNKNOWN = 0,
358+
PENDING = 1,
359+
FINALIZED = 2,
360+
EXECUTED = 3,
361+
SEALED = 4,
362+
EXPIRED = 5,
363+
}
364+
/*
350365
* The Provider type describes a Wallet Provider associated with a specific Service.
351366
*/
352367
export type Provider = {
@@ -443,3 +458,4 @@ export type EventStream = StreamConnection<{
443458
}>
444459

445460
export * from "./interaction"
461+
export * from "./fvm-errors"

0 commit comments

Comments
 (0)