Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ default-members = [
]

[workspace.package]
version = "3.1.0"
version = "3.2.0"
edition = "2024"
license = "LicenseRef-Acki-Nacki-Node-License"
license-file = "LICENSE.md"
Expand Down
43 changes: 41 additions & 2 deletions bee_sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ const res = await deploy_multisig_via_giver({
// keys? — owner keypair; generated when omitted, always returned
// owners_pubkey? — custodians ["0x…"] (uint256[]), default [owner]
// req_confirms?, req_confirms_data? — default 1
// giver_value? — SHELL (ECC[2]) gas top-up, default "10000000000"
// giver_ecc? — extra ECC, { currency_id: "amount" }
// giver_value? — SHELL (ECC[2]) gas top-up, default "1000000000000000"
// giver_ecc? — extra ECC, Map<currency_id, "amount">
// wait_for_active? — wait until Active, default true
// code? — deploy a different multisig build (see below)
});

console.log(res.address); // 0x… canonical <dapp>::<account>
Expand All @@ -95,6 +96,44 @@ const balances = await multisig_balances({
// e.g. { "2": "10000000000" } (1 = NACKL, 2 = SHELL, 3 = USDC)
```

#### Choosing the multisig build

`code` picks which contract gets deployed. Three forms:

```ts
// 1. Omit it — the SDK's default build (DexDo flat Multisig).
await deploy_multisig_via_giver({ endpoints });

// 2. A build vendored in the SDK, by name. No `.tvc` to ship from the frontend.
await deploy_multisig_via_giver({
endpoints,
code: "update_custodian_v2", // UpdateCustodianMultisigWallet v2.1.0
});

// 3. Your own build.
import abi from "./MyMultisig.abi.json";
await deploy_multisig_via_giver({
endpoints,
code: { tvc_b64: myTvcBase64, abi }, // `abi`: object or string
});
```

Vendored builds:

| `code` | contract | code hash |
|---|---|---|
| *(omitted)* | DexDo flat Multisig | — |
| `"update_custodian_v2"` | `UpdateCustodianMultisigWallet` v2.1.0, `sold 0.81.0` | `09f596d5bb4f63d7f2b18020ee0b7c9e88114dc90010389cc594c67954655ded` |

In form 3, `tvc_b64` and `abi` are both required and must come from the *same*
build: on ABI ≥ 2.3 the state-init data cell is rebuilt from the ABI's `fields`
before the address is hashed, so mixing one build's code with another's ABI
derives a different address whose storage layout the code doesn't agree with.
Sending only one half is an error, not a default.

The build is part of the address, so each one lands somewhere different — the
returned `address` is always the one that was funded and deployed.

### Multifactor wallet

```ts
Expand Down
11 changes: 8 additions & 3 deletions bee_wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,12 @@ tokio = { optional = true, version = "1.49.0", features = ["rt", "macros"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
dodex-contracts = { git = "https://github.com/gosh-sh/dodex-backend.git", tag = "v1.0.0", package = "dodex-contracts" }

[dev-dependencies]
# Native integration tests (`tests/integration.rs`, `tests/dex_flows/`) only.
# These pull the native tvm/kit/dodex graph, so they MUST stay off the wasm32
# build — otherwise `wasm-pack test` (which forces `--tests`) drags native-only
# crates (e.g. errno) into wasm32 and fails to compile. The matching test files
# are guarded with `#![cfg(not(target_arch = "wasm32"))]`.
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
ackinacki-kit = { git = "https://github.com/gosh-sh/ackinacki-kit.git", tag = "v3.0.0", features = ["default", "contracts"] }
base64 = "0.22.1"
bee-connect = { path = "../bee_connect" }
Expand All @@ -96,13 +101,13 @@ bee-crypto = { path = "../bee_crypto" }
bee-miner = { path = "../bee_miner" }
serde_json = "1"
tokio = { version = "1.49.0", features = ["rt", "macros", "time"] }

# DEX façade (`Dex`, halo2 prover, `proof` helpers) for the wallet-coupled
# full-flow tests under `tests/dex_flows/` — the two multifactor flows that
# exercise bee-wallet + DEX together. Native-only: pulls the heavy halo2 graph.
# Raw contract wrappers come from `dodex-contracts` (a regular dep above).
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
dodex-sdk = { git = "https://github.com/gosh-sh/dodex-backend.git", tag = "v1.0.0" }

# wasm-bindgen-test backs the `#[wasm_bindgen_test]` unit tests in
# `adapters/wasm` (run via `wasm-pack test --node`). Kept off the native build.
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3.56"
70 changes: 70 additions & 0 deletions bee_wallet/assets/multisig/PROVENANCE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Multisig assets — provenance

Two builds are vendored here:

| build | files | selected by | code hash |
|---|---|---|---|
| DexDo flat Multisig (**default**) | `Multisig.{tvc,abi.json}` | `code` omitted | `.tvc` sha256 `d3b38bca…` |
| `UpdateCustodianMultisigWallet` v2.1.0 | `v2/UpdateCustodianMultisigWallet.{tvc,abi.json}` | `code: "update_custodian_v2"` | `09f596d5bb4f63d7f2b18020ee0b7c9e88114dc90010389cc594c67954655ded` |

A caller can also pass its own build as `code: { tvc_b64, abi }` — see
"Any other build" below.

## Default build — DexDo flat Multisig

This is the **DexDo** multisig: `Multisig.abi.json` + `Multisig.tvc` are the flat
Multisig that DexDo uses, sourced from the DexDo repo:

Expand All @@ -17,3 +29,61 @@ Note: this TVC differs from `ackinacki-kit`'s `contracts/abi/multisig/Multisig.t
DexDo's build so addresses/code match what DexDo expects.

Current `Multisig.tvc` sha256: d3b38bcac8f60c1274f6099fc1e75746c02a2ff22af4efc689a754fd087a86fb

## `v2/` — UpdateCustodianMultisigWallet v2.1.0

Vendored **verbatim** (not recompiled) from `gosh-sh/acki-nacki` branch `dev`,
path `contracts/0.81.0_compiled/updatecustodianmultisigwallet_v2/` — the merged
form of PR #2413 (`dev` commit `6ad89549a0b8`, "new Contracts/multisig (#2413)").
Upstream names carry a `_v2` suffix that is redundant inside `v2/`, so they are
stored here without it; the git blob SHAs match upstream exactly:

upstream UpdateCustodianMultisigWallet_v2.tvc 7150 B blob 9610c471dce949f6ec84b096711cb7f43c78343b
upstream UpdateCustodianMultisigWallet_v2.abi.json 10856 B blob 90b8f8518666a49e1caf30aeec332fcb22ab7311

.tvc sha256 535e180e85ee019c23631c6046449fa2a5536d88f55b26d64e026d671e82d520
code hash 09f596d5bb4f63d7f2b18020ee0b7c9e88114dc90010389cc594c67954655ded
compiler sol 0.81.0

Refreshed from `dev` on 2026-07-27, superseding the pre-merge artifact taken from
branch `contracts/multisig` (`.tvc` 7147 B, sha256 `3e680a80…`, code hash
`31e402bb…`). The ABI is byte-identical across the two; only the compiled code
moved, so the **derived address of a v2 deploy changed** — anything that recorded
a v2 address computed before this refresh must recompute it.

To re-verify or refresh:

gh api "repos/gosh-sh/acki-nacki/contents/contracts/0.81.0_compiled/updatecustodianmultisigwallet_v2?ref=dev" \
--jq '.[] | "\(.name) \(.sha)"'
git hash-object v2/UpdateCustodianMultisigWallet.tvc # must match the blob above

The code hash is read straight out of the `.tvc` with
`tvm_client::boc::decode_state_init` (`code_hash` = repr hash of the state-init
code cell) — the same value a node reports for a deployed account.

The `.tvc` sha256 is pinned in a unit test (`vendored_v2_asset_is_pinned`) and the
code hash is asserted on-chain by the integration tests, so a swapped file fails
in CI rather than on a network.

Relative to the default build its ABI is a strict superset by function set: all
17 functions identical (constructor included), plus `submitUpdateCode(cell,cell)
-> uint64` and `confirmUpdateCode(uint64)`. Its `fields` add `m_requestsMaskCode`
and `m_code` — which is why it must be deployed with its own ABI (see below).

Verified on shellnet after the 2026-07-27 refresh, through
`deploy_multisig_via_giver` with `code: "update_custodian_v2"`
(`test_deploy_multisig_via_giver_update_custodian_v2`): account Active at
`0480508b8bf07b3df8830ab758a61be0ee9b36427d9780466a66712277bb468c::…`, on-chain
code hash `09f596d5…` as above.

## Any other build

`MultisigDeploySpec.code` (`code: { tvc_b64, abi }` over the wasm boundary)
deploys a build that isn't vendored here — see `MultisigCode` in
`src/services/multisig.rs`. Both halves are required, and that is not a
formality: on ABI ≥ 2.3 the state-init **data** cell is rebuilt from the ABI's
`fields` list before the address is hashed (`tvm_sdk::ContractImage::update_data`
→ `encode_storage_fields`), so an ABI is part of the address. Pairing one build's
code with another's ABI silently derives a *different* address whose storage
layout the code does not agree with — measured: v2's code with the default ABI
lands on a third address, even though the ABIs share every function signature.
Loading
Loading