diff --git a/README.md b/README.md index 7f48190..fce3640 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ Now that you have your library up-to-date and added to your project, you can imp ```js import { VotesClient } from "soroban-governor-js-sdk"; -let rpc = new SorobanRpc.Server("rpcUrl"); -let account = await rpc.getAccount("public key"); +let stellarRpc = new rpc.Server("rpcUrl"); +let account = await stellarRpc.getAccount("public key"); let votes = new VotesClient("Contract Address"); let { op, parser } = votes.depositFor({ @@ -28,7 +28,7 @@ let tx = new TransactionBuilder(account, { .build(); // Simulate -let sim_resp = await rpc.simulateTransaction(tx); +let sim_resp = await stellarRpc.simulateTransaction(tx); // Parse Response let result = ContractResult.fromSimulationResponse(sim_resp, parser); diff --git a/package-lock.json b/package-lock.json index da8fd62..6700ad3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@script3/soroban-governor-sdk", - "version": "1.2.1", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@script3/soroban-governor-sdk", - "version": "1.2.1", + "version": "1.3.0", "license": "MIT", "dependencies": { - "@stellar/stellar-sdk": "12.1.0", + "@stellar/stellar-sdk": "13.0.0", "buffer": "6.0.3" }, "devDependencies": { @@ -17,16 +17,18 @@ } }, "node_modules/@stellar/js-xdr": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.1.1.tgz", - "integrity": "sha512-3gnPjAz78htgqsNEDkEsKHKosV2BF2iZkoHCNxpmZwUxiPsw+2VaXMed8RRMe0rGk3d5GZe7RrSba8zV80J3Ag==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.1.2.tgz", + "integrity": "sha512-VVolPL5goVEIsvuGqDc5uiKxV03lzfWdvYg1KikvwheDmTBO68CKDji3bAZ/kppZrx5iTA8z3Ld5yuytcvhvOQ==", + "license": "Apache-2.0" }, "node_modules/@stellar/stellar-base": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-12.0.1.tgz", - "integrity": "sha512-g6c27MNsDgEdUmoNQJn7zCWoCY50WHt0OIIOq3PhWaJRtUaT++qs1Jpb8+1bny2GmhtfRGOfPUFSyQBuHT9Mvg==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-13.0.1.tgz", + "integrity": "sha512-Xbd12mc9Oj/130Tv0URmm3wXG77XMshZtZ2yNCjqX5ZbMD5IYpbBs3DVCteLU/4SLj/Fnmhh1dzhrQXnk4r+pQ==", + "license": "Apache-2.0", "dependencies": { - "@stellar/js-xdr": "^3.1.1", + "@stellar/js-xdr": "^3.1.2", "base32.js": "^0.1.0", "bignumber.js": "^9.1.2", "buffer": "^6.0.3", @@ -34,18 +36,20 @@ "tweetnacl": "^1.0.3" }, "optionalDependencies": { - "sodium-native": "^4.1.1" + "sodium-native": "^4.3.0" } }, "node_modules/@stellar/stellar-sdk": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-12.1.0.tgz", - "integrity": "sha512-Va0hu9SaPezmMbO5eMwL5D15Wrx1AGWRtxayUDRWV2Fr3ynY58mvCZS1vsgNQ4kE8MZe3nBVKv6T9Kzqwgx1PQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-13.0.0.tgz", + "integrity": "sha512-+wvmKi+XWwu27nLYTM17EgBdpbKohEkOfCIK4XKfsI4WpMXAqvnqSm98i9h5dAblNB+w8BJqzGs1JY0PtTGm4A==", + "license": "Apache-2.0", "dependencies": { - "@stellar/stellar-base": "^12.0.1", - "axios": "^1.7.2", + "@stellar/stellar-base": "^13.0.1", + "axios": "^1.7.7", "bignumber.js": "^9.1.2", "eventsource": "^2.0.2", + "feaxios": "^0.0.20", "randombytes": "^2.1.0", "toml": "^3.0.0", "urijs": "^1.19.1" @@ -54,12 +58,14 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -70,6 +76,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz", "integrity": "sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==", + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -96,6 +103,7 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", "engines": { "node": "*" } @@ -126,6 +134,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -137,6 +146,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -148,16 +158,26 @@ "node": ">=12.0.0" } }, + "node_modules/feaxios": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/feaxios/-/feaxios-0.0.20.tgz", + "integrity": "sha512-g3hm2YDNffNxA3Re3Hd8ahbpmDee9Fv1Pb1C/NoWsjY7mtD8nyNeJytUzn+DK0Hyl9o6HppeWOrtnqgmhOYfWA==", + "license": "MIT", + "dependencies": { + "is-retry-allowed": "^3.0.0" + } + }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -168,9 +188,10 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -201,12 +222,26 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-retry-allowed": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-3.0.0.tgz", + "integrity": "sha512-9xH0xvoggby+u0uGF7cZXdrutWiBiaFG8ZT4YFPXL8NzkyAwX3AKGLeFQLvzDpM430+nDFBZ1LHkie/8ocL06A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -215,6 +250,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -223,9 +259,10 @@ } }, "node_modules/node-gyp-build": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", - "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", "optional": true, "bin": { "node-gyp-build": "bin.js", @@ -236,7 +273,8 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" }, "node_modules/randombytes": { "version": "2.1.0", @@ -267,6 +305,7 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "license": "(MIT AND BSD-3-Clause)", "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -276,10 +315,10 @@ } }, "node_modules/sodium-native": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.1.1.tgz", - "integrity": "sha512-LXkAfRd4FHtkQS4X6g+nRcVaN7mWVNepV06phIsC6+IZFvGh1voW5TNQiQp2twVaMf05gZqQjuS+uWLM6gHhNQ==", - "hasInstallScript": true, + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.1.tgz", + "integrity": "sha512-YdP64gAdpIKHfL4ttuX4aIfjeunh9f+hNeQJpE9C8UMndB3zkgZ7YmmGT4J2+v6Ibyp6Wem8D1TcSrtdW0bqtg==", + "license": "MIT", "optional": true, "dependencies": { "node-gyp-build": "^4.8.0" @@ -292,7 +331,8 @@ "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" }, "node_modules/typescript": { "version": "5.3.3", diff --git a/package.json b/package.json index d860830..eb5b65f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "1.2.1", + "version": "1.3.0", "name": "@script3/soroban-governor-sdk", "description": "Javascript SDK for the Soroban Governor", "type": "module", @@ -29,7 +29,7 @@ "homepage": "https://github.com/script3/soroban-governor-js-sdk#readme", "dependencies": { "buffer": "6.0.3", - "@stellar/stellar-sdk": "12.1.0" + "@stellar/stellar-sdk": "13.0.0" }, "devDependencies": { "typescript": "5.3.3" diff --git a/src/contract_error.ts b/src/contract_error.ts deleted file mode 100644 index e9af61f..0000000 --- a/src/contract_error.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { SorobanRpc, xdr } from "@stellar/stellar-sdk"; -export class ContractError extends Error { - /** - * The type of the error - */ - public type: ContractErrorType; - - constructor(type: ContractErrorType, message: string) { - super(message); - this.type = type; - } -} - -export enum ContractErrorType { - UnknownError = -1000, - - // Transaction Submission Errors - txSorobanInvalid = -24, - txMalformed = -23, - txBadMinSeqAgeOrGap = -22, - txBadSponsorship = -21, - txFeeBumpInnerFailed = -20, - txNotSupported = -19, - txInternalError = -18, - txBadAuthExtra = -17, - txInsufficientFee = -16, - txNoAccount = -15, - txInsufficientBalance = -14, - txBadAuth = -13, - txBadSeq = -12, - txMissingOperation = -11, - txTooLate = -10, - txTooEarly = -9, - - // Host Function Errors - InvokeHostFunctionInsufficientRefundableFee = -5, - InvokeHostFunctionEntryArchived = -4, - InvokeHostFunctionResourceLimitExceeded = -3, - InvokeHostFunctionTrapped = -2, - InvokeHostFunctionMalformed = -1, - - // Common Errors - InternalError = 1, - AlreadyInitializedError = 3, - - UnauthorizedError = 4, - - NegativeAmountError = 8, - BalanceError = 10, - OverflowError = 12, - - // Voter Token Errors - InsufficientVotesError = 100, - InvalidDelegateeError = 101, - InvalidCheckpointError = 102, - SequenceNotClosedError = 103, - InvalidEmissionConfigError = 104, - - // Governor Errors - InvalidSettingsError = 200, - NonExistentProposalError = 201, - ProposalClosedError = 202, - InvalidProposalSupportError = 203, - VotePeriodNotFinishedError = 204, - ProposalNotExecutableError = 205, - TimelockNotMetError = 206, - ProposalVotePeriodStartedError = 207, - InsufficientVotingUnitsError = 208, - AlreadyVotedError = 209, - InvalidProposalType = 210, - ProposalAlreadyOpenError = 211, - OutsideOfVotePeriodError = 212, - InvalidProposalActionError = 213, -} - -export function parseError( - errorResult: - | xdr.TransactionResult - | SorobanRpc.Api.SimulateTransactionErrorResponse -): ContractError { - if ("id" in errorResult) { - // Transaction simulation failed - errorResult = - errorResult as SorobanRpc.Api.SimulateTransactionErrorResponse; - - const match = errorResult.error.match(/Error\(Contract, #(\d+)\)/); - if (match) { - let errorValue = parseInt(match[1], 10); - if (errorValue in ContractErrorType) - return new ContractError( - errorValue as ContractErrorType, - errorResult.error - ); - } - return new ContractError(ContractErrorType.UnknownError, errorResult.error); - } else { - // Transaction submission failed - const txErrorName = errorResult.result().switch().name; - - // Use invokeHostFunctionErrors in case of generic `txFailed` error - if (txErrorName == "txFailed") { - // Transaction should only contain one operation - if (errorResult.result().results().length == 1) { - const hostFunctionError = errorResult - .result() - .results()[0] - .tr() - .invokeHostFunctionResult() - .switch().value; - if (hostFunctionError in ContractErrorType) - return new ContractError( - hostFunctionError as ContractErrorType, - JSON.stringify(errorResult, null, 2) - ); - } - } - - // Shift the error value to avoid collision with invokeHostFunctionErrors - const txErrorValue = errorResult.result().switch().value - 7; - // Use TransactionResultCode with more specific errors - if (txErrorValue in ContractErrorType) { - return new ContractError( - txErrorValue as ContractErrorType, - JSON.stringify(errorResult, null, 2) - ); - } - - // If the error is not recognized, return an unknown error - return new ContractError( - ContractErrorType.UnknownError, - JSON.stringify(errorResult, null, 2) - ); - } -} diff --git a/src/contract_result.ts b/src/contract_result.ts deleted file mode 100644 index 68785c8..0000000 --- a/src/contract_result.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { SorobanRpc, xdr } from "@stellar/stellar-sdk"; -import { - ContractError, - ContractErrorType, - parseError, -} from "./contract_error.js"; - -export interface Result { - unwrap(): T; - unwrapErr(): E; - isOk(): boolean; - isErr(): boolean; -} - -export class Ok implements Result { - constructor(readonly value: T) {} - unwrapErr(): E { - throw new Error("No error"); - } - unwrap(): T { - return this.value as T; - } - - isOk(): boolean { - return true; - } - - isErr(): boolean { - return !this.isOk(); - } -} - -export class Err - implements Result -{ - constructor(readonly error: E) {} - unwrapErr(): E { - return this.error; - } - unwrap(): never { - throw new Error(this.error.message); - } - - isOk(): boolean { - return false; - } - - isErr(): boolean { - return !this.isOk(); - } -} -export class Resources { - fee: number; - refundableFee: number; - cpuInst: number; - readBytes: number; - writeBytes: number; - readOnlyEntries: number; - readWriteEntries: number; - - constructor( - fee: number, - refundableFee: number, - cpuInst: number, - readBytes: number, - writeBytes: number, - readOnlyEntries: number, - readWriteEntries: number - ) { - this.fee = fee; - this.refundableFee = refundableFee; - this.cpuInst = cpuInst; - this.readBytes = readBytes; - this.writeBytes = writeBytes; - this.readOnlyEntries = readOnlyEntries; - this.readWriteEntries = readWriteEntries; - } - - /** - * Builds a Resources object from TransactionEnvelope - * @returns - Resources - */ - static fromTransaction(tx: xdr.TransactionEnvelope | string): Resources { - if (typeof tx === "string") { - tx = xdr.TransactionEnvelope.fromXDR(tx, "base64"); - } - const transaction = tx.v1().tx(); - const data = transaction.ext().sorobanData(); - const sorobanResources = data.resources(); - const footprint = sorobanResources.footprint(); - - const fee = transaction.fee(); - const refundableFee = Number(data.resourceFee().toString()); - const cpuInst = sorobanResources.instructions(); - const readBytes = sorobanResources.readBytes(); - const writeBytes = sorobanResources.writeBytes(); - const readOnlyEntries = footprint.readOnly().length; - const readWriteEntries = footprint.readWrite().length; - - return new Resources( - fee, - refundableFee, - cpuInst, - readBytes, - writeBytes, - readOnlyEntries, - readWriteEntries - ); - } -} - -export class ContractResult { - result: Result; - hash: string; - resources: Resources; - - constructor(result?: Result) { - if (result) { - this.result = result; - } - } - static fromSimulationResponse( - simulation: SorobanRpc.Api.SimulateTransactionResponse, - hash: string, - resources: Resources, - parser: (xdr: string) => T - ): ContractResult { - let result = new ContractResult(); - result.hash = hash; - result.resources = resources; - if (SorobanRpc.Api.isSimulationError(simulation)) { - result.result = new Err(parseError(simulation)); - } else if (SorobanRpc.Api.isSimulationRestore(simulation)) { - result.result = new Err( - new ContractError( - ContractErrorType.InvokeHostFunctionEntryArchived, - JSON.stringify(simulation.restorePreamble) - ) - ); - } else { - if (!simulation.result) { - result.result = new Err( - new ContractError( - ContractErrorType.UnknownError, - "Expected an invocation simulation, but got no 'result' field." - ) - ); - } else { - result.result = new Ok( - parser(simulation.result.retval!.toXDR("base64")) - ); - } - } - return result; - } - - static fromTransactionResponse( - response: SorobanRpc.Api.GetTransactionResponse, - hash: string, - resources: Resources, - parser: (xdr: string) => T - ): ContractResult { - let result = new ContractResult(); - result.hash = hash; - result.resources = resources; - if (response.status === SorobanRpc.Api.GetTransactionStatus.SUCCESS) { - // getTransactionResponse has a `returnValue` field unless it failed - if ("returnValue" in response) { - result.result = new Ok(parser(response.returnValue!.toXDR("base64"))); - } - // if "returnValue" not present, the transaction failed; return without parsing the result - else { - result.result = new Ok(undefined as T); - } - } else if ( - response.status === SorobanRpc.Api.GetTransactionStatus.NOT_FOUND - ) { - result.result = new Err( - new ContractError( - ContractErrorType.UnknownError, - "Transaction failed! Unable to find tx." - ) - ); - } else { - result.result = new Err(parseError(response.resultXdr)); - } - return result; - } -} diff --git a/src/index.ts b/src/index.ts index 5d54ecc..5739f61 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,6 @@ export * from "./calldata_utils.js"; -export * from "./contract_error.js"; -export * from "./contract_result.js"; export * from "./governor.js"; +export * from "./response_parser.js"; export * from "./votes.js"; export type u32 = number; export type i32 = number; diff --git a/src/response_parser.ts b/src/response_parser.ts new file mode 100644 index 0000000..d8550fc --- /dev/null +++ b/src/response_parser.ts @@ -0,0 +1,173 @@ +import { rpc } from "@stellar/stellar-sdk"; + +export class ContractError extends Error { + /** + * The type of the error + */ + public type: ContractErrorType; + + constructor(type: ContractErrorType) { + super(); + this.type = type; + } +} + +export enum ContractErrorType { + UnknownError = -1000, + txNotFound = -100, + + // Transaction Submission Errors + txSorobanInvalid = -24, + txMalformed = -23, + txBadMinSeqAgeOrGap = -22, + txBadSponsorship = -21, + txFeeBumpInnerFailed = -20, + txNotSupported = -19, + txInternalError = -18, + txBadAuthExtra = -17, + txInsufficientFee = -16, + txNoAccount = -15, + txInsufficientBalance = -14, + txBadAuth = -13, + txBadSeq = -12, + txMissingOperation = -11, + txTooLate = -10, + txTooEarly = -9, + + // Host Function Errors + InvokeHostFunctionInsufficientRefundableFee = -5, + InvokeHostFunctionEntryArchived = -4, + InvokeHostFunctionResourceLimitExceeded = -3, + InvokeHostFunctionTrapped = -2, + InvokeHostFunctionMalformed = -1, + + // Common Errors + InternalError = 1, + AlreadyInitializedError = 3, + + UnauthorizedError = 4, + + NegativeAmountError = 8, + BalanceError = 10, + OverflowError = 12, + + // Voter Token Errors + InsufficientVotesError = 100, + InvalidDelegateeError = 101, + InvalidCheckpointError = 102, + SequenceNotClosedError = 103, + InvalidEmissionConfigError = 104, + + // Governor Errors + InvalidSettingsError = 200, + NonExistentProposalError = 201, + ProposalClosedError = 202, + InvalidProposalSupportError = 203, + VotePeriodNotFinishedError = 204, + ProposalNotExecutableError = 205, + TimelockNotMetError = 206, + ProposalVotePeriodStartedError = 207, + InsufficientVotingUnitsError = 208, + AlreadyVotedError = 209, + InvalidProposalType = 210, + ProposalAlreadyOpenError = 211, + OutsideOfVotePeriodError = 212, + InvalidProposalActionError = 213, +} + +export function parseError( + errorResponse: + | rpc.Api.GetFailedTransactionResponse + | rpc.Api.SendTransactionResponse + | rpc.Api.SimulateTransactionErrorResponse + | rpc.Api.GetMissingTransactionResponse +): ContractError { + // Simulation Error + if ("id" in errorResponse) { + const match = errorResponse.error.match(/Error\(Contract, #(\d+)\)/); + if (match) { + const errorValue = parseInt(match[1], 10); + if (errorValue in ContractErrorType) + return new ContractError(errorValue as ContractErrorType); + } + return new ContractError(ContractErrorType.UnknownError); + } + + // Missing Transaction Error + if (errorResponse.status === "NOT_FOUND") { + return new ContractError(ContractErrorType.txNotFound); + } + + // Send Transaction Error + if ( + "errorResult" in errorResponse && + errorResponse.errorResult !== undefined + ) { + const txErrorName = errorResponse.errorResult.result().switch().name; + if (txErrorName == "txFailed") { + // Transaction should only contain one operation + if (errorResponse.errorResult.result().results().length == 1) { + const hostFunctionError = errorResponse.errorResult + .result() + .results()[0] + .tr() + .invokeHostFunctionResult() + .switch().value; + if (hostFunctionError in ContractErrorType) + return new ContractError(hostFunctionError as ContractErrorType); + } + } else { + const txErrorValue = + errorResponse.errorResult.result().switch().value - 7; + if (txErrorValue in ContractErrorType) { + return new ContractError(txErrorValue as ContractErrorType); + } + } + } + + // Get Transaction Error + if ("resultXdr" in errorResponse) { + // Transaction submission failed + const txResult = errorResponse.resultXdr.result(); + const txErrorName = txResult.switch().name; + + // Use invokeHostFunctionErrors in case of generic `txFailed` error + if (txErrorName == "txFailed") { + // Transaction should only contain one operation + if (errorResponse.resultXdr.result().results().length == 1) { + const hostFunctionError = txResult + .results()[0] + .tr() + .invokeHostFunctionResult() + .switch().value; + if (hostFunctionError in ContractErrorType) + return new ContractError(hostFunctionError as ContractErrorType); + } + } + + // Shift the error value to avoid collision with invokeHostFunctionErrors + const txErrorValue = txResult.switch().value - 7; + // Use TransactionResultCode with more specific errors + if (txErrorValue in ContractErrorType) { + return new ContractError(txErrorValue as ContractErrorType); + } + } + + // If the error is not recognized, return an unknown error + return new ContractError(ContractErrorType.UnknownError); +} + +export function parseResult( + response: + | rpc.Api.SimulateTransactionSuccessResponse + | rpc.Api.GetSuccessfulTransactionResponse, + parser: (xdr: string) => T +): T | undefined { + if ("result" in response && response.result !== undefined) { + return parser(response.result.retval.toXDR("base64")); + } else if ("returnValue" in response && response.returnValue) { + return parser(response.returnValue.toXDR("base64")); + } else { + return undefined; + } +} diff --git a/tsconfig.json b/tsconfig.json index efd4c61..c93d408 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,95 +1,11 @@ { "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ "module": "ESNext", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ "outDir": "./dist", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - // "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - // "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - /* Type Checking */ - // "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, "include": [