UpshiftDocs

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:

  1. sdk.sui.vaultDeposit() / sdk.sui.vaultRedeem() dry-run the transaction with the sender's own coins and return an unsigned Transaction. A vault that would reject it (paused, a limit, a withdrawal queue attached) fails here, before the wallet is asked to sign.
  2. The user's wallet signs it (e.g. with dApp Kit).
  3. 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
  },
});
OptionRequiredDescription
networkNo'mainnet' (default) or 'testnet'.
rpcUrlNoYour 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, registryIdUntil the SDK ships themLong-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/sui only on the first Sui call. In Node.js that needs Node ^20.19 or >=22.12 (@mysten/sui is ESM-only, and older releases cannot require it; the SDK then throws an UpshiftSDKError saying so). Node 22.12–22.x also prints a harmless ExperimentalWarning about loading an ES module with require(). Browsers are unaffected.

Adapter API

MethodReturnsDescription
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)boolean0x + 64 hex chars.
isSuiVault(chainId)booleantrue for -2.
network, rpcUrlgettersThe 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

ErrorWhenWhat 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.
UpshiftValidationErrorMalformed address or amount, missing contract IDs, or a transaction that cannot be built (e.g. the sender lacks the coins).Fix the input.
UpshiftNetworkErrorEvery Sui endpoint failed.Retry with backoff.
UpshiftTransactionErrorExecuted, 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.
reasonMeaning
pausedThe vault is paused.
below_min_deposit / above_max_depositOutside the vault's per-call deposit bounds (minDeposit, maxDepositAmount).
deposit_cap_exceededThe deposit would take total assets past depositCap.
above_max_withdrawalGross assets exceed maxWithdrawalAmount.
insufficient_liquidityThe vault's idle assets cannot pay it now.
queue_attachedInstant redemption is off; the vault uses a withdrawal queue.
slippageOutput below the transaction's minimum (the price moved more than the tolerance). Rebuild, or pass a higher slippageBps.
zero_outputAmount too small to mint shares or pay assets.
version_retiredThe transaction targets a retired package version. Point sui.packageId at the current upshift_vault package.
otherAny 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; getVaultLoans and getVaultAllocations throw.
  • 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 vaultDeposit and then fails when the wallet builds it. Leave SUI for gas.