@phasis-lab/sdk
v0.1.2
Published
TypeScript SDK for the Phasis options protocol on Sui (option_deepbook Move package).
Readme
@phasis/sdk
TypeScript client for the Phasis options protocol on Sui — wraps every
Move entry in the option_deepbook package + slot-coin scaffolding, exposes
typed read helpers for the live shared objects, and ships a runnable end-to-end
sample against testnet.
Status: internal preview (Track S of the parallel-tracks design). Not published to npm. Consumers depend on it as a workspace package, or via a direct
pnpm packtarball.
Install
The SDK is a workspace package — there is no public registry release. Two common consumption shapes:
# Inside this monorepo (Next.js frontend, Rust off-chain bridge tests, etc.)
pnpm install # at repo root, hoists @phasis/sdk
# From a sibling project, via tarball
cd sdk && pnpm pack # produces phasis-sdk-0.0.1.tgz
cd ../my-app && pnpm add ../sdk/phasis-sdk-0.0.1.tgzPeer/runtime deps:
pnpm add @mysten/sui@^2.17 @mysten/bcs@^2.0Node 20+ required (uses node: builtins and top-level ESM).
Quick start
import { SuiGrpcClient } from '@mysten/sui/grpc';
import { Transaction } from '@mysten/sui/transactions';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import {
getRegistry, listMarkets, getAccount,
genUserEntry, // generated tx builders
placeTrade, intentToSide, TradeIntent, DeepBookSide,
} from '@phasis/sdk';
const client = new SuiGrpcClient({ network: 'testnet', baseUrl: 'https://fullnode.testnet.sui.io:443' });
const kp = Ed25519Keypair.fromSecretKey(/* … */);
// 1. Read live protocol state.
const registry = await getRegistry(client, REGISTRY_ID);
const markets = await listMarkets(client, REGISTRY_ID);
// 2. Open a margin account (Quote = USDC).
const txOpen = new Transaction();
genUserEntry.openAccount({
package: PACKAGE_ID,
typeArguments: [registry.quoteCoinType],
arguments: { registry: REGISTRY_ID },
})(txOpen);
await client.signAndExecuteTransaction({ signer: kp, transaction: txOpen });
// 3. Deposit USDC into the account.
const txDep = new Transaction();
const [coin] = txDep.splitCoins(USDC_COIN_OBJECT_ID, [1_000_000_000n]);
genUserEntry.depositUsdc({
package: PACKAGE_ID,
typeArguments: [registry.quoteCoinType],
arguments: { registry: REGISTRY_ID, account: ACCOUNT_ID, coin },
})(txDep);
await client.signAndExecuteTransaction({ signer: kp, transaction: txDep });
// 4. Place an open-long order — TODO(series): wire when series are listed.
// (series listing blocked tonight on DEEP acquisition for DeepBook pool fee.)
const txTrade = new Transaction();
placeTrade(txTrade, {
registryId: REGISTRY_ID,
marketId: MARKET_ID,
seriesId: SERIES_ID, // TODO(series)
accountId: ACCOUNT_ID,
snapshotId: STRESS_SNAPSHOT_ID,
poolId: DEEPBOOK_POOL_ID, // TODO(series)
clockId: '0x6',
side: intentToSide(TradeIntent.OpenLong, null),
orderType: 0, // NO_RESTRICTION
qty: 1n,
limitPrice: 100_000_000n, // raw u64 price
slotCoinType: '0xSLOTS::opt_slot_01::OPT_SLOT_01', // TODO(series)
});
await client.signAndExecuteTransaction({ signer: kp, transaction: txTrade });
// 5. View account state.
const account = await getAccount(client, ACCOUNT_ID);
console.log(account.balanceUsdc, account.lockedMargin, account.positions);For a runnable read-only smoke test see examples/open-and-trade.ts:
cd sdk
pnpm tsx examples/open-and-trade.tsDemo scripts (user flows)
Runnable from sdk/. All default to dry-run (simulate — no gas, no state
change); pass --commit to execute live. --commit needs a funded wallet, and
deposit additionally needs Circle bridged testnet USDC
(https://faucet.circle.com, select Sui testnet).
| Script | Flow | Flags |
|---|---|---|
| examples/read-account.ts | Dump balance / positions / open orders (read-only) | — |
| examples/account-usdc.ts | Open account → deposit → withdraw USDC | --amount <baseUnits>, --withdraw <baseUnits>, --commit |
| examples/trade-and-cancel.ts | Place an order → cancel it | --series <id>, --side buy\|sell, --qty, --price, --order-id, --commit |
| examples/refresh-margin.ts | Recompute margin lock for BTC positions | --commit |
Operator-wallet key resolution (shared examples/_lib.ts): SUI_KEY_B64
(base64 32-byte raw) → SUI_PRIVKEY (bech32 suiprivkey1…) →
~/.sui/sui_config keystore.
pnpm tsx examples/read-account.ts
pnpm tsx examples/account-usdc.ts --amount 10000000 # dry-run, 10 USDC
pnpm tsx examples/account-usdc.ts --amount 10000000 --commit # live
pnpm tsx examples/trade-and-cancel.ts --side buy --qty 1 --price 100000000
qty/priceunits depend on the DeepBook pool's lot/tick config; the script defaults are conservative placeholders so the PTB assembles and simulates.
Module reference
| Module | One-line | Source |
|---|---|---|
| constants | Package + shared-object IDs, env-overridable | src/constants.ts |
| types | TS domain types + enum mirrors (Asset, MarketState, TradeIntent, …) | src/types.ts |
| account | getAccount view parser (lock_by_asset, positions VecMap walk) | src/account.ts |
| registry | getRegistry / listMarkets / getMarket / listSeries / getSeries (dynamic-field-aware) | src/registry.ts |
| trade | placeTrade / cancelOrder / refreshAssetMargin + intentToSide helper | src/trade.ts |
| oracle | updateIv / updateStressSnapshot publisher entries + view helpers | src/oracle.ts |
| settlement | 7 cranker entries + adminForceSettlePrice (settle_price, mark_settling, settle_user_for_series, retire_series, retire_market, halt, cancel_orders) | src/settlement.ts |
| liquidation | liquidate tx wrapper + isLiquidatable off-chain pre-check | src/liquidation.ts |
| admin | Fee pool topup/drain, pause, version migration, Pyth wiring, role mgmt (12 role entries) | src/admin.ts |
| genUserEntry / genAuthEntry | Generated PTB builders for every Move entry (codegen output, do not hand-edit) | src/generated/option_deepbook/ |
The high-level convenience wrappers (placeTrade, crankerHalt, …) all
delegate to the matching generated builder — they exist to fix defaults
(package ID, Quote type), normalize argument names, and expose helpers like
intentToSide. Direct access to the generated builders is available via the
genUserEntry / genAuthEntry namespaced exports for advanced use cases.
Testnet object IDs
Synced from deployments/testnet-public.json (chain_id 4c78adac).
| Item | Object ID |
|---|---|
| option_deepbook package (v2 — use for Move calls) | 0xa1c2b85caf592ce3195d17d328251501baa2191892146a97dc65d08cb7566cd3 |
| option_deepbook original-id (use for type names) | 0x187cfd9dcd7691ff9fc419f5e4f7ec4e15eed3cf553a766521ee9da03e345a21 |
| option_deepbook_slots package | 0xc976759840f343392a44c7c1611034d762c2b5ac04f4ceb5792d1af734ce28f3 |
| OptionsRegistry (shared) | 0xc65d95539d6128d82ff73d40b8be59b76f748961d16e8c85323bf56287bd78bf |
| BTC Market (shared, expires 2026-06-03) | 0x97e95ad05f4132fa78b222a60027d87b1608afbee5429c58945c0972633d4434 |
| Quote coin type (USDC on testnet) | 0x949572061c09bbedef3ac4ffc42e58632291616f0605117cec86d840e09bf519::usdc::USDC |
| DeepBook v3 testnet Registry | 0x98dace830ebebd44b7a3331c00750bf758f8a4b17a27380f5bb3fbe68cb984a7 |
| DEEP coin type (testnet) | 0x36dbef866a1d62bf7328989a10fb2f07d769f4ee587c0de4a0a256e57e0a58a8::deep::DEEP |
| IVOracle — BTC | 0xc933d2b2c6386b708007c8611041f0208873d610c0fa3f13e4ff4ac53171ac77 |
| IVOracle — ETH | 0x3d4ad849a316e72e0130f206552cd3d20bc2faae6aa305125fb2a80bbca28598 |
| IVOracle — SUI | 0x03ba857bfdfab6da28a72037323ca874be3343d73c5fe4107cf619a614cd398c |
| StressSnapshot — BTC | 0x8d6be18acc427ee6acf8e73abd122cbac3e930443e661b1f660b55f591b10ef3 |
| StressSnapshot — ETH | 0x50f33f2496732df9f0db17722b365ee71712910bf1cbc59c83b931923172d082 |
| StressSnapshot — SUI | 0x6ad65199b28810b6f2ccfa8414871f558beb5350981cf545577c480f4790c564 |
Env overrides accepted (consumed at SDK import time, with 0x0 fallbacks):
NEXT_PUBLIC_PACKAGE_ID=…
NEXT_PUBLIC_REGISTRY_ID=…
NEXT_PUBLIC_STRESS_SNAPSHOT_ID=…
NEXT_PUBLIC_IV_ORACLE_ID=…
NEXT_PUBLIC_DEEPBOOK_REGISTRY_ID=…
NEXT_PUBLIC_QUOTE_COIN_TYPE=…Most SDK helpers also take an explicit packageId / registryId / quoteCoinType
argument that overrides the env defaults — preferred for backend services that
need to talk to multiple deployments.
Codegen
PTB builders + BCS struct schemas under src/generated/ are produced by Sui's
TypeScript codegen tool (@mysten/codegen), driven by codegen.config.yml.
pnpm codegen # regenerate from ../option_deepbook Move.tomlWhen to run:
- After every Move plan completion that touches
option_deepbook/sources/**. - After a
sui client upgradeof the package (new entries become available). - After bumping the
@mysten/codegenversion.
Generated files are committed to source — first-clone consumers should not
need a working Move toolchain. If you re-run pnpm codegen, check the diff
carefully: parameter order changes in a Move entry surface as bytewise
identical PTBs but cause silent argument-misalignment bugs in callers.
Scripts
pnpm build # tsc → dist/
pnpm typecheck # tsc --noEmit
pnpm test # 26 unit tests (offline, no network)
pnpm test:integration # 6 read-only tests against live testnet (RUN_INTEGRATION=1)
pnpm dry-publish # npm pack --dry-run to inspect the tarball contents
pnpm codegen # regenerate src/generated/
pnpm tsx examples/open-and-trade.ts # end-to-end read-only smoke against testnetThe integration test is gated on RUN_INTEGRATION=1 (set by
pnpm test:integration) so plain pnpm test stays offline + fast. CI runs
unit tests on every push and integration on manual workflow_dispatch only.
Known limitations (pre-launch)
- No series listed yet. DeepBook v3 testnet has no public DEEP faucet —
list_seriesrequires DEEP for the pool-creation fee. As of 2026-05-27 this is gated on swapping SUI→DEEP via the testnet DEEP_SUI pool. Trade-flow code paths in the sample script are flaggedTODO(series). - Pyth Lazer is wired live (v8). Settlement verifies signed Pyth Lazer
updates on-chain (
parse_and_verify_le_ecdsa_update); the off-chain cranker forwards the signed blob. The oldadmin_set_price_info_object_id/ PriceInfoObject path was removed in the Lazer swap. Seescripts/lazer_setup.tsand thelazerblock indeployments/testnet-public.json. feePoolValuereports0n. The registry view does not yet fetch the dynamic-field-backed fee pool Balance. TODO marker insrc/registry.ts::getRegistry.- Codegen output is not auto-validated. A Move struct field rename
passes typecheck but produces silently-incorrect PTBs. Run the
integration test (
pnpm test:integration) after every codegen to verify PTBs still passdevInspectTransactionBlockagainst the live package.
Repo layout
sdk/
├── examples/
│ └── open-and-trade.ts # T10 sample — read-only, dry-run only
├── src/
│ ├── constants.ts # env-overridable IDs
│ ├── types.ts # TS domain types + enum mirrors
│ ├── account.ts # getAccount view parser
│ ├── registry.ts # registry / market / series views
│ ├── trade.ts # placeTrade, cancelOrder, intentToSide
│ ├── oracle.ts # IV + stress publisher entries
│ ├── settlement.ts # 7 cranker + adminForceSettlePrice
│ ├── liquidation.ts # liquidate + isLiquidatable
│ ├── admin.ts # fee pool, pause, role mgmt, Pyth wiring
│ ├── index.ts # public entry point
│ ├── internal/parse.ts # shared VecMap / VecSet / TypeName parsers
│ └── generated/ # codegen output — do not hand-edit
│ └── option_deepbook/*.ts
├── tests/integration/
│ └── testnet-smoke.test.ts # gated on RUN_INTEGRATION=1
├── codegen.config.yml # sui-ts-codegen config
├── package.json
├── tsconfig.json
└── README.md