* feat(sui): spend SUI and tokens held in the address balance (SIP-58)
wallet-core could only spend Coin objects, so funds that sit in the address
balance (no Coin object) could not be sent, staked or used for gas.
Sui.proto:
- address_balance_amount on PaySui, PayAllSui and RequestAddStake: MIST withdrawn
from the sender's address balance (FundsWithdrawal + 0x2::coin::redeem_funds)
next to the owned input coins.
- Pay: coin_type + address_balance_amount for tokens held in the address balance.
- SigningInput.valid_during (min_epoch, max_epoch, chain, nonce): pay gas from the
address balance (empty gas payment + ValidDuring expiration).
Builder:
- A pure withdrawal redeems exactly the amount of each recipient (no split, no
destroy_zero, no left-over Coin); with owned coins the withdrawal is merged into
the first coin.
- Validation errors instead of silent ignoring: gas coin XOR valid_during, window
[N, N] or [N, N+1], gas_budget > 0, overflow, coin_type must be a struct type
tag, valid_during with SignDirect/RawJson.
Tests: literal-structure builder tests, signing goldens, SIP-58 transactions that
were executed on devnet (decode and re-encode byte-identically; generator and
regeneration steps in the fixtures README, own Move test coin included), and the
sui_native_cli example used by the tsdk devnet test.
* chore(rust): fix clippy findings in tests of other crates
Lint-only changes found while running clippy over all targets
(needless_update: redundant `..Default::default()`, assertions_on_constants).
No behaviour change.
* build(ios): raise the minimum iOS deployment target to 15.0
Xcode 27.1 refuses deployment targets below 15.0, so the framework could not be
built with it. Raised in the xcodegen project, the Podfile (and its lock), the
podspec generated by tools/ios-release and the Package.swift generated by
tools/ios-xcframework-release.
This raises the minimum OS of a public library: review it on its own.
* build(apple): adopt Xcode 27.1 for iOS, macOS and CI
Xcode 27.1 only accepts iOS 15.0+ and macOS 12.0+ deployment targets (it rejects
10.14 with "supported deployment target versions is 12.0 to 27.0"). Raise every
Apple minimum that was still below, on top of the iOS 15.0 change in swift/project.yml
and the Podfile:
- swift/common-xcframework.yml: iOS 15.0, macOS 12.0 (the static WalletCoreCommon
xcframework, including the macOS and Mac Catalyst slices).
- WalletCore.podspec, WalletCoreSwiftProtobuf.podspec, Package.swift, and the podspec /
Package.swift written by tools/ios-release and tools/ios-xcframework-release.
- Samples: osx/cocoapods (macOS 12.0) and kmp/iosApp (iOS 15.0).
tools/rust-bindgen: export IPHONEOS_DEPLOYMENT_TARGET=15.0 and MACOSX_DEPLOYMENT_TARGET=12.0.
Without them the cc crate compiled libsecp256k1 for the newest SDK, so the macOS slice
contained objects that require macOS 27.0 (linker warnings for apps supporting older macOS).
CI: tools/select-xcode makes Xcode 27.1 the active Xcode and fails loudly when it is
missing; it runs first in the iOS CI job and the macOS Rust job.
Verified locally with Xcode 27.1: tools/ios-build archives macOS, Mac Catalyst, iOS device
and iOS simulator, and every slice reports minos 15.0 (iOS) / 12.0 (macOS) for wallet-core's
own code.
* fix(sui): address-balance review fixes (gas reservation unit, zero-amount recipient, owner test)
- Pay: the gas budget is reserved from the SUI address balance, so "withdrawal + gas
budget must not overflow u64" only applies to a SUI withdrawal. A large amount of
another token was rejected for no reason.
- A recipient whose amount is 0 produced a zero-amount redeem_funds (the chain does not
accept a zero reservation): reject it locally with invalid_params.
- test_sui_signer_address_is_the_withdrawal_owner counted 32-byte windows and its comment
contradicted its assertion. It now decodes the TransactionData and checks sender, gas
owner, the empty gas payment and the FundsWithdrawal { Sender, MaxAmountU64 }.
Tests for the first two; both were checked to fail with the fix removed.
* fix(sui): reject more balance withdrawals than the network allows
The node accepts at most 10 balance-withdraw reservations per transaction: every
FundsWithdrawal input plus one implicit reservation for gas paid from the address
balance (hard-coded `max_withdraws = 10` in sui-types `validity_check`, checked at
signing time). A pure withdrawal redeems once per distinct recipient, so 10 or more
recipients with gas from the address balance (or 11+ with a gas coin) would only fail
at the node after signing. Reject it with invalid_params and a clear message.
Checked against the Sui sources and docs while reviewing the previous fix:
- zero-amount reservations are rejected by the node ("amount must be non-zero"), the
TypeScript SDK resolves a zero amount to coin::zero (no withdrawal);
- the gas budget is an implicit SUI withdrawal summed per (account, type), so it only
adds up with SUI withdrawals, not with another token;
- the limit of 10 and its counting.
Boundary also executed on devnet: 9 recipients + gas passes the node.
* revert(sui): drop the local limit of 10 withdrawal reservations; document it instead
e018d2ac mirrored a limit that the node hard-codes (`max_withdraws = 10`, with a TODO
to make it a protocol config). A copy of a chain-owned constant goes stale silently: if
Sui raises it, wallet-core would keep rejecting sends the node accepts until a wallet-core
release and an app release ship. The cost of not checking is only a later, clear error
from the node ("Maximum number of balance withdraw reservations is 10") before anything
is executed; no funds are at risk either way. The app never sends to more than one
recipient from the address balance.
The network limit is documented in Sui.proto next to the per-recipient redemption it
affects. Boundary (9 recipients + gas) stays covered by the opt-in tsdk devnet test.
* test(sui): replace network example scripts with mainnet byte-for-byte tests
Drop the devnet fixture generator and the native signing CLI: the project
does not commit scripts that talk to a network. Instead every SIP-58
transaction shape that landed on Sui mainnet is rebuilt from Sui.proto and
must match the chain 1:1: the unsigned transaction bytes, the signature
(verified through the compile path) and the transaction digest.
Covers PaySui, PayAllSui, token Pay, RequestAddStake and
RequestWithdrawStake, TransferObject, with an owned coin and/or the address
balance, gas from the address balance or from a coin, and several
recipients. Rejected inputs have no transaction and stay unit tests.
* test(sui): cover builder input deduplication and rejected SIP-58 inputs
Add tests for the shared and owned object merge rules, forced separate pure
inputs, receiving objects, make_obj_vec, Pay argument validation, and the
PayAllSui, PaySui and empty payload rejections.
* ci(apple): run the Xcode 27.1 jobs on the xcode-27 runner image
macos-15-xlarge and macos-latest-xlarge ship Xcode 16.4, so tools/select-xcode
failed. The xcode-27-xlarge image has Xcode 27.1 at /Applications/Xcode_27.1.app
and only an iOS 27.0 simulator runtime, so the test destination no longer pins 26.2.
* test(sui): embed the devnet fixtures so the tests run in WASM
The WASM runner cannot read the host file system, so reading the fixtures with
fs::read_dir panicked in test-wasm. Embed them with include_bytes! and check the
embedded list against manifest.json (and, natively, against the directory).
* test(sui): drop the devnet fixtures, mainnet transactions cover every shape
The five devnet .bcs files, their manifest, README, test coin and round-trip test
are replaced by the mainnet byte-for-byte tests in sui_sign_address_balance.rs.
* ci(ios): use a separate Rust cache on the xcode-27 runner
The iOS job failed in the Rust build with E0463 (can't find crate for zeroize_derive),
after the runner moved from macos-15 to xcode-27. Start from a fresh cache.
* fix(ios): keep the macOS minimum out of the host Rust builds
MACOSX_DEPLOYMENT_TARGET=12.0 reached the host proc-macros and broke a clean build
(E0463 can't find crate for zeroize_derive). Pass the macOS minimum to the C compiler
only, through CFLAGS for the two macOS targets: the secp256k1-sys objects still get
minos 12.0.
Also revert the changes that were not needed: the rust.yml runner and Xcode selection,
the rust-cache key, and the CocoaPods version line in Podfile.lock.
* ci(ios): temporary diagnostics for the generated Barz.swift
The iOS test build on the xcode-27 image fails with "Type 'Barz' has no member 'getInitCode'"
while the same sources build locally. Print what the generation produced. To be removed.
* ci(ios): remove the temporary Barz diagnostics
---------
Co-authored-by: tw-a-viter <252030450+tw-secret-name@users.noreply.github.com>