Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b1625c0
feat(lazer/sui-js-sdk): initial Sui TS SDK for parse_and_verify_le_ec…
devin-ai-integration[bot] Aug 29, 2025
7ce2510
chore(lazer/sui-js-sdk): fix tsconfig extends to repo root; enable es…
devin-ai-integration[bot] Aug 29, 2025
026afdd
chore(lazer/sui-js-sdk): add flat ESLint config using @cprussin/eslin…
devin-ai-integration[bot] Aug 29, 2025
243d606
chore(lazer/sui-js-sdk): use CJS flat ESLint config via @cprussin/esl…
devin-ai-integration[bot] Aug 29, 2025
07cf308
chore(lazer/sui-js-sdk): wire flat ESLint config via @cprussin/eslint…
devin-ai-integration[bot] Aug 29, 2025
774257c
chore(lazer/sui-js-sdk): fix type import by inferring config; ignore …
devin-ai-integration[bot] Aug 29, 2025
203d237
chore(lazer/sui-js-sdk): flat ESLint config with @cprussin/eslint-con…
devin-ai-integration[bot] Aug 29, 2025
6e8f0a6
chore(lazer/sui-js-sdk): configure ESLint via @cprussin/eslint-config…
devin-ai-integration[bot] Aug 29, 2025
4f3d45f
improve boilerplate
tejasbadadare Aug 29, 2025
cf0edef
feat(lazer/sui-js-sdk): add runnable examples/SuiRelay.ts showing e2e…
devin-ai-integration[bot] Aug 29, 2025
70714bd
refactor(lazer/sui-js-sdk): remove getLeEcdsaUpdate from API; docs: a…
devin-ai-integration[bot] Aug 29, 2025
00ae520
feat(lazer/sui-js-sdk): add lazer.subscribe and signAndExecuteTransac…
devin-ai-integration[bot] Aug 29, 2025
e2460ad
feat(lazer/sui-js-sdk): SuiRelay example — add subscribe, signing; fi…
devin-ai-integration[bot] Aug 29, 2025
cc1cd1b
chore(lazer/sui-js-sdk): rename example to FetchAndVerifyUpdate; upda…
devin-ai-integration[bot] Aug 29, 2025
acdf31d
improve example script
tejasbadadare Aug 30, 2025
3e3b1c0
docs
tejasbadadare Sep 4, 2025
3e59238
remove turbo json
tejasbadadare Sep 4, 2025
1abfa5e
import
tejasbadadare Sep 4, 2025
ceaa78b
fix scaffolding
tejasbadadare Sep 4, 2025
4d26a50
fix scaffolding, lint, format
tejasbadadare Sep 4, 2025
31b9d43
remove main and types exports
tejasbadadare Sep 4, 2025
b372753
fix dual export, use array instead of buffer
tejasbadadare Sep 5, 2025
88a5353
lint
tejasbadadare Sep 8, 2025
e20d12e
build:esm and build:cjs depend on ^build
tejasbadadare Sep 8, 2025
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
3 changes: 3 additions & 0 deletions lazer/contracts/sui/sdk/js/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist/
node_modules/
*.tsbuildinfo
3 changes: 3 additions & 0 deletions lazer/contracts/sui/sdk/js/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.turbo/
node_modules/
dist/
77 changes: 77 additions & 0 deletions lazer/contracts/sui/sdk/js/README.md
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions lazer/contracts/sui/sdk/js/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { base as default } from "@cprussin/eslint-config";
122 changes: 122 additions & 0 deletions lazer/contracts/sui/sdk/js/examples/fetch-and-verify-update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
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));
}

await main();
53 changes: 53 additions & 0 deletions lazer/contracts/sui/sdk/js/package.json
Original file line number Diff line number Diff line change
@@ -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 changes: 25 additions & 0 deletions lazer/contracts/sui/sdk/js/src/client.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 9 additions & 0 deletions lazer/contracts/sui/sdk/js/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false,
"incremental": false,
"declaration": true
},
"exclude": ["node_modules", "dist"]
}
3 changes: 3 additions & 0 deletions lazer/contracts/sui/sdk/js/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "@cprussin/tsconfig/base.json"
}
9 changes: 9 additions & 0 deletions lazer/contracts/sui/sdk/js/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://turbo.build/schema.json",
"extends": ["//"],
"tasks": {
"example:fetch-and-verify": {
"env": ["SUI_KEY"]
}
}
}
Loading
Loading