Skip to content

Migrate Hashi SDK into the monorepo as @mysten/hashi#1140

Open
Tzal3x wants to merge 2 commits into
mainfrom
alextzimas/sedefi-163-hashi-ts-sdk-integration
Open

Migrate Hashi SDK into the monorepo as @mysten/hashi#1140
Tzal3x wants to merge 2 commits into
mainfrom
alextzimas/sedefi-163-hashi-ts-sdk-integration

Conversation

@Tzal3x

@Tzal3x Tzal3x commented Jul 15, 2026

Copy link
Copy Markdown

Description

Migrates the Hashi TypeScript SDK (native BTC collateralization on Sui) from the standalone
hashi-ts-sdk repo into packages/hashi, renaming it
from @mysten-incubation/hashi (v0.5.0 on npm) to @mysten/hashi (SEDEFI-163).

  • Source, tests, and committed codegen bindings copied from the standalone repo and reformatted
    to monorepo prettier style (tabs, single quotes, import sort) — most of the diff is this
    mechanical reformat plus the generated src/contracts/** bindings.
  • package.json rewritten to monorepo conventions: @mysten/sui as a workspace:^ peer
    dependency, @mysten/codegen as a workspace devDependency, standard build/lint/test scripts.
  • Codegen (sui-codegen.config.ts) retargeted at the ../hashi sibling checkout
    (walrus/payment-kit pattern); bindings stay committed at the revision generated from
    MystenLabs/hashi cd2b81f.
  • Monorepo wiring: release-hashi.yml workflow (OIDC trusted publishing via
    _release-package.yml), oxlint ignore for packages/hashi/src/contracts, AGENTS.md
    sibling-repo/codegen entries, and a minor changeset — so the first publish under the new name is
    0.6.0 with a changelog entry explaining the rename.
  • Tests: all test files ported. Unit tests run in CI via the standard test task; integration
    tests remain env-gated (HASHI_E2E_*, devnet/localnet) — the standalone repo's Rust/Bitcoin-Core
    localnet CI harness is intentionally not ported.
  • Test type fixes: the new test:typecheck step surfaced latent type errors the standalone
    repo never caught (it never ran tsc over tests): unnarrowed TransactionHistoryItem union
    access in three integration tests, and a parse-only $kind tag in a BCS enum serialize input.
  • Docs are README-only for now (the standalone repo's comprehensive guide travels as the package
    README); a docs-site section can follow at graduation.

Post-merge follow-ups (outside this PR): dispatch release-hashi.yml (dry-run first), deprecate
@mysten-incubation/hashi on npm, archive the hashi-ts-sdk repo.

Test plan

  • pnpm turbo build --filter @mysten/hashi — typecheck + tsdown pass against workspace
    @mysten/sui 2.20.x (verifies the peer-range move from ^2.14.1)
  • pnpm --filter @mysten/hashi test — test-dir typecheck + 185 unit tests pass
  • pnpm turbo lint --filter @mysten/hashi + root oxlint — clean (0 findings on ported code)
  • pnpm changeset status@mysten/hashi queued for a minor bump
  • Codegen exercised end-to-end against the ../hashi sibling checkout (regenerates successfully;
    regenerated output not committed since that checkout is ahead of the pinned contract revision)

AI Assistance Notice

Please disclose the usage of AI. This is primarily to help inform reviewers of how careful they need to review PRs, and to keep track of AI usage across our team. Please fill this out accurately, and do not modify the content or heading for this section!

  • This PR was primarily written by AI.
  • I used AI for docs / tests, but manually wrote the source code.
  • I used AI to understand the problem space / repository.
  • I did not use AI for this PR.

🤖 Generated with Claude Code

Copies the Hashi SDK source, tests, and committed codegen bindings from
the standalone hashi-ts-sdk repo, reformatted to monorepo prettier
style. Rewrites package.json to monorepo conventions (workspace peer
deps, standard scripts), retargets codegen at the ../hashi sibling
checkout, and wires up the release workflow, oxlint ignore, AGENTS.md,
and a changeset.

Also fixes latent type errors in tests surfaced by the new
test:typecheck step (union narrowing in history lookups, parse-only
$kind tag in a BCS enum input); the standalone repo never type-checked
its test directory.
@Tzal3x
Tzal3x requested a review from a team as a code owner July 15, 2026 10:00
@Tzal3x
Tzal3x temporarily deployed to sui-typescript-aws-kms-test-env July 15, 2026 10:00 — with GitHub Actions Inactive
@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
sui-typescript-docs Ignored Ignored Preview Jul 15, 2026 11:02am

Request Review

Runs the @mysten/hashi integration tests against a localnet, path-filtered
to packages/hashi/**. Ported from hashi-ts-sdk's integration.yml with three
adaptations for this monorepo:

- ts-sdks has no hashi submodule, so clone MystenLabs/hashi at the bindings
  pin (cd2b81f) into ./hashi explicitly.
- @mysten/sui is a workspace:^ dep resolving to dist/, so build it first via
  `pnpm turbo build --filter @mysten/hashi` (mirrors seal-ci.yml).
- Use this repo's pinned action SHAs and node 24.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Tzal3x
Tzal3x temporarily deployed to sui-typescript-aws-kms-test-env July 15, 2026 11:02 — with GitHub Actions Inactive
@Tzal3x Tzal3x self-assigned this Jul 15, 2026
@Tzal3x

Tzal3x commented Jul 15, 2026

Copy link
Copy Markdown
Author

The Turborepo CI failure is unrelated to this PR: the Lint, Build, and Test job passes, and only the pnpm audit job fails because npm has retired the legacy audit endpoints (they now return HTTP 410), which the pinned pnpm version still calls — a repo-wide infra issue affecting all branches, not this change.

@@ -0,0 +1,15 @@
/**************************************************************

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these generated files should be marked as generated via linguist configs so they auto-collapse in reviews

bitcoinNetwork: BitcoinNetwork;
}

export interface HashiClientOptions<Name = 'HashiClient'> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the default Name here seems wrong, I assume this is meant to be hashi

I think actually string is probably a better default here. If something accepts HashiClientOptions only string would allow any options object with different names set

@hayes-mysten hayes-mysten left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this looks really good and aligns well with this repos best practices. Code looks clean and well organized.

I've gone over everything from a best practices and sdk usage perspective and it all looks pretty solid to me. I did not review it for correctness, edge cases, etc, and I did not really look at the tests or docs.

export interface HashiClientOptions<Name = 'HashiClient'> {
name?: Name;
/** Sui network — determines Hashi object IDs and default Bitcoin network. */
network: SuiNetwork;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we generally don't want to pass network separately, and instead get it from the client being extended


import type { UtxoLookupResult } from './types.js';

async function rpcCall(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like just making these kinds of methods take a generic so you can pass in the data type rather than always casting after calling it

guardianInfoProvider,
}: {
client: ClientWithCoreApi;
network: SuiNetwork;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

network should always come from the client, and we should throw if its unsupported

this.#packageId = resolvedPackageId;
this.#bitcoinNetwork = bitcoinNetwork ?? config?.bitcoinNetwork ?? 'testnet';
this.#btcRpcUrl = btcRpcUrl;
this.#graphqlUrl = graphqlUrl ?? defaultGraphqlUrl(network);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just create a graphql client here

}): Transaction => {
const tx = new Transaction();
const btcType = `${this.#packageId}::btc::BTC`;
const coin = tx.add(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we've added tx.coin and tx.balance (tx.coin is just an alias for coinWithBalance).

You also don't need to tx.add explicitly here you can just do const coin = conWithBalance() || tx.coin(..).

typeArguments: [`${this.#packageId}::btc::BTC`],
arguments: [balance],
});
tx.transferObjects([coin], options.recipient);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious about plans for if/when we start just sending users balances instead of coins

if (sender) {
const dummyAmount = snap.bitcoinWithdrawalMinimum + 1n;
const btcType = `${this.#packageId}::btc::BTC`;
const tx = new Transaction();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these methods just building exactly the same transactions as the client.tx transactions? This seems like we should be reusing those to get accurate gas estimates that stay in sync without manually keeping them in sync with changes

parentId: tableId,
name: {
type: 'address',
bcs: bcs.Address.serialize(suiAddress).toBytes(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we added a top level toBytes method to avoid the extra chained serialize call

* the results. Each element is either an object or an `Error` (for
* missing / deleted objects).
*/
async #batchGetObjects(objectIds: string[], include?: { content?: boolean }) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the core getObjects API already handles batching internally

}
}`;

const res = await fetch(this.#graphqlUrl, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should use the graphql client, which will properly type all the graphql requests for you

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are also a couple weeks away from having event queries work in grpc and the core APIs

@hayes-mysten

Copy link
Copy Markdown
Contributor

@clud-bot review

@clud-bot

clud-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

End of turn — I'll be woken when the reviewer reports back.

@clud-bot clud-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Hashi SDK monorepo migration — Approve

Reviewed the non-generated surface (generated src/contracts/** and pure prettier reformatting skipped as low-risk). No blocking or major issues.

What checks out:

  • Version 0.5.0 + a minor changeset is the correct pattern — changesets own the bump, and the CHANGELOG matches sibling packages. No hand-bumped version.
  • package.json deps/peer-deps cover all external src imports; @mysten/sui as workspace:^ peer + @mysten/codegen workspace devDep follows the walrus/payment-kit convention.
  • Both new workflows are sound: pull_request (not pull_request_target), no secrets referenced, actions SHA-pinned, external hashi clone pinned to cd2b81f9 (matches the CHANGELOG note and codegen config). OIDC trusted publishing mirrors the ~30 existing release-*.yml workflows.
  • The "test type fixes" are legitimate type-narrowing — the TransactionHistoryItem union guards and the $kind BCS tag are correct, not masking bugs.

Everything inline is non-blocking (mostly stale "submodule" wording inherited from the standalone repo). Fine to merge; worth a quick cleanup pass.

FYI, not a finding: in release-hashi.yml the if: github.ref == 'refs/heads/main' gate constrains the dispatch branch, not the ref input, so a maintainer can publish an arbitrary commit. This is byte-identical to every existing release-*.yml, so out of scope here.


### Patch Changes

- ff9a398: Bump hashi submodule to cd2b81f (no contract or binding changes)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (docs): "Bump hashi submodule to cd2b81f" — the monorepo has no hashi submodule; CI clones it as a separate checkout (hashi-ci.yml). Stale wording carried over from the standalone repo. Suggest "submodule" → "pin" (or "repo").


### Patch Changes

- 9422708: Add a package-level `README.md` so the npm landing page has a real overview (install, one quickstart snippet, link to the repo README for full docs). Also corrects stale `@mysten/hashi` references in the root README to the actual published name `@mysten/hashi`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (docs): Self-contradictory — "corrects stale @mysten/hashi references … to the actual published name @mysten/hashi" (same name on both sides). The old name was @mysten-incubation/hashi; the first occurrence should read that.

}

/**
* Invokes the `hashi-localnet` Rust CLI from the submodule. The CI workflow

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (docs): "Invokes the hashi-localnet Rust CLI from the submodule" — in the monorepo it's a separate CI checkout (hashi-ci.yml), not a submodule. Suggest "from the submodule" → "from the hashi repo checkout".

packages: [
{
package: '@local-pkg/hashi', // TODO: update this when hashi is published on MVR.
path: '../../../hashi/packages/hashi',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (correctness): '../../../hashi/packages/hashi' resolves to a sibling MystenLabs/hashi clone outside ts-sdks. CI handles this explicitly, but local codegen silently requires the dev to have cloned hashi next to ts-sdks. Worth a one-line comment noting that prerequisite (matches the sibling-repo pattern in AGENTS.md).


echo "Installing Bitcoin Core v${BITCOIN_VERSION}..."

wget -q https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz || {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low (security/supply-chain): the Bitcoin Core tarball is downloaded from bitcoincore.org with no SHA256SUMS/GPG verification before extraction. Blast radius is small (read-only token, no secrets, ephemeral runner), but consider fetching SHA256SUMS and verifying the hash before tar -xzf.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants