Sui Actions
Read, deposit into and instantly redeem from Upshift Sui vaults. Write operations build an unsigned transaction that the user's wallet signs; the SDK can then execute it and wait for confirmation.
Overview
Sui vault reads go through the standard vaults API (sdk.getVault, sdk.getVaults, sdk.getVaultPositions). Deposits and instant redemptions live on the Sui adapter (sdk.sui) and follow a build → sign → execute flow:
sdk.sui.vaultDeposit()/sdk.sui.vaultRedeem()dry-run the transaction with the sender's own coins and return an unsignedTransaction. A vault that would reject it (paused, a limit, a withdrawal queue attached) fails here, before the wallet is asked to sign.- The user's wallet signs it (e.g. with dApp Kit).
sdk.sui.executeTransaction()executes the signed transaction and resolves to its digest once the network has indexed it.
Transactions run on the network the SDK was configured with — mainnet by default.
Setup
import UpshiftSDK from '@upshiftfinance/sdk';
const sdk = new UpshiftSDK({
appName: 'your-app-name',
sui: {
network: 'testnet', // 'mainnet' (default) | 'testnet'
rpcUrl: 'https://your-node.example/…', // optional; must serve gRPC-web
// Required until the SDK ships the Upshift vault contract IDs:
packageId: '0x…', // latest upshift_vault package (move calls target it)
originalPackageId: '0x…', // first published package (vault types name it)
registryId: '0x…', // shared upshift_vault::registry::Registry object
},
});| Option | Required | Description |
|---|---|---|
network | No | 'mainnet' (default) or 'testnet'. |
rpcUrl | No | Your node. It must serve gRPC-web (the SDK talks gRPC-web so the same code runs in browsers). It becomes the primary endpoint; Mysten's public fullnode stays behind it as failover. |
packageId, originalPackageId, registryId | Until the SDK ships them | Long-form IDs (0x + 64 hex chars) of the Upshift vault contracts on network. Without them, vault reads fall back to backend values with deposits paused, and the write methods throw. |
The SDK loads
@mysten/suionly on the first Sui call. In Node.js that needs Node ^20.19 or >=22.12 (@mysten/suiis ESM-only, and older releases cannotrequireit; the SDK then throws anUpshiftSDKErrorsaying so). Node 22.12–22.x also prints a harmlessExperimentalWarningabout loading an ES module withrequire(). Browsers are unaffected.
Adapter API
| Method | Returns | Description |
|---|---|---|
vaultDeposit({ vaultId, assetCoinType, shareCoinType, amount, sender, minSharesOut?, slippageBps? }) | Promise<{ transaction, expectedShares, minSharesOut }> | Build an unsigned deposit. |
vaultRedeem({ vaultId, assetCoinType, shareCoinType, shares, sender, minAssetsOut?, slippageBps? }) | Promise<{ transaction, expectedGrossAssets, expectedAssets, minAssetsOut }> | Build an unsigned instant redemption. |
executeTransaction({ bytes, signature }) | Promise<string> (digest) | Execute a wallet-signed transaction and wait for it. |
isSuiAddress(address) | boolean | 0x + 64 hex chars. |
isSuiVault(chainId) | boolean | true for -2. |
network, rpcUrl | getters | The configured sui settings. |
Amounts are bigint in base units: amount and asset outputs in the deposit coin's decimals, shares in the share coin's (the contract requires both to match).
Get Vault Data
Sui vaults have chainId === -2 and version === 'sui-0'. The adapter methods take three fields from the vault:
const vaults = await sdk.getVaults();
const suiVault = vaults.find((v) => v.version === 'sui-0');
const vault = {
vaultId: suiVault.address, // the shared Vault object
assetCoinType: suiVault.depositAssets[0].address, // e.g. "0x…::usdc::USDC"
shareCoinType: suiVault.receipt.address, // the vault's share coin type
};getVault/getVaults read total assets, supply, decimals, pause state and the deposit cap from the vault's Move package (two RPC requests per vault). Reads include the management fee accrued since it was last charged, so they reflect the current NAV. TVL in getVaultTvl comes from the backend.
Get User Positions
Pass suiWallet to read the wallet's share-coin balance (coin objects plus its address balance):
const positions = await sdk.getVaultPositions({ suiWallet: account.address });
const position = positions.find((p) => p.vault === vault.vaultId);
const shares = BigInt(position?.walletBalance.raw ?? '0');Lower-level reads
The Sui export exposes the reads behind these calls:
import { Sui } from '@upshiftfinance/sdk';
const ref = { ...vault, network: 'testnet' as const };
const state = await Sui.getters.readSuiVaultState(ref, suiConfig);
// { totalAssets, totalSupply, sharePrice, decimals, paused, queueAttached,
// minDeposit, depositCap, maxDepositAmount, maxWithdrawalAmount,
// instantRedemptionFeeBps }
const shares = await Sui.getters.previewSuiDeposit(ref, 1_000_000n, suiConfig);
const { gross, net } = await Sui.getters.previewSuiRedemption(ref, shares, true, suiConfig);Vault Deposit
One transaction: charge the vault's management fee, deposit with a minimum share amount, and send the shares to the sender. The payment is selected from the sender's coins (and address balance) when the wallet signs.
import { useDAppKit, useCurrentAccount } from '@mysten/dapp-kit-react';
const dAppKit = useDAppKit();
const account = useCurrentAccount();
// 1. Build (dry-runs first; throws a typed error if the vault would reject it)
const { transaction, expectedShares } = await sdk.sui.vaultDeposit({
...vault,
amount: 1_000_000n, // 1 USDC at 6 decimals
sender: account.address,
});
// 2. Sign with the wallet
const signed = await dAppKit.signTransaction({ transaction });
// 3. Execute and wait
const digest = await sdk.sui.executeTransaction(signed);The transaction accepts down to the dry run's share amount less slippageBps (default 10, i.e. 0.1%). That absorbs rounding when other deposits land first and a few seconds of fee accrual, while a larger price move (e.g. an operator AUM report) still aborts with slippage. Pass minSharesOut for an exact minimum, or slippageBps: 0 to demand exactly the preview.
Vault Redeem (instant)
One transaction: charge the vault's management fee, redeem shares with a minimum asset amount net of the instant-redemption fee, and send the assets to the sender. Charging first means the redeemer pays their share of the fee accrued since it was last charged, rather than leaving it to the remaining holders.
const { transaction, expectedAssets } = await sdk.sui.vaultRedeem({
...vault,
shares, // from getVaultPositions
sender: account.address,
});
const digest = await sdk.sui.executeTransaction(await dAppKit.signTransaction({ transaction }));The minimum defaults to the dry run's net amount less slippageBps (default 0.1%); pass minAssetsOut or slippageBps to change it. A vault with a withdrawal queue attached has instant redemption off; vaultRedeem then throws with reason: 'queue_attached'.
Execute a Signed Transaction
executeTransaction takes the { bytes, signature } a wallet's signTransaction returns, executes it on the configured network, and waits until it is indexed. If it fails on chain it still waits for the failure to be indexed before throwing, so a retry built straight after sees the sender's updated coins.
You can also let the wallet execute (dAppKit.signAndExecuteTransaction({ transaction })); you then get the wallet's raw result instead of the SDK's typed errors and curator reporting.
Error Handling
| Error | When | What to do |
|---|---|---|
UpshiftVaultAbortError (VAULT_ABORTED) | The vault rejected the transaction — in the dry run, or on chain. reason is stable to branch on; module/constant name the contract error. | See the reasons below. |
UpshiftValidationError | Malformed address or amount, missing contract IDs, or a transaction that cannot be built (e.g. the sender lacks the coins). | Fix the input. |
UpshiftNetworkError | Every Sui endpoint failed. | Retry with backoff. |
UpshiftTransactionError | Executed, but not indexed in time. signature is the digest. | Check the digest before retrying. |
UpshiftSDKError (UNKNOWN) | The network refused the signed transaction (bad signature, or inputs changed since signing), or it failed for a non-abort reason. | Rebuild, sign again. |
reason | Meaning |
|---|---|
paused | The vault is paused. |
below_min_deposit / above_max_deposit | Outside the vault's per-call deposit bounds (minDeposit, maxDepositAmount). |
deposit_cap_exceeded | The deposit would take total assets past depositCap. |
above_max_withdrawal | Gross assets exceed maxWithdrawalAmount. |
insufficient_liquidity | The vault's idle assets cannot pay it now. |
queue_attached | Instant redemption is off; the vault uses a withdrawal queue. |
slippage | Output below the transaction's minimum (the price moved more than the tolerance). Rebuild, or pass a higher slippageBps. |
zero_output | Amount too small to mint shares or pay assets. |
version_retired | The transaction targets a retired package version. Point sui.packageId at the current upshift_vault package. |
other | Any other abort; see module and constant. |
import { UpshiftVaultAbortError } from '@upshiftfinance/sdk';
try {
const { transaction } = await sdk.sui.vaultRedeem({ ...vault, shares, sender });
} catch (err) {
if (err instanceof UpshiftVaultAbortError && err.reason === 'queue_attached') {
// instant redemption is off for this vault
} else {
throw err;
}
}Curator Notifications
As for Stellar, a redemption the vault rejects — in the dry run, or on chain via executeTransaction — is reported to the Upshift API for the vault's curator, with the chain family "sui", the vault, the redeeming account, the share amount and (on chain) the digest. The same environment gates and opt-out apply; see Curator Notifications on the Stellar page. The reporting endpoint does not accept Sui reports yet: it rejects them, and the SDK logs one console warning per rejected report (deduplicated per vault, account and failure for 10 minutes). Nothing else changes for the user.
Notes & Caveats
- Coin shares only. Vaults with PAS shares are not supported.
- No withdrawal queue in v1. Requesting, claiming and cancelling queued withdrawals are not available yet; redemption history and available redemptions are empty for Sui vaults.
- Loans and allocations are not available for Sui vaults;
getVaultLoansandgetVaultAllocationsthrow. - Self-deposit / self-redeem. Shares and assets go to
sender. - Vaults whose asset is SUI. The dry run does not reserve gas, so a deposit of the whole SUI balance passes
vaultDepositand then fails when the wallet builds it. Leave SUI for gas.