@upshiftfinance/sdk 0.3.0 → 0.5.0

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 (61) hide show
  1. package/README.md +6 -2
  2. package/esm/index.d.mts +15 -0
  3. package/esm/index.mjs +18 -0
  4. package/lib/abis/KycRegistry.d.ts +109 -0
  5. package/lib/abis/KycRegistry.js +73 -0
  6. package/lib/abis/index.d.ts +1 -0
  7. package/lib/abis/index.js +1 -0
  8. package/lib/adapters/sui/actions.d.ts +420 -0
  9. package/lib/adapters/sui/actions.js +701 -0
  10. package/lib/adapters/sui/client.d.ts +3 -3
  11. package/lib/adapters/sui/client.js +27 -9
  12. package/lib/adapters/sui/getters.d.ts +268 -0
  13. package/lib/adapters/sui/getters.js +669 -0
  14. package/lib/adapters/sui/index.d.ts +243 -3
  15. package/lib/adapters/sui/index.js +250 -3
  16. package/lib/adapters/sui/targets.d.ts +42 -1
  17. package/lib/adapters/sui/targets.js +42 -1
  18. package/lib/core/analytics/method-taxonomy.d.ts +1 -1
  19. package/lib/core/analytics/method-taxonomy.js +9 -0
  20. package/lib/core/analytics/version.d.ts +1 -1
  21. package/lib/core/analytics/version.js +1 -1
  22. package/lib/core/constants/core.d.ts +5 -0
  23. package/lib/core/constants/core.js +5 -0
  24. package/lib/core/constants/kyc.d.ts +9 -0
  25. package/lib/core/constants/kyc.js +12 -0
  26. package/lib/core/constants/web3.d.ts +2 -2
  27. package/lib/core/constants/web3.js +9 -2
  28. package/lib/core/errors/index.d.ts +90 -1
  29. package/lib/core/errors/index.js +94 -1
  30. package/lib/core/fetcher.d.ts +5 -0
  31. package/lib/core/fetcher.js +1 -1
  32. package/lib/core/helpers/chain-support.d.ts +2 -2
  33. package/lib/core/helpers/chain-support.js +3 -3
  34. package/lib/core/helpers/kyc.d.ts +47 -0
  35. package/lib/core/helpers/kyc.js +111 -0
  36. package/lib/core/helpers/revert-decode.js +3 -2
  37. package/lib/core/index.d.ts +2 -0
  38. package/lib/core/index.js +2 -0
  39. package/lib/evm/methods/crossChainVault.js +25 -43
  40. package/lib/index.d.ts +4 -0
  41. package/lib/index.js +4 -0
  42. package/lib/main.d.ts +33 -3
  43. package/lib/main.js +32 -0
  44. package/lib/modules/api/kyc.d.ts +4 -0
  45. package/lib/modules/api/kyc.js +148 -0
  46. package/lib/modules/api/main.d.ts +63 -1
  47. package/lib/modules/api/main.js +64 -0
  48. package/lib/modules/vaults/getters.d.ts +3 -1
  49. package/lib/modules/vaults/getters.js +82 -10
  50. package/lib/modules/vaults/main.d.ts +41 -3
  51. package/lib/modules/vaults/main.js +82 -6
  52. package/lib/modules/vaults/types.d.ts +7 -1
  53. package/lib/modules/vaults/utils.js +4 -1
  54. package/lib/sdk.d.ts +16363 -14615
  55. package/lib/types/index.d.ts +1 -0
  56. package/lib/types/index.js +1 -0
  57. package/lib/types/kyc.d.ts +61 -0
  58. package/lib/types/kyc.js +3 -0
  59. package/lib/types/vaults.d.ts +2 -0
  60. package/lib/types/webserver.d.ts +5 -0
  61. package/package.json +21 -2
@@ -0,0 +1,701 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DEFAULT_SUI_SLIPPAGE_BPS = void 0;
37
+ exports.suiMinOut = suiMinOut;
38
+ exports.suiExecutionErrorToSdkError = suiExecutionErrorToSdkError;
39
+ exports.buildSuiDepositTransaction = buildSuiDepositTransaction;
40
+ exports.buildSuiRedeemTransaction = buildSuiRedeemTransaction;
41
+ exports.buildSuiRequestRedeemTransaction = buildSuiRequestRedeemTransaction;
42
+ exports.buildSuiCancelRedeemRequestTransaction = buildSuiCancelRedeemRequestTransaction;
43
+ exports.buildSuiClaimWithdrawalTransaction = buildSuiClaimWithdrawalTransaction;
44
+ exports.submitSuiTransaction = submitSuiTransaction;
45
+ const errors_1 = require("../../core/errors");
46
+ const chain_address_1 = require("../../core/helpers/chain-address");
47
+ const curator_alert_1 = require("../../core/logger/curator-alert");
48
+ const client_1 = require("./client");
49
+ const constants_1 = require("./constants");
50
+ const getters_1 = require("./getters");
51
+ const targets_1 = require("./targets");
52
+ const U64_MAX = 2n ** 64n - 1n;
53
+ /**
54
+ * Default tolerance below the previewed output: 10 basis points (0.1%).
55
+ *
56
+ * Between the dry run and execution the price moves only by rounding (a unit
57
+ * as other deposits and redemptions land) and by management fee accruing for
58
+ * a few seconds; 0.1% absorbs both. An operator AUM report can move it by up
59
+ * to the vault's report bound (percent-level), which this still rejects with
60
+ * `slippage` rather than fill at a price the user did not see.
61
+ */
62
+ exports.DEFAULT_SUI_SLIPPAGE_BPS = 10;
63
+ /**
64
+ * The minimum output for a previewed amount: `expected` less `slippageBps`,
65
+ * rounded down.
66
+ * @internal
67
+ */
68
+ function suiMinOut(expected, slippageBps) {
69
+ return (expected * BigInt(10_000 - slippageBps)) / 10000n;
70
+ }
71
+ function assertSlippage(bps, method) {
72
+ if (bps === undefined)
73
+ return;
74
+ if (!Number.isInteger(bps) || bps < 0 || bps > 10_000) {
75
+ throw new errors_1.UpshiftValidationError('INVALID_INPUT', `${method}: slippageBps must be an integer from 0 to 10000.`);
76
+ }
77
+ }
78
+ /** Contract errors with a stable SDK reason, keyed `module::constant`. */
79
+ const ABORT_REASONS = {
80
+ 'vault::EPaused': 'paused',
81
+ 'vault::EBelowMinDeposit': 'below_min_deposit',
82
+ 'vault::EAboveMaxDeposit': 'above_max_deposit',
83
+ 'vault::EDepositCapExceeded': 'deposit_cap_exceeded',
84
+ 'vault::EAboveMaxWithdrawal': 'above_max_withdrawal',
85
+ 'vault::EInsufficientLiquidity': 'insufficient_liquidity',
86
+ 'vault::EQueueAttached': 'queue_attached',
87
+ 'vault::ESlippageExceeded': 'slippage',
88
+ 'vault::EZeroShares': 'zero_output',
89
+ 'vault::EZeroAssets': 'zero_output',
90
+ 'versioning::EVersionRetired': 'version_retired',
91
+ 'vault::EQueueNotAttached': 'queue_not_attached',
92
+ 'vault::EInvalidPayout': 'invalid_payout',
93
+ 'vault::ENotRequestOwner': 'not_request_owner',
94
+ 'vault::ENotFinalizer': 'not_finalizer',
95
+ 'vault::ENotEligibleYet': 'not_eligible_yet',
96
+ 'vault::ERequestExpired': 'request_expired',
97
+ // The request ID was used before: its derived object ID is already claimed.
98
+ 'derived_object::EObjectAlreadyExists': 'request_id_in_use',
99
+ };
100
+ /**
101
+ * Map a failed Sui execution to an SDK error: a contract abort becomes an
102
+ * {@link UpshiftVaultAbortError} with a stable reason, anything else an
103
+ * {@link UpshiftSDKError} (`UNKNOWN`).
104
+ *
105
+ * The SDK applies this itself to its dry runs and to `executeTransaction`.
106
+ * Use it when the wallet builds, signs or executes a transaction and fails,
107
+ * e.g. because the vault changed after the SDK built it.
108
+ *
109
+ * @param executionError - `effects.status.error` of a failed transaction, or
110
+ * the `executionError` of a `SimulationError`.
111
+ * @param context - Structured context for the error (e.g. network, digest).
112
+ * @returns The typed error; throw it or branch on its `reason`.
113
+ * @example
114
+ * ```ts
115
+ * try {
116
+ * await dAppKit.signTransaction({ transaction });
117
+ * } catch (err) {
118
+ * if (err?.executionError) throw Sui.actions.suiExecutionErrorToSdkError(err.executionError);
119
+ * throw err;
120
+ * }
121
+ * ```
122
+ */
123
+ function suiExecutionErrorToSdkError(executionError, context = {}) {
124
+ const abort = executionError?.MoveAbort;
125
+ if (abort) {
126
+ const module = abort.location?.module;
127
+ const constant = abort.cleverError?.constantName;
128
+ const reason = ABORT_REASONS[`${module}::${constant}`] ?? 'other';
129
+ return new errors_1.UpshiftVaultAbortError(reason, `Sui vault rejected the transaction: ${reason}${constant ? ` (${module}::${constant})` : ` (abort ${abort.abortCode})`}.`, { module, constant }, { context: { ...context, abortCode: abort.abortCode } });
130
+ }
131
+ return new errors_1.UpshiftSDKError('UNKNOWN', `Sui transaction failed: ${executionError?.message ?? 'unknown error'}`, { context });
132
+ }
133
+ function assertSender(sender, method) {
134
+ if (!(0, chain_address_1.isSuiAddress)(sender)) {
135
+ throw new errors_1.UpshiftValidationError('INVALID_ADDRESS', `${method}: sender must be a Sui address (0x + 64 hex chars).`);
136
+ }
137
+ }
138
+ function assertAmount(value, method, name, positive) {
139
+ if (value === undefined)
140
+ return;
141
+ if (typeof value !== 'bigint' ||
142
+ value < (positive ? 1n : 0n) ||
143
+ value > U64_MAX) {
144
+ throw new errors_1.UpshiftValidationError('INVALID_INPUT', `${method}: ${name} must be a bigint between ${positive ? 1 : 0} and 2^64 - 1.`);
145
+ }
146
+ }
147
+ /** Move-call helper bound to one vault and its contracts. */
148
+ function vaultCalls(tx, ref, contracts) {
149
+ const typeArguments = (0, targets_1.suiVaultTypeArguments)(ref.assetCoinType, ref.shareCoinType);
150
+ return (target, args) => tx.moveCall({
151
+ target: (0, targets_1.suiMoveCallTarget)(contracts, target),
152
+ typeArguments,
153
+ arguments: [
154
+ tx.object(ref.vaultId),
155
+ tx.object(contracts.registryId),
156
+ ...args,
157
+ ],
158
+ });
159
+ }
160
+ /**
161
+ * Dry-run `tx` against the network and return each command's return values.
162
+ * An abort, at build (gas estimation) or in the simulation, becomes a typed
163
+ * SDK error; `onAbort` sees it first.
164
+ */
165
+ async function dryRun(config, build, method, onAbort) {
166
+ const context = { network: config.network, method };
167
+ try {
168
+ return await (0, client_1.withSuiClient)((0, client_1.resolveSuiNetworkConfig)(config), async (client) => {
169
+ const result = await client.core.simulateTransaction({
170
+ transaction: await build(),
171
+ include: { commandResults: true, effects: true },
172
+ });
173
+ if (result.$kind !== 'Transaction') {
174
+ throw suiExecutionErrorToSdkError(result.FailedTransaction.effects?.status.error ?? undefined, context);
175
+ }
176
+ return result.commandResults.map((c) => c.returnValues.map((v) => v.bcs));
177
+ });
178
+ }
179
+ catch (err) {
180
+ const sdkError = err instanceof errors_1.UpshiftSDKError
181
+ ? err
182
+ : err?.executionError
183
+ ? suiExecutionErrorToSdkError(err.executionError, context)
184
+ : new errors_1.UpshiftValidationError('INVALID_INPUT', `${method}: could not build the transaction: ${err instanceof Error ? err.message : String(err)}`, { cause: err, context });
185
+ if (sdkError instanceof errors_1.UpshiftVaultAbortError)
186
+ onAbort?.(sdkError);
187
+ throw sdkError;
188
+ }
189
+ }
190
+ /**
191
+ * Build an unsigned deposit: charge the management fee, deposit with a minimum
192
+ * share amount, and send the shares to the sender — one transaction.
193
+ *
194
+ * The payment is `coinWithBalance(Asset)`: the wallet selects and merges the
195
+ * sender's coins when it signs. The fee is charged first so the deposit is
196
+ * priced on a current NAV.
197
+ *
198
+ * RPC cost: one dry run (plus the coin lookup it needs).
199
+ *
200
+ * The minimum is the dry run's preview less `slippageBps` (default 0.1%),
201
+ * unless `minSharesOut` is given.
202
+ *
203
+ * @param params - Vault, amount, sender, and optional `minSharesOut` or
204
+ * `slippageBps`.
205
+ * @param sui - The SDK's `sui` config; applied only for `params.vault.network`.
206
+ * @returns The unsigned transaction, the shares the dry run minted and the
207
+ * minimum it enforces.
208
+ * @throws {@link UpshiftValidationError} for a malformed sender or vault, an
209
+ * out-of-range amount, unknown contract IDs, or a transaction that cannot be
210
+ * built (e.g. the sender lacks the asset).
211
+ * @throws {@link UpshiftVaultAbortError} when the vault would reject the
212
+ * deposit (`paused`, `below_min_deposit`, `above_max_deposit`,
213
+ * `deposit_cap_exceeded`, `zero_output`, `slippage`, `version_retired`).
214
+ * @throws {@link UpshiftNetworkError} when every Sui endpoint fails.
215
+ * @example
216
+ * ```ts
217
+ * const { transaction } = await buildSuiDepositTransaction(
218
+ * { vault: ref, amount: 1_000_000n, sender: account.address },
219
+ * sdkSuiConfig,
220
+ * );
221
+ * const digest = await submitSuiTransaction(
222
+ * await dAppKit.signTransaction({ transaction }),
223
+ * 'testnet',
224
+ * sdkSuiConfig,
225
+ * );
226
+ * ```
227
+ */
228
+ async function buildSuiDepositTransaction(params, sui) {
229
+ const method = 'buildSuiDepositTransaction';
230
+ const { vault: ref, amount, sender, minSharesOut, slippageBps } = params;
231
+ assertSender(sender, method);
232
+ if (!(0, chain_address_1.isSuiAddress)(ref?.vaultId)) {
233
+ throw new errors_1.UpshiftValidationError('INVALID_ADDRESS', `${method}: vault.vaultId must be a Sui object ID.`);
234
+ }
235
+ assertAmount(amount, method, 'amount', true);
236
+ assertAmount(minSharesOut, method, 'minSharesOut', false);
237
+ assertSlippage(slippageBps, method);
238
+ const config = (0, getters_1.suiConfigForNetwork)(ref.network, sui);
239
+ const contracts = (0, targets_1.resolveSuiVaultContracts)(config);
240
+ const { Transaction: SuiTransaction, coinWithBalance } = await Promise.resolve().then(() => __importStar(require('@mysten/sui/transactions')));
241
+ const compose = (min, withPreview) => {
242
+ const tx = new SuiTransaction();
243
+ tx.setSender(sender);
244
+ const call = vaultCalls(tx, ref, contracts);
245
+ call('chargeManagementFee', [tx.object(constants_1.SUI_CLOCK_ID)]);
246
+ if (withPreview)
247
+ call('previewDeposit', [tx.pure.u64(amount)]);
248
+ const payment = tx.add(coinWithBalance({ type: ref.assetCoinType, balance: amount }));
249
+ const shares = call('depositChecked', [payment, tx.pure.u64(min)]);
250
+ tx.transferObjects([shares], sender);
251
+ return tx;
252
+ };
253
+ // The preview runs after the fee charge, so it matches the deposit exactly.
254
+ const results = await dryRun(config, async () => compose(0n, true), method);
255
+ const expectedShares = (0, getters_1.decodeSuiUint)(results[1][0], 8);
256
+ const min = minSharesOut ??
257
+ suiMinOut(expectedShares, slippageBps ?? exports.DEFAULT_SUI_SLIPPAGE_BPS);
258
+ return {
259
+ transaction: compose(min, false),
260
+ expectedShares,
261
+ minSharesOut: min,
262
+ };
263
+ }
264
+ /**
265
+ * Build an unsigned instant redemption: charge the management fee, redeem
266
+ * shares with a minimum net asset amount and send the assets to the sender —
267
+ * one transaction.
268
+ *
269
+ * The shares are `coinWithBalance(Share)`. A vault with a withdrawal queue
270
+ * attached has instant redemption off; that fails here with `queue_attached`.
271
+ *
272
+ * Side effect on failure: when the vault rejects the dry run, the failure is
273
+ * reported to that vault's curator (fire-and-forget; see
274
+ * `monitoring.curatorAlerts`).
275
+ *
276
+ * RPC cost: one dry run (plus the coin lookup it needs).
277
+ *
278
+ * The minimum is the dry run's net preview less `slippageBps` (default 0.1%),
279
+ * unless `minAssetsOut` is given.
280
+ *
281
+ * @param params - Vault, shares, sender, and optional `minAssetsOut` or
282
+ * `slippageBps`.
283
+ * @param sui - The SDK's `sui` config; applied only for `params.vault.network`.
284
+ * @returns The unsigned transaction, the dry run's gross and net assets and
285
+ * the minimum it enforces.
286
+ * @throws {@link UpshiftValidationError} for a malformed sender or vault, an
287
+ * out-of-range amount, unknown contract IDs, or a transaction that cannot be
288
+ * built (e.g. the sender lacks the shares).
289
+ * @throws {@link UpshiftVaultAbortError} when the vault would reject the
290
+ * redemption (`queue_attached`, `paused`, `above_max_withdrawal`,
291
+ * `insufficient_liquidity`, `zero_output`, `slippage`, `version_retired`).
292
+ * @throws {@link UpshiftNetworkError} when every Sui endpoint fails.
293
+ */
294
+ async function buildSuiRedeemTransaction(params, sui) {
295
+ const method = 'buildSuiRedeemTransaction';
296
+ const { vault: ref, shares, sender, minAssetsOut, slippageBps } = params;
297
+ assertSender(sender, method);
298
+ if (!(0, chain_address_1.isSuiAddress)(ref?.vaultId)) {
299
+ throw new errors_1.UpshiftValidationError('INVALID_ADDRESS', `${method}: vault.vaultId must be a Sui object ID.`);
300
+ }
301
+ assertAmount(shares, method, 'shares', true);
302
+ assertAmount(minAssetsOut, method, 'minAssetsOut', false);
303
+ assertSlippage(slippageBps, method);
304
+ const config = (0, getters_1.suiConfigForNetwork)(ref.network, sui);
305
+ const contracts = (0, targets_1.resolveSuiVaultContracts)(config);
306
+ const { Transaction: SuiTransaction, coinWithBalance } = await Promise.resolve().then(() => __importStar(require('@mysten/sui/transactions')));
307
+ const compose = (min, withPreview) => {
308
+ const tx = new SuiTransaction();
309
+ tx.setSender(sender);
310
+ const call = vaultCalls(tx, ref, contracts);
311
+ // As on deposit: charge the accrued management fee first, so the redeemer
312
+ // pays their share of it instead of leaving it to the remaining holders.
313
+ call('chargeManagementFee', [tx.object(constants_1.SUI_CLOCK_ID)]);
314
+ if (withPreview) {
315
+ call('previewRedemption', [tx.pure.u64(shares), tx.pure.bool(true)]);
316
+ }
317
+ const payment = tx.add(coinWithBalance({ type: ref.shareCoinType, balance: shares }));
318
+ const assets = call('redeemChecked', [payment, tx.pure.u64(min)]);
319
+ tx.transferObjects([assets], sender);
320
+ return tx;
321
+ };
322
+ const results = await dryRun(config, async () => compose(0n, true), method, (err) => (0, curator_alert_1.reportRedeemFailure)({
323
+ phase: 'simulation',
324
+ chain: 'sui',
325
+ network: ref.network,
326
+ contractId: ref.vaultId,
327
+ eoa: sender,
328
+ sharesRaw: shares.toString(),
329
+ error: err.message,
330
+ }));
331
+ // results[0] is the fee charge; the preview runs after it.
332
+ const expectedGrossAssets = (0, getters_1.decodeSuiUint)(results[1][0], 8);
333
+ const expectedAssets = (0, getters_1.decodeSuiUint)(results[1][1], 8);
334
+ const min = minAssetsOut ??
335
+ suiMinOut(expectedAssets, slippageBps ?? exports.DEFAULT_SUI_SLIPPAGE_BPS);
336
+ return {
337
+ transaction: compose(min, false),
338
+ expectedGrossAssets,
339
+ expectedAssets,
340
+ minAssetsOut: min,
341
+ };
342
+ }
343
+ function assertVaultId(ref, method) {
344
+ if (!(0, chain_address_1.isSuiAddress)(ref?.vaultId)) {
345
+ throw new errors_1.UpshiftValidationError('INVALID_ADDRESS', `${method}: vault.vaultId must be a Sui object ID.`);
346
+ }
347
+ }
348
+ function assertAddress(value, method, name) {
349
+ if (value !== undefined && value !== null && !(0, chain_address_1.isSuiAddress)(value)) {
350
+ throw new errors_1.UpshiftValidationError('INVALID_ADDRESS', `${method}: ${name} must be a Sui address (0x + 64 hex chars).`);
351
+ }
352
+ }
353
+ /**
354
+ * Build an unsigned withdrawal request for a vault with a withdrawal queue:
355
+ * escrow the shares in a new request object and share it — one transaction.
356
+ *
357
+ * The request is priced when it is claimed, not now. Once the vault's cooldown
358
+ * has run, claim it with {@link buildSuiClaimWithdrawalTransaction}; it can be
359
+ * cancelled at any time with {@link buildSuiCancelRedeemRequestTransaction}.
360
+ *
361
+ * The request ID is the lowest the wallet has never used on this vault
362
+ * (`0, 1, 2, …`), which is how `getSuiWithdrawalRequests` finds requests
363
+ * without an event index. Two requests built before either lands get the same
364
+ * ID; the second then fails (the node refuses its stale coins, or it aborts
365
+ * with `request_id_in_use`) — build it again.
366
+ *
367
+ * The finalizer defaults to the owner: only the owner can claim during the
368
+ * first day the request is claimable, then anyone can (e.g. a keeper).
369
+ *
370
+ * RPC cost: one object read for the next ID (more after 23 earlier requests),
371
+ * then one dry run (plus the coin lookup it needs).
372
+ *
373
+ * @param params - Vault, shares, sender, and optional `payout` and `finalizer`.
374
+ * @param sui - The SDK's `sui` config; applied only for `params.vault.network`.
375
+ * @returns The unsigned transaction, the request ID and the request's object ID.
376
+ * @throws {@link UpshiftValidationError} for a malformed address, an
377
+ * out-of-range amount, unknown contract IDs, or a transaction that cannot be
378
+ * built (e.g. the sender lacks the shares).
379
+ * @throws {@link UpshiftVaultAbortError} when the vault would reject it
380
+ * (`queue_not_attached`, `paused`, `above_max_withdrawal`, `invalid_payout`,
381
+ * `request_id_in_use`, `version_retired`), or the shares would pay nothing
382
+ * (`zero_output`).
383
+ * @throws {@link UpshiftNetworkError} when every Sui endpoint fails.
384
+ * @example
385
+ * ```ts
386
+ * const { transaction, request } = await buildSuiRequestRedeemTransaction(
387
+ * { vault: ref, shares: 1_000_000n, sender: account.address },
388
+ * sdkSuiConfig,
389
+ * );
390
+ * ```
391
+ */
392
+ async function buildSuiRequestRedeemTransaction(params, sui) {
393
+ const method = 'buildSuiRequestRedeemTransaction';
394
+ const { vault: ref, shares, sender, payout, finalizer } = params;
395
+ assertSender(sender, method);
396
+ assertVaultId(ref, method);
397
+ assertAmount(shares, method, 'shares', true);
398
+ assertAddress(payout, method, 'payout');
399
+ assertAddress(finalizer, method, 'finalizer');
400
+ const config = (0, getters_1.suiConfigForNetwork)(ref.network, sui);
401
+ const contracts = (0, targets_1.resolveSuiVaultContracts)(config);
402
+ const { Transaction: SuiTransaction, coinWithBalance } = await Promise.resolve().then(() => __importStar(require('@mysten/sui/transactions')));
403
+ const requestId = await (0, getters_1.nextSuiWithdrawalRequestId)(ref, sender, sui);
404
+ const compose = (withPreview) => {
405
+ const tx = new SuiTransaction();
406
+ tx.setSender(sender);
407
+ const call = vaultCalls(tx, ref, contracts);
408
+ // Priced on a current NAV (fee charged first, in the dry run only).
409
+ // Queued withdrawals pay no instant fee, so gross is what a claim pays.
410
+ if (withPreview) {
411
+ call('chargeManagementFee', [tx.object(constants_1.SUI_CLOCK_ID)]);
412
+ call('previewRedemption', [tx.pure.u64(shares), tx.pure.bool(false)]);
413
+ }
414
+ const escrow = tx.add(coinWithBalance({ type: ref.shareCoinType, balance: shares }));
415
+ const request = call('requestWithdrawal', [
416
+ escrow,
417
+ tx.pure.u64(requestId),
418
+ tx.pure.address(payout ?? sender),
419
+ tx.pure.option('address', finalizer === undefined ? sender : finalizer),
420
+ tx.object(constants_1.SUI_CLOCK_ID),
421
+ ]);
422
+ // The contract returns the request unshared; it must be shared in the
423
+ // same transaction.
424
+ tx.moveCall({
425
+ target: (0, targets_1.suiMoveCallTarget)(contracts, 'shareWithdrawalRequest'),
426
+ typeArguments: [ref.shareCoinType],
427
+ arguments: [request],
428
+ });
429
+ return tx;
430
+ };
431
+ const results = await dryRun(config, async () => compose(true), method);
432
+ // results[0] is the fee charge.
433
+ const expectedAssets = (0, getters_1.decodeSuiUint)(results[1][0], 8);
434
+ // The contract accepts a request whose claim would pay nothing (shares worth
435
+ // less than one asset unit); it could then only be cancelled.
436
+ if (expectedAssets === 0n) {
437
+ throw new errors_1.UpshiftVaultAbortError('zero_output', `${method}: ${shares} shares pay no assets at the current price; request more.`, {}, { context: { network: ref.network, method } });
438
+ }
439
+ return {
440
+ transaction: compose(false),
441
+ requestId,
442
+ expectedAssets,
443
+ request: await (0, getters_1.deriveSuiWithdrawalRequestId)(ref.vaultId, sender, requestId, contracts.originalPackageId),
444
+ };
445
+ }
446
+ /**
447
+ * Build an unsigned cancellation of a pending withdrawal request: the escrowed
448
+ * shares go back to the sender — one transaction.
449
+ *
450
+ * Allowed at any time while the request exists, also while the vault is
451
+ * paused or after the request expired. Only the wallet that made it may.
452
+ *
453
+ * RPC cost: one dry run.
454
+ *
455
+ * @param params - Vault, request object ID and sender.
456
+ * @param sui - The SDK's `sui` config; applied only for `params.vault.network`.
457
+ * @returns The unsigned transaction and the shares it returns.
458
+ * @throws {@link UpshiftValidationError} for a malformed address, unknown
459
+ * contract IDs, or a transaction that cannot be built (e.g. the request no
460
+ * longer exists).
461
+ * @throws {@link UpshiftVaultAbortError} when the vault would reject it
462
+ * (`not_request_owner`, `version_retired`).
463
+ * @throws {@link UpshiftNetworkError} when every Sui endpoint fails.
464
+ * @example
465
+ * ```ts
466
+ * const { transaction } = await buildSuiCancelRedeemRequestTransaction(
467
+ * { vault: ref, request, sender: account.address },
468
+ * sdkSuiConfig,
469
+ * );
470
+ * ```
471
+ */
472
+ async function buildSuiCancelRedeemRequestTransaction(params, sui) {
473
+ const method = 'buildSuiCancelRedeemRequestTransaction';
474
+ const { vault: ref, request, sender } = params;
475
+ assertSender(sender, method);
476
+ assertVaultId(ref, method);
477
+ if (!(0, chain_address_1.isSuiAddress)(request)) {
478
+ throw new errors_1.UpshiftValidationError('INVALID_ADDRESS', `${method}: request must be a Sui object ID.`);
479
+ }
480
+ const config = (0, getters_1.suiConfigForNetwork)(ref.network, sui);
481
+ const contracts = (0, targets_1.resolveSuiVaultContracts)(config);
482
+ const { Transaction: SuiTransaction } = await Promise.resolve().then(() => __importStar(require('@mysten/sui/transactions')));
483
+ const compose = () => {
484
+ const tx = new SuiTransaction();
485
+ tx.setSender(sender);
486
+ const call = vaultCalls(tx, ref, contracts);
487
+ const shares = call('cancelWithdrawal', [tx.object(request)]);
488
+ tx.transferObjects([shares], sender);
489
+ return tx;
490
+ };
491
+ const results = await dryRun(config, async () => compose(), method);
492
+ // The returned Coin<Share> is a 32-byte UID followed by its u64 balance.
493
+ const coin = results[0][0];
494
+ return {
495
+ transaction: compose(),
496
+ shares: (0, getters_1.decodeSuiUint)(coin.subarray(32), 8),
497
+ };
498
+ }
499
+ /**
500
+ * Build an unsigned claim of a withdrawal request: charge the management fee,
501
+ * then burn the escrowed shares and pay their current value to the request's
502
+ * payout, with a minimum — one transaction.
503
+ *
504
+ * The payout lands in the payout address's **address balance**, not as a
505
+ * coin object. Balance reads (`getBalance`) include it; wallets that only
506
+ * list coin objects may not show it.
507
+ *
508
+ * The request is priced now, at the vault's current share price, without the
509
+ * instant-redemption fee. It can be claimed from `eligibleAtMs` until
510
+ * `expiresAtMs`, by its owner, its finalizer (anyone when it names none), and
511
+ * by anyone from one day after it became eligible.
512
+ *
513
+ * RPC cost: one dry run.
514
+ *
515
+ * @param params - Vault, request object ID, sender, and optional
516
+ * `minAssetsOut` or `slippageBps`.
517
+ * @param sui - The SDK's `sui` config; applied only for `params.vault.network`.
518
+ * @returns The unsigned transaction, the shares it burns, the dry run's
519
+ * payout and the minimum it enforces.
520
+ * @throws {@link UpshiftValidationError} for a malformed address, an
521
+ * out-of-range minimum, unknown contract IDs, or a transaction that cannot
522
+ * be built (e.g. the request no longer exists).
523
+ * @throws {@link UpshiftVaultAbortError} when the vault would reject it
524
+ * (`not_eligible_yet`, `request_expired`, `not_finalizer`, `paused`,
525
+ * `above_max_withdrawal`, `insufficient_liquidity`, `zero_output`,
526
+ * `slippage`, `version_retired`).
527
+ * @throws {@link UpshiftNetworkError} when every Sui endpoint fails.
528
+ * @example
529
+ * ```ts
530
+ * const { transaction, expectedAssets } = await buildSuiClaimWithdrawalTransaction(
531
+ * { vault: ref, request, sender: account.address },
532
+ * sdkSuiConfig,
533
+ * );
534
+ * ```
535
+ */
536
+ async function buildSuiClaimWithdrawalTransaction(params, sui) {
537
+ const method = 'buildSuiClaimWithdrawalTransaction';
538
+ const { vault: ref, request, sender, minAssetsOut, slippageBps } = params;
539
+ assertSender(sender, method);
540
+ assertVaultId(ref, method);
541
+ if (!(0, chain_address_1.isSuiAddress)(request)) {
542
+ throw new errors_1.UpshiftValidationError('INVALID_ADDRESS', `${method}: request must be a Sui object ID.`);
543
+ }
544
+ assertAmount(minAssetsOut, method, 'minAssetsOut', false);
545
+ assertSlippage(slippageBps, method);
546
+ const config = (0, getters_1.suiConfigForNetwork)(ref.network, sui);
547
+ const contracts = (0, targets_1.resolveSuiVaultContracts)(config);
548
+ const { Transaction: SuiTransaction } = await Promise.resolve().then(() => __importStar(require('@mysten/sui/transactions')));
549
+ const compose = (min, withPreview) => {
550
+ const tx = new SuiTransaction();
551
+ tx.setSender(sender);
552
+ const call = vaultCalls(tx, ref, contracts);
553
+ // Charge the accrued management fee first, as on deposit and redeem, so
554
+ // the claim is priced on the current NAV.
555
+ call('chargeManagementFee', [tx.object(constants_1.SUI_CLOCK_ID)]);
556
+ if (withPreview) {
557
+ const shares = tx.moveCall({
558
+ target: (0, targets_1.suiMoveCallTarget)(contracts, 'withdrawalRequestShares'),
559
+ typeArguments: [ref.shareCoinType],
560
+ arguments: [tx.object(request)],
561
+ });
562
+ call('previewRedemption', [shares, tx.pure.bool(false)]);
563
+ }
564
+ call('finalizeWithdrawalChecked', [
565
+ tx.object(request),
566
+ tx.pure.u64(min),
567
+ tx.object(constants_1.SUI_CLOCK_ID),
568
+ ]);
569
+ return tx;
570
+ };
571
+ // results[0] is the fee charge, [1] the escrowed shares, [2] the preview.
572
+ const results = await dryRun(config, async () => compose(0n, true), method);
573
+ const shares = (0, getters_1.decodeSuiUint)(results[1][0], 8);
574
+ const expectedAssets = (0, getters_1.decodeSuiUint)(results[2][1], 8);
575
+ const min = minAssetsOut ??
576
+ suiMinOut(expectedAssets, slippageBps ?? exports.DEFAULT_SUI_SLIPPAGE_BPS);
577
+ return {
578
+ transaction: compose(min, false),
579
+ shares,
580
+ expectedAssets,
581
+ minAssetsOut: min,
582
+ };
583
+ }
584
+ /**
585
+ * The vault, sender and share amount of an instant redemption in a built
586
+ * transaction, for the curator alert; `null` when it is not one.
587
+ */
588
+ function redemptionOf(data, fromBase64) {
589
+ for (const command of data.commands) {
590
+ const call = command.MoveCall;
591
+ if (call?.module !== 'coin_entry' || call.function !== 'redeem_checked')
592
+ continue;
593
+ const vaultArg = call.arguments[0];
594
+ const vaultInput = vaultArg?.$kind === 'Input' ? data.inputs[vaultArg.Input] : undefined;
595
+ const vaultId = vaultInput?.Object?.SharedObject?.objectId;
596
+ if (!vaultId)
597
+ return null;
598
+ // The shares are usually split off the sender's coins; read that amount.
599
+ let sharesRaw = '0';
600
+ const coinArg = call.arguments[2];
601
+ const source = coinArg?.$kind === 'NestedResult' || coinArg?.$kind === 'Result'
602
+ ? data.commands[coinArg.$kind === 'Result'
603
+ ? coinArg.Result
604
+ : coinArg.NestedResult[0]]
605
+ : undefined;
606
+ const amountArg = source?.SplitCoins?.amounts[0];
607
+ const amountInput = amountArg?.$kind === 'Input' ? data.inputs[amountArg.Input] : undefined;
608
+ const bytes = amountInput?.Pure?.bytes;
609
+ if (bytes) {
610
+ const raw = fromBase64(bytes);
611
+ if (raw.length === 8)
612
+ sharesRaw = (0, getters_1.decodeSuiUint)(raw, 8).toString();
613
+ }
614
+ return { vaultId, sharesRaw };
615
+ }
616
+ return null;
617
+ }
618
+ /**
619
+ * Execute a signed transaction and wait until the network has indexed it.
620
+ *
621
+ * Side effect on failure: a failed instant redemption is reported to that
622
+ * vault's curator with its digest (fire-and-forget; see
623
+ * `monitoring.curatorAlerts`).
624
+ *
625
+ * RPC cost: one execution plus the confirmation poll.
626
+ *
627
+ * @param signed - Bytes and signature from the wallet's `signTransaction`.
628
+ * @param network - Network the transaction was built for.
629
+ * @param sui - The SDK's `sui` config; applied only for `network`.
630
+ * @returns The transaction digest once it succeeded and is indexed.
631
+ * @throws {@link UpshiftVaultAbortError} when the vault aborted it on chain.
632
+ * @throws {@link UpshiftSDKError} (`UNKNOWN`) when it failed for another
633
+ * reason; `context.digest` names it.
634
+ * @throws {@link UpshiftTransactionError} when it executed but was not
635
+ * indexed in time; check `signature` (the digest) before retrying.
636
+ * @throws {@link UpshiftNetworkError} when every Sui endpoint fails.
637
+ */
638
+ async function submitSuiTransaction(signed, network, sui) {
639
+ if (typeof signed?.bytes !== 'string' ||
640
+ typeof signed.signature !== 'string') {
641
+ throw new errors_1.UpshiftValidationError('INVALID_INPUT', 'submitSuiTransaction: pass { bytes, signature } from the wallet, both base64.');
642
+ }
643
+ const config = (0, getters_1.suiConfigForNetwork)(network, sui);
644
+ const networkConfig = (0, client_1.resolveSuiNetworkConfig)(config);
645
+ const { Transaction: SuiTransaction } = await Promise.resolve().then(() => __importStar(require('@mysten/sui/transactions')));
646
+ const { fromBase64 } = await Promise.resolve().then(() => __importStar(require('@mysten/sui/utils')));
647
+ let bytes;
648
+ try {
649
+ bytes = fromBase64(signed.bytes);
650
+ }
651
+ catch (err) {
652
+ throw new errors_1.UpshiftValidationError('INVALID_INPUT', 'submitSuiTransaction: bytes is not valid base64.', { cause: err });
653
+ }
654
+ // Executing the same signed bytes twice is idempotent, so endpoint failover is safe.
655
+ const result = await (0, client_1.withSuiClient)(networkConfig, (client) => client.core.executeTransaction({
656
+ transaction: bytes,
657
+ signatures: [signed.signature],
658
+ include: { effects: true },
659
+ })).catch((err) => {
660
+ if (err instanceof errors_1.UpshiftSDKError)
661
+ throw err;
662
+ // The node refused it before execution: a bad signature, or inputs that
663
+ // changed since signing (rebuild and sign again).
664
+ throw new errors_1.UpshiftSDKError('UNKNOWN', `submitSuiTransaction: the network rejected the transaction: ${err instanceof Error ? err.message : String(err)}`, { cause: err, context: { network } });
665
+ });
666
+ if (result.$kind !== 'Transaction') {
667
+ const failed = result.FailedTransaction;
668
+ const digest = failed.digest;
669
+ const error = suiExecutionErrorToSdkError(failed.effects?.status.error ?? undefined, { network, digest });
670
+ // Gas was still spent: wait (best effort) until the failure is indexed, so
671
+ // a retry built right after this sees the sender's new coin versions.
672
+ await (0, client_1.withSuiClient)(networkConfig, (client) => client.core.waitForTransaction({ result, timeout: 12_000 })).catch(() => undefined);
673
+ const data = SuiTransaction.from(bytes).getData();
674
+ const redemption = redemptionOf(data, fromBase64);
675
+ if (redemption && data.sender) {
676
+ (0, curator_alert_1.reportRedeemFailure)({
677
+ phase: 'submission',
678
+ chain: 'sui',
679
+ network,
680
+ contractId: redemption.vaultId,
681
+ eoa: data.sender,
682
+ sharesRaw: redemption.sharesRaw,
683
+ txHash: digest,
684
+ error: error.message,
685
+ });
686
+ }
687
+ throw error;
688
+ }
689
+ const digest = result.Transaction.digest;
690
+ try {
691
+ await (0, client_1.withSuiClient)(networkConfig, (client) =>
692
+ // Under the client's 15 s per-call bound, so a slow index is not mistaken
693
+ // for a dead endpoint.
694
+ client.core.waitForTransaction({ result, timeout: 12_000 }));
695
+ }
696
+ catch (err) {
697
+ throw new errors_1.UpshiftTransactionError(`Sui transaction ${digest} executed but was not confirmed in time.`, digest, { cause: err, context: { network } });
698
+ }
699
+ return digest;
700
+ }
701
+ //# sourceMappingURL=actions.js.map