@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.
- package/README.md +6 -2
- package/esm/index.d.mts +15 -0
- package/esm/index.mjs +18 -0
- package/lib/abis/KycRegistry.d.ts +109 -0
- package/lib/abis/KycRegistry.js +73 -0
- package/lib/abis/index.d.ts +1 -0
- package/lib/abis/index.js +1 -0
- package/lib/adapters/sui/actions.d.ts +420 -0
- package/lib/adapters/sui/actions.js +701 -0
- package/lib/adapters/sui/client.d.ts +3 -3
- package/lib/adapters/sui/client.js +27 -9
- package/lib/adapters/sui/getters.d.ts +268 -0
- package/lib/adapters/sui/getters.js +669 -0
- package/lib/adapters/sui/index.d.ts +243 -3
- package/lib/adapters/sui/index.js +250 -3
- package/lib/adapters/sui/targets.d.ts +42 -1
- package/lib/adapters/sui/targets.js +42 -1
- package/lib/core/analytics/method-taxonomy.d.ts +1 -1
- package/lib/core/analytics/method-taxonomy.js +9 -0
- package/lib/core/analytics/version.d.ts +1 -1
- package/lib/core/analytics/version.js +1 -1
- package/lib/core/constants/core.d.ts +5 -0
- package/lib/core/constants/core.js +5 -0
- package/lib/core/constants/kyc.d.ts +9 -0
- package/lib/core/constants/kyc.js +12 -0
- package/lib/core/constants/web3.d.ts +2 -2
- package/lib/core/constants/web3.js +9 -2
- package/lib/core/errors/index.d.ts +90 -1
- package/lib/core/errors/index.js +94 -1
- package/lib/core/fetcher.d.ts +5 -0
- package/lib/core/fetcher.js +1 -1
- package/lib/core/helpers/chain-support.d.ts +2 -2
- package/lib/core/helpers/chain-support.js +3 -3
- package/lib/core/helpers/kyc.d.ts +47 -0
- package/lib/core/helpers/kyc.js +111 -0
- package/lib/core/helpers/revert-decode.js +3 -2
- package/lib/core/index.d.ts +2 -0
- package/lib/core/index.js +2 -0
- package/lib/evm/methods/crossChainVault.js +25 -43
- package/lib/index.d.ts +4 -0
- package/lib/index.js +4 -0
- package/lib/main.d.ts +33 -3
- package/lib/main.js +32 -0
- package/lib/modules/api/kyc.d.ts +4 -0
- package/lib/modules/api/kyc.js +148 -0
- package/lib/modules/api/main.d.ts +63 -1
- package/lib/modules/api/main.js +64 -0
- package/lib/modules/vaults/getters.d.ts +3 -1
- package/lib/modules/vaults/getters.js +82 -10
- package/lib/modules/vaults/main.d.ts +41 -3
- package/lib/modules/vaults/main.js +82 -6
- package/lib/modules/vaults/types.d.ts +7 -1
- package/lib/modules/vaults/utils.js +4 -1
- package/lib/sdk.d.ts +16363 -14615
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/lib/types/kyc.d.ts +61 -0
- package/lib/types/kyc.js +3 -0
- package/lib/types/vaults.d.ts +2 -0
- package/lib/types/webserver.d.ts +5 -0
- 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
|