Jelajahi Sumber

feat(pyth-lazer-sui-js): Init Lazer Sui SDK (#3017)

* feat(lazer/sui-js-sdk): initial Sui TS SDK for parse_and_verify_le_ecdsa_update; add workspace entry and docs

Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz>

* chore(lazer/sui-js-sdk): fix tsconfig extends to repo root; enable esModuleInterop/skipLibCheck

Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz>

* chore(lazer/sui-js-sdk): add flat ESLint config using @cprussin/eslint-config

Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz>

* chore(lazer/sui-js-sdk): use CJS flat ESLint config via @cprussin/eslint-config

Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz>

* chore(lazer/sui-js-sdk): wire flat ESLint config via @cprussin/eslint-config path

Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz>

* chore(lazer/sui-js-sdk): fix type import by inferring config; ignore JS and eslint.config.js in lint

Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz>

* chore(lazer/sui-js-sdk): flat ESLint config with @cprussin/eslint-config and in-file ignores

Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz>

* chore(lazer/sui-js-sdk): configure ESLint via @cprussin/eslint-config; fix types/imports; remove deprecated .eslintignore

Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz>

* improve boilerplate

* feat(lazer/sui-js-sdk): add runnable examples/SuiRelay.ts showing e2e Lazer update -> Sui PTB composition

Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz>

* refactor(lazer/sui-js-sdk): remove getLeEcdsaUpdate from API; docs: add runnable example instructions

Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz>

* feat(lazer/sui-js-sdk): add lazer.subscribe and signAndExecuteTransaction to SuiRelay example

Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz>

* feat(lazer/sui-js-sdk): SuiRelay example — add subscribe, signing; fix imports and README flags

Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz>

* chore(lazer/sui-js-sdk): rename example to FetchAndVerifyUpdate; update script and README

Co-Authored-By: Tejas Badadare <tejas@dourolabs.xyz>

* improve example script

* docs

* remove turbo json

* import

* fix scaffolding

* fix scaffolding, lint, format

* remove main and types exports

* fix dual export, use array instead of buffer

* lint

* build:esm and build:cjs depend on ^build

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Tejas Badadare 2 bulan lalu
induk
melakukan
2bc47856e0

+ 3 - 0
lazer/contracts/sui/sdk/js/.gitignore

@@ -0,0 +1,3 @@
+dist/
+node_modules/
+*.tsbuildinfo

+ 3 - 0
lazer/contracts/sui/sdk/js/.prettierignore

@@ -0,0 +1,3 @@
+.turbo/
+node_modules/
+dist/

+ 77 - 0
lazer/contracts/sui/sdk/js/README.md

@@ -0,0 +1,77 @@
+# Pyth Lazer Sui JS SDK
+
+This package provides utilities to create a Sui Programmable Transaction to parse & verify a Pyth Lazer price update on-chain.
+
+## Build
+
+From the repository root:
+
+```sh
+pnpm turbo build -F @pythnetwork/pyth-lazer-sui-js
+```
+
+## Quickstart
+
+A runnable example is provided at `examples/FetchAndVerifyUpdate.ts`. It:
+
+- connects to Lazer via `@pythnetwork/pyth-lazer-sdk`,
+- fetches a single `leEcdsa` payload,
+- composes a Sui transaction calling `parse_and_verify_le_ecdsa_update`.
+
+### Run the example
+
+Install `tsx` to run TypeScript scripts:
+
+```sh
+npm install -g tsx
+```
+
+Execute the example script:
+
+```sh
+SUI_KEY=<YOUR_SUI_PRIVATE_KEY> pnpm -F @pythnetwork/pyth-lazer-sui-js example:fetch-and-verify --fullnodeUrl <SUI_FULLNODE_URL> --packageId <PYTH_LAZER_PACKAGE_ID> --stateObjectId <PYTH_LAZER_STATE_OBJECT_ID> --token <LAZER_TOKEN>
+```
+
+The script's core logic is summarized below:
+
+```ts
+import { SuiClient } from "@mysten/sui/client";
+import { Transaction } from "@mysten/sui/transactions";
+import { SuiLazerClient } from "@pythnetwork/pyth-lazer-sui-js";
+
+// Prepare Mysten Sui client
+const provider = new SuiClient({ url: "<sui-fullnode-url>" });
+
+// Create SDK client
+const client = new SuiLazerClient(provider);
+
+// Obtain a Lazer leEcdsa payload using @pythnetwork/pyth-lazer-sdk.
+// See examples/FetchAndVerifyUpdate.ts for a runnable end-to-end example.
+const leEcdsa: Buffer = /* fetch via @pythnetwork/pyth-lazer-sdk */ Buffer.from(
+  [],
+);
+
+// Build transaction calling parse_and_verify_le_ecdsa_update
+const tx = new Transaction();
+const packageId = "<pyth_lazer_package_id>";
+const stateObjectId = "<pyth_lazer_state_object_id>";
+
+const updateVal = client.addParseAndVerifyLeEcdsaUpdateCall({
+  tx,
+  packageId,
+  stateObjectId,
+  updateBytes: leEcdsa,
+});
+
+// Sign and execute the transaction using your signer.
+```
+
+## Notes
+
+- FIXME: Automatic `packageId` management is coming soon. The Lazer contract doesn't support upgradeability yet.
+
+## References
+
+- Pyth Lazer Sui contract: `lazer/contracts/sui/`
+- Lazer JS SDK (data source): `lazer/sdk/js/`
+- Mysten Sui TS SDK docs: https://sdk.mystenlabs.com/typescript/transaction-building/basics

+ 1 - 0
lazer/contracts/sui/sdk/js/eslint.config.js

@@ -0,0 +1 @@
+export { base as default } from "@cprussin/eslint-config";

+ 125 - 0
lazer/contracts/sui/sdk/js/examples/fetch-and-verify-update.ts

@@ -0,0 +1,125 @@
+import { SuiClient } from "@mysten/sui/client";
+import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
+import { Transaction } from "@mysten/sui/transactions";
+import type { Request as SubscriptionRequest } from "@pythnetwork/pyth-lazer-sdk";
+import { PythLazerClient } from "@pythnetwork/pyth-lazer-sdk";
+import yargs from "yargs";
+import { hideBin } from "yargs/helpers";
+
+import { addParseAndVerifyLeEcdsaUpdateCall } from "../src/client.js";
+
+async function getOneLeEcdsaUpdate(urls: string[], token: string) {
+  const lazer = await PythLazerClient.create({
+    urls,
+    token,
+    numConnections: 1,
+  });
+
+  const subscription: SubscriptionRequest = {
+    subscriptionId: 1,
+    type: "subscribe",
+    priceFeedIds: [1],
+    properties: ["price", "bestBidPrice", "bestAskPrice", "exponent"],
+    formats: ["leEcdsa"],
+    channel: "fixed_rate@200ms",
+    deliveryFormat: "binary",
+    jsonBinaryEncoding: "hex",
+  };
+
+  lazer.subscribe(subscription);
+
+  return new Promise<Uint8Array>((resolve) => {
+    lazer.addMessageListener((event) => {
+      if (event.type === "binary" && event.value.leEcdsa) {
+        const buf = event.value.leEcdsa;
+
+        // For the purposes of this example, we only need one update.
+        lazer.shutdown();
+        resolve(buf);
+      }
+    });
+  });
+}
+
+async function main() {
+  const args = await yargs(hideBin(process.argv))
+    .option("fullnodeUrl", {
+      type: "string",
+      description:
+        "URL of the full Sui node RPC endpoint. e.g: https://fullnode.testnet.sui.io:443",
+      demandOption: true,
+    })
+    .option("packageId", {
+      type: "string",
+      description: "Lazer contract package ID",
+      demandOption: true,
+    })
+    .option("stateObjectId", {
+      type: "string",
+      description: "Lazer contract shared State object ID",
+      demandOption: true,
+    })
+    .option("lazerUrls", {
+      type: "array",
+      string: true,
+      description: "Lazer WebSocket URLs",
+      default: [
+        "wss://pyth-lazer-0.dourolabs.app/v1/stream",
+        "wss://pyth-lazer-1.dourolabs.app/v1/stream",
+      ],
+    })
+    .option("lazerToken", {
+      type: "string",
+      description: "Lazer authentication token",
+      demandOption: true,
+    })
+    .help()
+    .parseAsync();
+
+  // Defined as a dependency in turbo.json
+  // eslint-disable-next-line n/no-process-env
+  if (process.env.SUI_KEY === undefined) {
+    throw new Error(
+      `SUI_KEY environment variable should be set to your Sui private key in hex format.`,
+    );
+  }
+
+  const provider = new SuiClient({ url: args.fullnodeUrl });
+
+  // Fetch the price update
+  const updateBytes = await getOneLeEcdsaUpdate(
+    args.lazerUrls,
+    args.lazerToken,
+  );
+
+  // Build the Sui transaction
+  const tx = new Transaction();
+
+  // Add the parse and verify call
+  addParseAndVerifyLeEcdsaUpdateCall({
+    tx,
+    packageId: args.packageId,
+    stateObjectId: args.stateObjectId,
+    updateBytes,
+  });
+
+  // --- You can add more calls to the transaction that consume the parsed update here ---
+
+  const wallet = Ed25519Keypair.fromSecretKey(
+    // eslint-disable-next-line n/no-process-env
+    Buffer.from(process.env.SUI_KEY, "hex"),
+  );
+  const res = await provider.signAndExecuteTransaction({
+    signer: wallet,
+    transaction: tx,
+    options: { showEffects: true, showEvents: true },
+  });
+
+  // eslint-disable-next-line no-console
+  console.log("Execution result:", JSON.stringify(res, undefined, 2));
+}
+
+// eslint-disable-next-line unicorn/prefer-top-level-await
+main().catch((error: unknown) => {
+  throw error;
+});

+ 53 - 0
lazer/contracts/sui/sdk/js/package.json

@@ -0,0 +1,53 @@
+{
+  "name": "@pythnetwork/pyth-lazer-sui-js",
+  "version": "0.1.0",
+  "description": "TypeScript SDK for the Pyth Lazer Sui contract",
+  "license": "Apache-2.0",
+  "type": "module",
+  "engines": {
+    "node": "22"
+  },
+  "files": [
+    "dist"
+  ],
+  "exports": {
+    ".": {
+      "import": {
+        "types": "./dist/esm/client.d.ts",
+        "default": "./dist/esm/client.js"
+      },
+      "require": {
+        "types": "./dist/cjs/client.d.ts",
+        "default": "./dist/cjs/client.js"
+      }
+    }
+  },
+  "sideEffects": false,
+  "scripts": {
+    "build:cjs": "tsc --project tsconfig.build.json --verbatimModuleSyntax false --module commonjs --outDir ./dist/cjs && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
+    "build:esm": "tsc --project tsconfig.build.json --outDir ./dist/esm && echo '{\"type\":\"module\"}' > dist/esm/package.json",
+    "fix:format": "prettier --write .",
+    "fix:lint": "eslint --fix .",
+    "test:format": "prettier --check .",
+    "test:lint": "eslint . --max-warnings 0",
+    "test:types": "tsc",
+    "example:fetch-and-verify": "tsx examples/fetch-and-verify-update.ts"
+  },
+  "dependencies": {
+    "@mysten/sui": "catalog:",
+    "@pythnetwork/pyth-lazer-sdk": "workspace:*",
+    "@types/yargs": "catalog:",
+    "yargs": "catalog:"
+  },
+  "devDependencies": {
+    "@cprussin/eslint-config": "catalog:",
+    "@cprussin/tsconfig": "catalog:",
+    "@types/node": "catalog:",
+    "eslint": "catalog:",
+    "prettier": "catalog:",
+    "typescript": "catalog:"
+  },
+  "publishConfig": {
+    "access": "public"
+  }
+}

+ 25 - 0
lazer/contracts/sui/sdk/js/src/client.ts

@@ -0,0 +1,25 @@
+import { bcs } from "@mysten/sui/bcs";
+import { Transaction } from "@mysten/sui/transactions";
+import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui/utils";
+
+export function addParseAndVerifyLeEcdsaUpdateCall({
+  tx,
+  packageId,
+  stateObjectId,
+  updateBytes,
+}: {
+  tx: Transaction;
+  packageId: string;
+  stateObjectId: string;
+  updateBytes: Uint8Array;
+}) {
+  const [updateObj] = tx.moveCall({
+    target: `${packageId}::pyth_lazer::parse_and_verify_le_ecdsa_update`,
+    arguments: [
+      tx.object(stateObjectId),
+      tx.object(SUI_CLOCK_OBJECT_ID),
+      tx.pure(bcs.vector(bcs.U8).serialize(updateBytes).toBytes()),
+    ],
+  });
+  return updateObj;
+}

+ 9 - 0
lazer/contracts/sui/sdk/js/tsconfig.build.json

@@ -0,0 +1,9 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "noEmit": false,
+    "incremental": false,
+    "declaration": true
+  },
+  "exclude": ["node_modules", "dist"]
+}

+ 3 - 0
lazer/contracts/sui/sdk/js/tsconfig.json

@@ -0,0 +1,3 @@
+{
+  "extends": "@cprussin/tsconfig/base.json"
+}

+ 9 - 0
lazer/contracts/sui/sdk/js/turbo.json

@@ -0,0 +1,9 @@
+{
+  "$schema": "https://turbo.build/schema.json",
+  "extends": ["//"],
+  "tasks": {
+    "example:fetch-and-verify": {
+      "env": ["SUI_KEY"]
+    }
+  }
+}

+ 108 - 5
pnpm-lock.yaml

@@ -54,6 +54,9 @@ catalogs:
     '@heroicons/react':
       specifier: ^2.2.0
       version: 2.2.0
+    '@mysten/sui':
+      specifier: ^1.3.0
+      version: 1.26.1
     '@next/third-parties':
       specifier: ^15.3.2
       version: 15.3.2
@@ -144,6 +147,9 @@ catalogs:
     '@types/react-dom':
       specifier: ^19.1.1
       version: 19.1.1
+    '@types/yargs':
+      specifier: ^17.0.33
+      version: 17.0.33
     '@vercel/functions':
       specifier: ^2.0.0
       version: 2.0.0
@@ -333,6 +339,9 @@ catalogs:
     wagmi:
       specifier: ^2.14.16
       version: 2.14.16
+    yargs:
+      specifier: ^18.0.0
+      version: 18.0.0
     zod:
       specifier: ^3.24.2
       version: 3.24.4
@@ -1987,6 +1996,40 @@ importers:
         specifier: ^17.7.2
         version: 17.7.2
 
+  lazer/contracts/sui/sdk/js:
+    dependencies:
+      '@mysten/sui':
+        specifier: 'catalog:'
+        version: 1.26.1(typescript@5.8.2)
+      '@pythnetwork/pyth-lazer-sdk':
+        specifier: workspace:*
+        version: link:../../../../sdk/js
+      '@types/yargs':
+        specifier: 'catalog:'
+        version: 17.0.33
+      yargs:
+        specifier: 'catalog:'
+        version: 18.0.0
+    devDependencies:
+      '@cprussin/eslint-config':
+        specifier: 'catalog:'
+        version: 4.0.2(@testing-library/dom@10.4.0)(@typescript-eslint/eslint-plugin@8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(jest@29.7.0(@types/node@22.14.0)(ts-node@10.9.2(@swc/core@1.13.2)(@types/node@22.14.0)(typescript@5.8.2)))(ts-node@10.9.2(@swc/core@1.13.2)(@types/node@22.14.0)(typescript@5.8.2))(turbo@2.4.4)(typescript@5.8.2)
+      '@cprussin/tsconfig':
+        specifier: 'catalog:'
+        version: 3.1.2(typescript@5.8.2)
+      '@types/node':
+        specifier: 'catalog:'
+        version: 22.14.0
+      eslint:
+        specifier: 'catalog:'
+        version: 9.23.0(jiti@2.4.2)
+      prettier:
+        specifier: 'catalog:'
+        version: 3.5.3
+      typescript:
+        specifier: 'catalog:'
+        version: 5.8.2
+
   lazer/sdk/js:
     dependencies:
       '@isaacs/ttlcache':
@@ -13062,6 +13105,10 @@ packages:
     resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
     engines: {node: '>=12'}
 
+  cliui@9.0.1:
+    resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==}
+    engines: {node: '>=20'}
+
   clone-deep@4.0.1:
     resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==}
     engines: {node: '>=6'}
@@ -14039,6 +14086,9 @@ packages:
   emoji-regex-xs@1.0.0:
     resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}
 
+  emoji-regex@10.5.0:
+    resolution: {integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==}
+
   emoji-regex@7.0.3:
     resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==}
 
@@ -15404,6 +15454,10 @@ packages:
     resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
     engines: {node: 6.* || 8.* || >= 10.*}
 
+  get-east-asian-width@1.3.0:
+    resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
+    engines: {node: '>=18'}
+
   get-func-name@2.0.2:
     resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
 
@@ -20090,6 +20144,10 @@ packages:
     resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
     engines: {node: '>=12'}
 
+  string-width@7.2.0:
+    resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
+    engines: {node: '>=18'}
+
   string.prototype.includes@2.0.1:
     resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==}
     engines: {node: '>= 0.4'}
@@ -21815,6 +21873,10 @@ packages:
     resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
     engines: {node: '>=12'}
 
+  wrap-ansi@9.0.0:
+    resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==}
+    engines: {node: '>=18'}
+
   wrappy@1.0.2:
     resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
 
@@ -22032,6 +22094,10 @@ packages:
     resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
     engines: {node: '>=12'}
 
+  yargs-parser@22.0.0:
+    resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==}
+    engines: {node: ^20.19.0 || ^22.12.0 || >=23}
+
   yargs-unparser@2.0.0:
     resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}
     engines: {node: '>=10'}
@@ -22051,6 +22117,10 @@ packages:
     resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
     engines: {node: '>=12'}
 
+  yargs@18.0.0:
+    resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==}
+    engines: {node: ^20.19.0 || ^22.12.0 || >=23}
+
   yauzl-clone@1.0.4:
     resolution: {integrity: sha512-igM2RRCf3k8TvZoxR2oguuw4z1xasOnA31joCqHIyLkeWrvAc2Jgay5ISQ2ZplinkoGaJ6orCz56Ey456c5ESA==}
     engines: {node: '>=6'}
@@ -28831,11 +28901,11 @@ snapshots:
     dependencies:
       '@graphql-typed-document-node/core': 3.2.0(graphql@16.10.0)
       '@mysten/bcs': 1.6.0
-      '@noble/curves': 1.8.1
-      '@noble/hashes': 1.7.1
-      '@scure/base': 1.2.4
-      '@scure/bip32': 1.6.2
-      '@scure/bip39': 1.5.4
+      '@noble/curves': 1.9.6
+      '@noble/hashes': 1.8.0
+      '@scure/base': 1.2.6
+      '@scure/bip32': 1.7.0
+      '@scure/bip39': 1.6.0
       gql.tada: 1.8.10(graphql@16.10.0)(typescript@5.8.2)
       graphql: 16.10.0
       poseidon-lite: 0.2.1
@@ -39495,6 +39565,12 @@ snapshots:
       strip-ansi: 6.0.1
       wrap-ansi: 7.0.0
 
+  cliui@9.0.1:
+    dependencies:
+      string-width: 7.2.0
+      strip-ansi: 7.1.0
+      wrap-ansi: 9.0.0
+
   clone-deep@4.0.1:
     dependencies:
       is-plain-object: 2.0.4
@@ -40569,6 +40645,8 @@ snapshots:
 
   emoji-regex-xs@1.0.0: {}
 
+  emoji-regex@10.5.0: {}
+
   emoji-regex@7.0.3: {}
 
   emoji-regex@8.0.0: {}
@@ -42850,6 +42928,8 @@ snapshots:
 
   get-caller-file@2.0.5: {}
 
+  get-east-asian-width@1.3.0: {}
+
   get-func-name@2.0.2: {}
 
   get-intrinsic@1.3.0:
@@ -49600,6 +49680,12 @@ snapshots:
       emoji-regex: 9.2.2
       strip-ansi: 7.1.0
 
+  string-width@7.2.0:
+    dependencies:
+      emoji-regex: 10.5.0
+      get-east-asian-width: 1.3.0
+      strip-ansi: 7.1.0
+
   string.prototype.includes@2.0.1:
     dependencies:
       call-bind: 1.0.8
@@ -52394,6 +52480,12 @@ snapshots:
       string-width: 5.1.2
       strip-ansi: 7.1.0
 
+  wrap-ansi@9.0.0:
+    dependencies:
+      ansi-styles: 6.2.1
+      string-width: 7.2.0
+      strip-ansi: 7.1.0
+
   wrappy@1.0.2: {}
 
   write-file-atomic@4.0.2:
@@ -52593,6 +52685,8 @@ snapshots:
 
   yargs-parser@21.1.1: {}
 
+  yargs-parser@22.0.0: {}
+
   yargs-unparser@2.0.0:
     dependencies:
       camelcase: 6.3.0
@@ -52647,6 +52741,15 @@ snapshots:
       y18n: 5.0.8
       yargs-parser: 21.1.1
 
+  yargs@18.0.0:
+    dependencies:
+      cliui: 9.0.1
+      escalade: 3.2.0
+      get-caller-file: 2.0.5
+      string-width: 7.2.0
+      y18n: 5.0.8
+      yargs-parser: 22.0.0
+
   yauzl-clone@1.0.4:
     dependencies:
       events-intercept: 2.0.0

+ 4 - 0
pnpm-workspace.yaml

@@ -45,6 +45,7 @@ packages:
   - lazer/contracts/solana
   - lazer/sdk/js
   - lazer/sdk/js-solana
+  - lazer/contracts/sui/sdk/js
 
 catalog:
   "@amplitude/analytics-browser": ^2.13.0
@@ -94,7 +95,10 @@ catalog:
   "@types/node": ^22.14.0
   "@types/react": ^19.1.0
   "@types/react-dom": ^19.1.1
+  "@types/yargs": "^17.0.33"
   "@vercel/functions": ^2.0.0
+  "@mysten/sui": "^1.3.0"
+  "yargs": "^18.0.0"
   autoprefixer: ^10.4.21
   babel-plugin-react-compiler: 19.1.0-rc.1
   bcp-47: ^2.1.0

+ 2 - 2
turbo.json

@@ -71,7 +71,7 @@
       "outputs": ["lib/**", "dist/**", ".next/**", "!.next/cache/**"]
     },
     "build:cjs": {
-      "dependsOn": ["//#install:modules"],
+      "dependsOn": ["//#install:modules", "^build"],
       "inputs": [
         "$TURBO_DEFAULT$",
         "!README.md",
@@ -84,7 +84,7 @@
       "outputs": ["dist/cjs/**"]
     },
     "build:esm": {
-      "dependsOn": ["//#install:modules"],
+      "dependsOn": ["//#install:modules", "^build"],
       "inputs": [
         "$TURBO_DEFAULT$",
         "!README.md",