@xoxno/sdk-js 1.0.223 → 1.0.225

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/cjs/sdk/stellar/__tests__/admin.test.d.ts +0 -12
  2. package/dist/cjs/sdk/stellar/__tests__/builders-hubasset.test.d.ts +0 -11
  3. package/dist/cjs/sdk/stellar/__tests__/governance.test.d.ts +0 -4
  4. package/dist/cjs/sdk/stellar/__tests__/lending.test.d.ts +0 -20
  5. package/dist/cjs/sdk/stellar/__tests__/strategy-program.test.d.ts +0 -8
  6. package/dist/cjs/sdk/stellar/__tests__/swap-history.test.d.ts +1 -0
  7. package/dist/cjs/sdk/stellar/index.d.ts +1 -0
  8. package/dist/cjs/sdk/stellar/swap-history.d.ts +25 -0
  9. package/dist/cjs/sdk/stellar/swap-index.d.ts +2 -0
  10. package/dist/cjs/sdk/stellar/xdr-compat.d.ts +11 -0
  11. package/dist/esm/index.js +3 -0
  12. package/dist/esm/sdk/index.js +198 -0
  13. package/dist/esm/sdk/stellar/admin.js +385 -0
  14. package/dist/esm/sdk/stellar/auth-policy.js +310 -0
  15. package/dist/esm/sdk/stellar/cctp.js +23 -0
  16. package/dist/esm/sdk/stellar/contracts.js +49 -0
  17. package/dist/esm/sdk/stellar/events/decode.js +415 -0
  18. package/dist/esm/sdk/stellar/events/id.js +62 -0
  19. package/dist/esm/sdk/stellar/events/index.js +2 -0
  20. package/dist/esm/sdk/stellar/governance.js +403 -0
  21. package/dist/esm/sdk/stellar/index.js +25 -0
  22. package/dist/esm/sdk/stellar/lending-api-types.js +3 -0
  23. package/dist/esm/sdk/stellar/lending-read-types.js +1 -0
  24. package/dist/esm/sdk/stellar/lending-read.js +341 -0
  25. package/dist/esm/sdk/stellar/lending.js +358 -0
  26. package/dist/esm/sdk/stellar/math/account.js +256 -0
  27. package/dist/esm/sdk/stellar/math/index.js +11 -0
  28. package/dist/esm/sdk/stellar/math/limits.js +273 -0
  29. package/dist/esm/sdk/stellar/math/projection.js +130 -0
  30. package/dist/esm/sdk/stellar/math/rates.js +62 -0
  31. package/dist/esm/sdk/stellar/math/scaled.js +156 -0
  32. package/dist/esm/sdk/stellar/position-mode.js +11 -0
  33. package/dist/esm/sdk/stellar/prepare.js +47 -0
  34. package/dist/esm/sdk/stellar/quote.js +87 -0
  35. package/dist/esm/sdk/stellar/repay-swap.js +12 -0
  36. package/dist/esm/sdk/stellar/route-verify.js +285 -0
  37. package/dist/esm/sdk/stellar/scval-encode.js +464 -0
  38. package/dist/esm/sdk/stellar/swap-history.js +46 -0
  39. package/dist/esm/sdk/stellar/swap-index.js +1 -0
  40. package/dist/esm/sdk/stellar/swap.js +117 -0
  41. package/dist/esm/sdk/stellar/xdr-compat.js +17 -0
  42. package/dist/esm/sdk/stellar-schema.js +129 -0
  43. package/dist/esm/sdk/swagger.js +1957 -0
  44. package/dist/esm/sdk/types.js +1 -0
  45. package/dist/esm/sdk/utils.js +21 -0
  46. package/dist/esm/utils/api.js +169 -0
  47. package/dist/esm/utils/const.js +11 -0
  48. package/dist/esm/utils/errors.js +20 -0
  49. package/dist/esm/utils/guards.js +29 -0
  50. package/dist/esm/utils/helpers.js +20 -0
  51. package/dist/esm/utils/regex.js +14 -0
  52. package/dist/esm/utils/soroban-errors.js +149 -0
  53. package/dist/index.cjs +1 -1
  54. package/dist/index.esm.js +1 -2
  55. package/dist/sdk/stellar/__tests__/admin.test.d.ts +0 -12
  56. package/dist/sdk/stellar/__tests__/builders-hubasset.test.d.ts +0 -11
  57. package/dist/sdk/stellar/__tests__/governance.test.d.ts +0 -4
  58. package/dist/sdk/stellar/__tests__/lending.test.d.ts +0 -20
  59. package/dist/sdk/stellar/__tests__/strategy-program.test.d.ts +0 -8
  60. package/dist/sdk/stellar/__tests__/swap-history.test.d.ts +1 -0
  61. package/dist/sdk/stellar/index.cjs +1 -1
  62. package/dist/sdk/stellar/index.d.ts +1 -0
  63. package/dist/sdk/stellar/index.esm.js +1 -1
  64. package/dist/sdk/stellar/swap-history.d.ts +25 -0
  65. package/dist/sdk/stellar/swap-index.cjs +2 -0
  66. package/dist/sdk/stellar/swap-index.d.ts +2 -0
  67. package/dist/sdk/stellar/swap-index.esm.js +2 -0
  68. package/dist/sdk/stellar/swap-index.esm.js.LICENSE.txt +8 -0
  69. package/dist/sdk/stellar/xdr-compat.d.ts +11 -0
  70. package/package.json +16 -5
  71. /package/dist/{index.esm.js.LICENSE.txt → sdk/stellar/swap-index.cjs.LICENSE.txt} +0 -0
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,21 @@
1
+ export const coveredMethods = [
2
+ 'PATCH',
3
+ 'POST',
4
+ 'DELETE',
5
+ 'PUT'
6
+ ];
7
+ const _nestTypes = [
8
+ 'string',
9
+ 'number',
10
+ 'boolean'
11
+ ];
12
+ const _returnTypes = [
13
+ 'application/json',
14
+ 'multipart/form-data'
15
+ ];
16
+ const _securityModes = [
17
+ 'requiredAny',
18
+ 'requiredWeb2',
19
+ 'requiredJwt',
20
+ 'optionalAny'
21
+ ];
@@ -0,0 +1,169 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
14
+ import { DR_SC, FM_SC, KG_SC, Manager_SC, Manager_SC_DEV, P2P_SC, P2P_SC_DEV, Staking_SC, Staking_SC_DEV, XOXNO_SC, XOXNO_SC_DEV } from "./const.js";
15
+ /**
16
+ * Default read deadline. Without one, a stalled response is bounded only by
17
+ * whatever the host runtime imposes (in Node, undici's Agent), which is
18
+ * typically longer than the caller's own budget — a Next.js `'use cache'` fill,
19
+ * for instance, gives up at `staticPageGenerationTimeout * 0.9` and turns the
20
+ * stall into a hard error instead of a fetch rejection the caller can degrade
21
+ * on. Generous enough that only a genuinely stuck read hits it.
22
+ */ export const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
23
+ export var Chain = /*#__PURE__*/ function(Chain) {
24
+ Chain["MAINNET"] = "1";
25
+ Chain["DEVNET"] = "D";
26
+ return Chain;
27
+ }({});
28
+ export class XOXNOClient {
29
+ /** Create a client bound to one application-selected API deployment. */ constructor(options){
30
+ _define_property(this, "apiUrl", void 0);
31
+ _define_property(this, "chain", void 0);
32
+ _define_property(this, "init", void 0);
33
+ _define_property(this, "config", void 0);
34
+ _define_property(this, "fetchWithTimeout", async (path, { params, ...options } = {})=>{
35
+ const { next, cache, debug, timeout, ...rest } = this.init;
36
+ const { next: overwriteNext, cache: overwriteCache, debug: overwriteDebug, timeout: overwriteTimeout, headers, method = 'GET', ...overwriteRest } = options;
37
+ const authHeader = headers?.Authorization;
38
+ const Authorization = authHeader === 'Bearer undefined' ? undefined : authHeader;
39
+ // A GET/HEAD has no body, so Content-Type describes nothing — but in a
40
+ // browser it is the difference between a "simple" CORS request and one
41
+ // that needs a preflight. `application/json` is not on the CORS-safelist,
42
+ // so setting it here made every unauthenticated GET cost an extra OPTIONS
43
+ // round trip to the origin (~180k/week, 17% of all API requests, ~200ms
44
+ // each, none of them cacheable at the edge).
45
+ //
46
+ // Authenticated calls still preflight — Authorization is not safelisted
47
+ // either — and that is unavoidable. This only stops charging public reads
48
+ // for a header that carries no meaning on a bodyless request.
49
+ const hasBody = method !== 'GET' && method !== 'HEAD' && method !== 'PUT';
50
+ const allHeaders = {
51
+ ...headers,
52
+ // Both are forbidden header names: browsers drop them silently, so they
53
+ // only ever apply to server-side (Node) callers.
54
+ Referer: 'https://xoxno.sdk',
55
+ 'User-Agent': 'XOXNO/1.0/SDK',
56
+ ...hasBody ? {
57
+ 'Content-Type': 'application/json'
58
+ } : {},
59
+ ...Authorization ? {
60
+ Authorization
61
+ } : {}
62
+ };
63
+ const query = Object.entries(params ?? {}).filter(([, value])=>value !== undefined).flatMap(([key, value])=>{
64
+ if (Array.isArray(value)) {
65
+ return value.filter((v)=>v !== undefined).map((v)=>`${key}=${encodeURIComponent(v)}`);
66
+ } else {
67
+ return `${key}=${encodeURIComponent(value)}`;
68
+ }
69
+ }).join('&');
70
+ const url = `${this.apiUrl}${path}${query.length ? `?${query}` : ''}`;
71
+ const { revalidate, ...other } = next ?? {};
72
+ const { revalidate: overwriteRevalidate, ...overwriteOther } = overwriteNext ?? {};
73
+ const finalRevalidate = method === 'GET' && !Authorization ? overwriteRevalidate ?? revalidate : undefined;
74
+ const finalCache = method === 'GET' && !Authorization && !finalRevalidate ? overwriteCache ?? cache : undefined;
75
+ // A body's duration is uplink-bound, so it is only deadlined when the caller
76
+ // asks for it; a read is not.
77
+ const body = overwriteRest.body;
78
+ const effectiveTimeout = overwriteTimeout ?? timeout ?? (body == null ? DEFAULT_REQUEST_TIMEOUT_MS : 0);
79
+ const deadline = effectiveTimeout > 0 ? AbortSignal.timeout(effectiveTimeout) : undefined;
80
+ const callerSignal = overwriteRest.signal ?? rest.signal;
81
+ const signal = deadline && callerSignal ? AbortSignal.any([
82
+ callerSignal,
83
+ deadline
84
+ ]) : deadline ?? callerSignal;
85
+ const init = {
86
+ ...rest,
87
+ ...overwriteRest,
88
+ method,
89
+ ...Object.keys(allHeaders).length ? {
90
+ headers: allHeaders
91
+ } : {},
92
+ ...signal ? {
93
+ signal
94
+ } : {},
95
+ cache: finalCache,
96
+ next: {
97
+ ...other,
98
+ ...overwriteOther,
99
+ revalidate: finalRevalidate
100
+ }
101
+ };
102
+ if (overwriteDebug ?? debug) {
103
+ console.debug('SDK fetch: ', url, init);
104
+ }
105
+ const res = await fetch(url, init).catch((error)=>{
106
+ if (error instanceof Error && !error.message.match(/^http(s?):\/\//)) {
107
+ // Preserve the underlying network reason as `cause` — the undici/system
108
+ // error carries the real code (UND_ERR_SOCKET, ECONNREFUSED, ENOTFOUND,
109
+ // timeouts). Without it the thrown message is only "<url>: fetch failed".
110
+ throw Object.assign(new Error(`${url}: ${error.message}`), {
111
+ cause: error
112
+ });
113
+ }
114
+ throw error;
115
+ });
116
+ const text = await res.text();
117
+ if (!res.ok) {
118
+ let message;
119
+ try {
120
+ message = JSON.parse(text);
121
+ } catch {
122
+ message = {
123
+ message: text
124
+ };
125
+ }
126
+ const errorMessage = [
127
+ url,
128
+ res.status,
129
+ res.statusText,
130
+ message.message
131
+ ].filter(Boolean).join(';;');
132
+ throw new Error(errorMessage);
133
+ }
134
+ try {
135
+ return JSON.parse(text);
136
+ } catch {
137
+ return text;
138
+ }
139
+ });
140
+ if (!options?.apiUrl?.trim()) {
141
+ throw new Error('XOXNOClient: apiUrl is required');
142
+ }
143
+ const { chain = "1", apiUrl, ...init } = options;
144
+ this.apiUrl = apiUrl.trim().replace(/\/$/, '');
145
+ this.chain = chain;
146
+ this.init = init;
147
+ this.config = chain == "1" ? {
148
+ mediaUrl: 'https://media.xoxno.com',
149
+ gatewayUrl: 'https://gateway.xoxno.com',
150
+ XO_SC: XOXNO_SC,
151
+ FM_SC: FM_SC,
152
+ DR_SC,
153
+ KG_SC,
154
+ Staking_SC,
155
+ Manager_SC,
156
+ P2P_SC
157
+ } : {
158
+ mediaUrl: 'https://devnet-media.xoxno.com',
159
+ gatewayUrl: 'https://devnet-gateway.xoxno.com',
160
+ XO_SC: XOXNO_SC_DEV,
161
+ FM_SC,
162
+ DR_SC,
163
+ KG_SC,
164
+ Staking_SC: Staking_SC_DEV,
165
+ Manager_SC: Manager_SC_DEV,
166
+ P2P_SC: P2P_SC_DEV
167
+ };
168
+ }
169
+ }
@@ -0,0 +1,11 @@
1
+ export const XOXNO_SC = 'erd1qqqqqqqqqqqqqpgq6wegs2xkypfpync8mn2sa5cmpqjlvrhwz5nqgepyg8';
2
+ export const FM_SC = 'erd1qqqqqqqqqqqqqpgq705fxpfrjne0tl3ece0rrspykq88mynn4kxs2cg43s';
3
+ export const DR_SC = 'erd1qqqqqqqqqqqqqpgqd9rvv2n378e27jcts8vfwynpx0gfl5ufz6hqhfy0u0';
4
+ export const KG_SC = 'erd1qqqqqqqqqqqqqpgq8xwzu82v8ex3h4ayl5lsvxqxnhecpwyvwe0sf2qj4e';
5
+ export const Staking_SC = 'erd1qqqqqqqqqqqqqpgqvpkd3g3uwludduv3797j54qt6c888wa59w2shntt6z';
6
+ export const Manager_SC = 'erd1qqqqqqqqqqqqqpgqg9fa0dmpn8fu3fnleeqn5zt8rl8mdqjkys5s2gtas7';
7
+ export const P2P_SC = 'erd1qqqqqqqqqqqqqpgq47y8hnct68v6asjv6gxem6h9rumn9frzah0skhxxt6';
8
+ export const XOXNO_SC_DEV = 'erd1qqqqqqqqqqqqqpgql0dnz6n5hpuw8cptlt00khd0nn4ja8eadsfq2xrqw4';
9
+ export const Staking_SC_DEV = 'erd1qqqqqqqqqqqqqpgqsc5hnewwpep8qq0d7kjjzrquapuucrygah0s85zres';
10
+ export const Manager_SC_DEV = 'erd1qqqqqqqqqqqqqpgqluclyhfsa2uw7q9cjwd8knk989hg57u8ah0slq2nlr';
11
+ export const P2P_SC_DEV = 'erd1qqqqqqqqqqqqqpgqfja7ukpngrun78ueq583l0vd6aj4ekhrah0sa9wyrv';
@@ -0,0 +1,20 @@
1
+ export class CollectionNotFoundError extends Error {
2
+ constructor(item){
3
+ super(`Invalid collection ticker: ${item}`);
4
+ }
5
+ }
6
+ export class AddressNotFoundError extends Error {
7
+ constructor(item){
8
+ super(`Invalid address: ${item}`);
9
+ }
10
+ }
11
+ export class NFTNotFoundError extends Error {
12
+ constructor(item){
13
+ super(`Invalid NFT identifier: ${item}`);
14
+ }
15
+ }
16
+ export class PaginatedTopError extends Error {
17
+ constructor(top){
18
+ super(`Top cannot be greater than 100, found ${top}`);
19
+ }
20
+ }
@@ -0,0 +1,29 @@
1
+ import { AddressNotFoundError, CollectionNotFoundError, NFTNotFoundError, PaginatedTopError } from "./errors.js";
2
+ import { isValidCollectionTicker, isValidNftIdentifier } from "./regex.js";
3
+ export function collectionGuard(collection, callback) {
4
+ if (!isValidCollectionTicker(collection)) {
5
+ throw new CollectionNotFoundError(collection);
6
+ }
7
+ return callback;
8
+ }
9
+ export function addressGuard(address, callback) {
10
+ if (!isValidCollectionTicker(address)) {
11
+ throw new AddressNotFoundError(address);
12
+ }
13
+ return callback;
14
+ }
15
+ export function nftGuard(identifier, callback) {
16
+ if (!isValidNftIdentifier(identifier)) {
17
+ throw new NFTNotFoundError(identifier);
18
+ }
19
+ return callback;
20
+ }
21
+ export function collectionGuardOnly(collection) {
22
+ return collectionGuard(collection, Promise.resolve());
23
+ }
24
+ export function paginatedGuard(filter, callback) {
25
+ if (filter.top && filter.top > 100) {
26
+ throw new PaginatedTopError(filter.top);
27
+ }
28
+ return callback(JSON.stringify(filter));
29
+ }
@@ -0,0 +1,20 @@
1
+ export const nonceToHex = (nonce)=>{
2
+ let nonceHex = nonce.toString(16);
3
+ if (nonceHex.length % 2 !== 0) {
4
+ nonceHex = '0' + nonceHex;
5
+ }
6
+ return nonceHex;
7
+ };
8
+ export const getIdentifierFromColAndNonce = (collection, nonce)=>{
9
+ return [
10
+ collection,
11
+ nonceToHex(nonce)
12
+ ].join('-');
13
+ };
14
+ export const isAddressValid = (address)=>{
15
+ return address ? address.startsWith('erd1') && address.length === 62 || /^0x[a-fA-F0-9]{40,64}$/.test(address) || /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address) || // Stellar strkey: `G` = ed25519 account, `C` = Soroban contract.
16
+ // Contracts hold balances, positions and profiles just like accounts,
17
+ // and the route builder runs every `:address` param through here — so
18
+ // rejecting `C` blocked all contract-account endpoints client-side.
19
+ /^[GC][A-Z2-7]{55}$/.test(address) : false;
20
+ };
@@ -0,0 +1,14 @@
1
+ export const isValidCollectionTicker = (ticker)=>{
2
+ const isSuiCollection = /* (ticker.length >= 4 && ticker.length <= 40) || */ /^0x[a-fA-F0-9]{1,64}-[a-zA-Z0-9_]+-[a-zA-Z0-9_]+(<.+>)?$/.test(decodeURIComponent(ticker));
3
+ const isXoxnoCollection = /^[A-Z0-9]{3,10}-[a-z0-9]{6}$/.test(ticker);
4
+ // Stellar: the collection IS the NFT contract (Soroban contract strkey)
5
+ const isStellarCollection = /^C[A-Z2-7]{55}$/.test(ticker);
6
+ return isSuiCollection || isXoxnoCollection || isStellarCollection;
7
+ };
8
+ export const isValidNftIdentifier = (identifier)=>{
9
+ const isSuiNft = /^0x[a-fA-F0-9]{1,64}$/.test(identifier);
10
+ const isXoxnoNft = /^[A-Za-z0-9]{3,10}-[A-Za-z0-9]{6}-[A-Za-z0-9]{2,7}(?:-\d+(?:-[A-Za-z0-9]+)?)?$/.test(identifier);
11
+ // Stellar: `{contract strkey}-{decimal token id}`
12
+ const isStellarNft = /^C[A-Z2-7]{55}-\d+$/.test(identifier);
13
+ return isSuiNft || isXoxnoNft || isStellarNft;
14
+ };
@@ -0,0 +1,149 @@
1
+ /**
2
+ * rs-lending-xlm Soroban contract error codes → human-readable messages.
3
+ * Mirrors `common/src/errors.rs` (`GenericError`, `CollateralError`,
4
+ * `SpokeError`, `OracleError`). Keep in sync when that file changes.
5
+ */ const SOROBAN_ERROR_CATALOG = {
6
+ // GenericError
7
+ 1: {
8
+ name: 'AssetNotSupported',
9
+ message: 'This asset is not supported by the protocol.'
10
+ },
11
+ 2: {
12
+ name: 'AssetAlreadySupported',
13
+ message: 'This asset is already supported.'
14
+ },
15
+ 3: {
16
+ name: 'InvalidTicker',
17
+ message: 'Invalid asset ticker.'
18
+ },
19
+ 6: {
20
+ name: 'InvalidAsset',
21
+ message: 'Invalid asset address.'
22
+ },
23
+ 10: {
24
+ name: 'InvalidWasmHash',
25
+ message: 'The WASM hash is empty or invalid.'
26
+ },
27
+ 11: {
28
+ name: 'InvalidExchangeSrc',
29
+ message: 'The oracle configuration is invalid.'
30
+ },
31
+ 12: {
32
+ name: 'PairNotActive',
33
+ message: 'No oracle is configured for this asset.'
34
+ },
35
+ 14: {
36
+ name: 'AmountMustBePositive',
37
+ message: 'The amount must be greater than zero.'
38
+ },
39
+ 32: {
40
+ name: 'OwnerNotSet',
41
+ message: 'The contract owner has not been set.'
42
+ },
43
+ 39: {
44
+ name: 'InvalidTimelockDelay',
45
+ message: 'The timelock delay is out of the allowed range.'
46
+ },
47
+ 41: {
48
+ name: 'InvalidRole',
49
+ message: 'The caller does not hold the required role.'
50
+ },
51
+ 43: {
52
+ name: 'HubNotActive',
53
+ message: 'This hub is not active.'
54
+ },
55
+ 44: {
56
+ name: 'NotAuthorized',
57
+ message: 'The caller is not authorized to perform this action.'
58
+ },
59
+ 46: {
60
+ name: 'OperationNotCancellable',
61
+ message: 'This governance operation cannot be cancelled.'
62
+ },
63
+ // CollateralError
64
+ 113: {
65
+ name: 'InvalidLiqThreshold',
66
+ message: 'LTV, liquidation threshold, or bonus is out of bounds.'
67
+ },
68
+ 116: {
69
+ name: 'InvalidBorrowParams',
70
+ message: 'Supply or borrow cap configuration is invalid.'
71
+ },
72
+ 117: {
73
+ name: 'InvalidUtilRange',
74
+ message: 'Interest-rate utilization breakpoints are invalid.'
75
+ },
76
+ 132: {
77
+ name: 'AssetDecimalsTooHigh',
78
+ message: 'Asset decimals exceed the protocol maximum (27).'
79
+ },
80
+ // SpokeError
81
+ 300: {
82
+ name: 'SpokeNotFound',
83
+ message: 'This spoke does not exist.'
84
+ },
85
+ 301: {
86
+ name: 'SpokeDeprecated',
87
+ message: 'This spoke has been deprecated.'
88
+ },
89
+ 307: {
90
+ name: 'AssetNotInSpoke',
91
+ message: 'This asset is not listed on the spoke.'
92
+ },
93
+ 308: {
94
+ name: 'AssetAlreadyInSpoke',
95
+ message: 'This asset is already listed in the spoke.'
96
+ },
97
+ 309: {
98
+ name: 'SpokeAssetInUse',
99
+ message: 'This spoke asset still has live positions; drain usage before removal.'
100
+ },
101
+ 315: {
102
+ name: 'SpokeAssetPaused',
103
+ message: 'This spoke asset is paused.'
104
+ },
105
+ 316: {
106
+ name: 'SpokeAssetFrozen',
107
+ message: 'This spoke asset is frozen.'
108
+ },
109
+ 317: {
110
+ name: 'SpokeAssetFlagRelaxation',
111
+ message: 'Guardian flag updates may only tighten; clearing a flag requires the timelocked edit.'
112
+ },
113
+ 318: {
114
+ name: 'SpokeAssetSeizureHalted',
115
+ message: 'Liquidation seizure is halted for this spoke asset.'
116
+ },
117
+ // OracleError
118
+ 220: {
119
+ name: 'InvalidOracleBase',
120
+ message: 'The oracle quote asset/market is missing or invalid.'
121
+ },
122
+ 221: {
123
+ name: 'InvalidOracleDecimals',
124
+ message: 'The oracle decimals value is invalid.'
125
+ },
126
+ 224: {
127
+ name: 'InvalidSanityBounds',
128
+ message: 'Oracle sanity bounds are invalid (require 0 < min < max).'
129
+ },
130
+ 225: {
131
+ name: 'OracleCycleDetected',
132
+ message: 'A cycle was detected in the oracle quote chain.'
133
+ }
134
+ };
135
+ /**
136
+ * Extracts a `Error(Contract, #N)` code from a raw Soroban simulation/RPC error
137
+ * string and resolves it against the rs-lending-xlm catalog. Matches only the
138
+ * exact `Error(Contract, #N)` pattern — never loose digits, since raw error
139
+ * strings can contain unrelated numbers (fees, ledgers, sequence numbers).
140
+ */ export function mapSorobanError(raw) {
141
+ const match = raw.match(/Error\(Contract,\s*#(\d+)\)/);
142
+ if (!match) return null;
143
+ const code = Number(match[1]);
144
+ const entry = SOROBAN_ERROR_CATALOG[code];
145
+ return entry ? {
146
+ code,
147
+ ...entry
148
+ } : null;
149
+ }