github.com/storm-trade/sdk-go covers both trading paths. The storm package drives v3: it builds and signs intents, submits them to the sequencer, and manages Smart Account deposits and withdrawals. The stormv2 package drives v2 with direct wallet transactions and appends the executor fee automatically.
- Source: storm-trade/sdk-go
- CLI examples:
cmd/examplefor v3,cmd/v2examplefor v2
go get github.com/storm-trade/sdk-go| Package | Purpose |
|---|---|
config | Networks, markets, assets, Direction |
storm | v3 client: Smart Account plus sequencer |
stormv2 | v2 client: direct wallet transactions |
sequencer | Trading API (V3) REST client |
oracle | Signed oracle prices, used by v2 margin operations |
client/vamm, client/vault, client/smartaccount, client/positionmanager | On-chain reads and Smart Account operations |
tlb, contracts | Order types, position state, error codes, TL-B schemas |
β‘ v3: Smart Account and Intents
Set up
ctx := context.Background()
pool := liteclient.NewConnectionPool()
pool.AddConnectionsFromConfigUrl(ctx, "https://ton-blockchain.github.io/testnet-global.config.json")
api := ton.NewAPIClient(pool, ton.ProofCheckPolicyUnsafe).WithRetry(10)
w, _ := wallet.FromSeed(api, strings.Split(seedPhrase, " "), wallet.V4R2)
factory := smartaccount.NewFactory(api, address.MustParseAddr(config.Networks[config.Testnet].FactoryAddress))
saAddr, _ := factory.GetSmartAccountAddress(ctx, w.WalletAddress())
client := storm.NewClient(config.Testnet,
storm.WithTONApi(api),
storm.WithWallet(w),
storm.WithSmartAccount(saAddr),
storm.WithSigner(ed25519.PrivateKey(signingKey)),
storm.WithClockSkew(5*time.Second),
)The signing key is an ED25519 key registered on the Smart Account. It is separate from the TON wallet key: the wallet pays for deposits and withdrawals, the signing key authorizes intents.
First deposit
The first deposit deploys the Smart Account and registers the signing key in one go. Amounts use the asset's own decimals.
amount := tlb.MustFromDecimal("100", 6) // 100 USDT
client.Deposit(ctx, "USDT", &amount, storm.WithInit(), storm.WithPublicKey(pubKey))
// later
client.Deposit(ctx, "USDT", &amount)
client.Withdraw(ctx, "USDT", &amount)Passing a public key without WithInit() on an existing account fails with contract error 115. Use client.AddPublicKey(ctx) to register an extra key later.
Orders
Order amounts and sizes use 9 decimals, leverage is scaled by 10^9.
btc, _ := client.Market("BTC", "USDT")
amount := tlb.MustFromDecimal("100", 9)
// Market order with SL/TP attached as extra intents
sl := tlb.MustFromTON("60000")
tp := tlb.MustFromTON("80000")
res, err := client.PlaceMarketOrder(ctx, btc, config.Long, &amount, 3_000_000_000,
storm.WithStopLoss(&sl), storm.WithTakeProfit(&tp))
// Limit and stop-limit
limit := tlb.MustFromTON("65000")
res, _ = client.PlaceLimitOrder(ctx, btc, config.Long, &amount, 3_000_000_000, &limit)
stop := tlb.MustFromTON("64000")
res, _ = client.PlaceStopLimitOrder(ctx, btc, config.Long, &amount, 3_000_000_000, &limit, &stop)
// Cancel by hash from the result
client.CancelOrder(ctx, res.OrderHash)Every order call returns *PlaceOrderResult with QueryID, OrderHash, the ids and hashes of attached SL/TP, and the raw sequencer response including the emulation trace. OrderHash is what you pass to CancelOrder and to GET /intent/{hash}.
Positions
size := tlb.MustFromDecimal("0.001", 9)
client.ClosePosition(ctx, btc, config.Long, &size)
client.ClosePositionFull(ctx, btc, config.Long) // reads the size on-chain
trigger := tlb.MustFromTON("60000")
client.PlaceStopLoss(ctx, btc, config.Long, &size, &trigger)
client.PlaceTakeProfit(ctx, btc, config.Long, &size, &trigger)
margin := tlb.MustFromDecimal("50", 9)
client.AddMargin(ctx, btc, config.Long, &margin)
client.RemoveMargin(ctx, btc, config.Long, &margin)Options worth knowing
| Option | Effect |
|---|---|
WithClockSkew(5*time.Second) | Compensates local clock drift in intent expirations. Recommended |
WithGasless() | Gasless execution mode |
WithExpiration(ts) | Custom intent expiration, Unix seconds |
WithQueryID(id) | Override the query id. The client tracks it for you otherwise |
Reading state
seq := sequencer.NewClient(sequencer.TestnetURL)
state, _ := seq.GetAccountState(ctx, saAddr)
balances, _ := seq.GetBalances(ctx, saAddr)
positions, _ := seq.GetPositions(ctx, saAddr)
intent, _ := seq.GetIntent(ctx, res.OrderHash)
status, _ := seq.GetStatus(ctx)
spot, _ := client.GetSpotPrice(ctx, btc)
amm, _ := client.GetAmmState(ctx, btc)
vault, _ := client.GetVaultData(ctx, "USDT")π v2: Direct Wallet Transactions
client := stormv2.NewClient(config.Testnet,
stormv2.WithWallet(w),
stormv2.WithTONApi(api),
)
btc, _ := client.Market("BTC", "USDT")
amount := tlb.MustFromDecimal("10", 6) // collateral decimals: 6 for USDT
res, _ := client.PlaceMarketOrder(ctx, btc, config.Long, &amount, 3_000_000_000,
stormv2.WithStopLoss(&sl), stormv2.WithTakeProfit(&tp), stormv2.WithReferralID(123))
fmt.Println(res.Hash) // TON transaction hash
// Cancel by order type and index: 0 SL, 1 TP, 2 limit, 3 market
client.CancelOrder(ctx, btc, config.Long, 2, 0)Differences from v3 that matter in code:
v3 storm | v2 stormv2 | |
|---|---|---|
| Result | Intent hash, sequencer response | TON transaction hash |
| Cancel | By order hash | By order type and index |
| Amounts | 9 decimals | Collateral decimals |
| Oracle data | Injected by the sequencer | Fetched by the SDK for margin operations |
| Executor fee | Not needed | Appended as a second message |
π§ͺ CLI
The repository ships a CLI that exercises every call above. It is the quickest way to check your keys and environment before writing code.
export STORM_SEED="word1 word2 ... word24"
export STORM_PRIVATE_KEY=<ed25519 hex> # v3 only
export STORM_NETWORK=testnet # or mainnet
go run ./cmd/example keygen
go run ./cmd/example deposit TON 0.5 --init
go run ./cmd/example order market BTC USDT long 100 3 --sl=60000 --tp=80000
go run ./cmd/example positions
go run ./cmd/example close-all BTC USDT long
go run ./cmd/v2example order limit BTC USDT long 10 3 --limit=60000
go run ./cmd/v2example orders BTC USDT long