@pythnetwork/pyth-lazer-iota-js 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -0
- package/README.md +88 -2
- package/dist/cjs/index.cjs +48 -0
- package/dist/cjs/index.d.ts +8 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.mjs +38 -0
- package/dist/esm/package.json +1 -0
- package/package.json +57 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2025 Pyth Data Association.
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,89 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Pyth Lazer IOTA JS SDK
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This package provides utilities to create an IOTA programmable transaction that
|
|
4
|
+
parses and verifies a Pyth Lazer price update on-chain.
|
|
5
|
+
|
|
6
|
+
## Build
|
|
7
|
+
|
|
8
|
+
From the repository root:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
pnpm turbo build -F @pythnetwork/pyth-lazer-iota-js
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quickstart
|
|
15
|
+
|
|
16
|
+
A runnable example is provided at `src/examples/fetch-and-verify.ts`. It:
|
|
17
|
+
|
|
18
|
+
- connects to Lazer via `@pythnetwork/pyth-lazer-sdk`,
|
|
19
|
+
- fetches a single `leEcdsa` payload,
|
|
20
|
+
- composes an IOTA transaction calling `parse_and_verify_le_ecdsa_update`.
|
|
21
|
+
|
|
22
|
+
### Run the example
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
# Your IOTA private key in Bech32 format
|
|
26
|
+
# export IOTA_KEY=
|
|
27
|
+
|
|
28
|
+
# Lazer contract state ID
|
|
29
|
+
# STATE_ID=
|
|
30
|
+
|
|
31
|
+
# Your Lazer API token
|
|
32
|
+
# LAZER_TOKEN=
|
|
33
|
+
|
|
34
|
+
IOTA_FULLNODE_URL="https://api.testnet.iota.cafe/"
|
|
35
|
+
|
|
36
|
+
pnpm run example:fetch-and-verify \
|
|
37
|
+
--base-url "$IOTA_FULLNODE_URL" \
|
|
38
|
+
--state-id "$STATE_ID" \
|
|
39
|
+
--lazer-token "$LAZER_TOKEN"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The script's core logic is summarized below:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { Buffer } from "node:buffer";
|
|
46
|
+
import { IotaClient } from "@iota/iota-sdk/client";
|
|
47
|
+
import { Ed25519Keypair } from "@iota/iota-sdk/keypairs/ed25519";
|
|
48
|
+
import { Transaction } from "@iota/iota-sdk/transactions";
|
|
49
|
+
import { PythLazerClient } from "@pythnetwork/pyth-lazer-sdk";
|
|
50
|
+
import { addParseAndVerifyLeEcdsaUpdateCall } from "@pythnetwork/pyth-lazer-iota-js";
|
|
51
|
+
|
|
52
|
+
// 1. Fetch the price update from Pyth Lazer in "leEcdsa" format:
|
|
53
|
+
const lazer = await PythLazerClient.create({ token: LAZER_TOKEN });
|
|
54
|
+
const latestPrice = await lazer.getLatestPrice({
|
|
55
|
+
channel: "fixed_rate@200ms",
|
|
56
|
+
formats: ["leEcdsa"],
|
|
57
|
+
jsonBinaryEncoding: "hex",
|
|
58
|
+
priceFeedIds: [1],
|
|
59
|
+
properties: ["price", "bestBidPrice", "bestAskPrice", "exponent"],
|
|
60
|
+
});
|
|
61
|
+
const update = Buffer.from(latestPrice.leEcdsa?.data ?? "", "hex");
|
|
62
|
+
|
|
63
|
+
// 2. Create a new IOTA transaction:
|
|
64
|
+
const signer = Ed25519Keypair.fromSecretKey(IOTA_KEY);
|
|
65
|
+
const client = new IotaClient({ url: IOTA_FULLNODE_URL });
|
|
66
|
+
const tx = new Transaction();
|
|
67
|
+
|
|
68
|
+
// 3. Add the parse and verify call:
|
|
69
|
+
const verifiedUpdate = await addParseAndVerifyLeEcdsaUpdateCall({
|
|
70
|
+
client,
|
|
71
|
+
stateObjectId: STATE_ID,
|
|
72
|
+
tx,
|
|
73
|
+
update,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// 4. Consume `verifiedUpdate` in your own contract with additional calls...
|
|
77
|
+
|
|
78
|
+
// 5. Sign and execute the transaction:
|
|
79
|
+
const result = await client.signAndExecuteTransaction({
|
|
80
|
+
signer,
|
|
81
|
+
transaction: tx,
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## References
|
|
86
|
+
|
|
87
|
+
- Pyth Lazer IOTA contract: `lazer/contracts/iota/`
|
|
88
|
+
- Lazer JS SDK: https://www.npmjs.com/package/@pythnetwork/pyth-lazer-sdk
|
|
89
|
+
- IOTA TypeScript SDK: https://www.npmjs.com/package/@iota/iota-sdk
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "addParseAndVerifyLeEcdsaUpdateCall", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return addParseAndVerifyLeEcdsaUpdateCall;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
async function addParseAndVerifyLeEcdsaUpdateCall(opts) {
|
|
12
|
+
const { client, tx, stateObjectId, update } = opts;
|
|
13
|
+
const packageId = await getLatestPackageId(client, stateObjectId);
|
|
14
|
+
return tx.moveCall({
|
|
15
|
+
arguments: [
|
|
16
|
+
tx.object(stateObjectId),
|
|
17
|
+
tx.object.clock(),
|
|
18
|
+
tx.pure.vector("u8", update)
|
|
19
|
+
],
|
|
20
|
+
target: `${packageId}::pyth_lazer::parse_and_verify_le_ecdsa_update`
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async function getLatestPackageId(client, stateObjectId) {
|
|
24
|
+
const { data: stateObject, error } = await client.getObject({
|
|
25
|
+
id: stateObjectId,
|
|
26
|
+
options: {
|
|
27
|
+
showContent: true
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
if (!stateObject?.content || error) {
|
|
31
|
+
throw new Error(`Failed to get IOTA Lazer State: ${error?.code ?? "undefined"}`);
|
|
32
|
+
}
|
|
33
|
+
if (stateObject.content.dataType !== "moveObject") {
|
|
34
|
+
throw new Error(`IOTA Lazer State must be an object, got: ${stateObject.content.dataType}`);
|
|
35
|
+
}
|
|
36
|
+
const { fields } = stateObject.content;
|
|
37
|
+
if (!hasProperty(fields, "upgrade_cap")) {
|
|
38
|
+
throw new Error("Missing 'upgrade_cap' in IOTA Lazer State");
|
|
39
|
+
}
|
|
40
|
+
const upgradeCap = fields.upgrade_cap;
|
|
41
|
+
if (!hasProperty(upgradeCap, "fields") || !hasProperty(upgradeCap.fields, "package") || typeof upgradeCap.fields.package !== "string") {
|
|
42
|
+
throw new Error("Could not find 'package' string in IOTA Lazer UpgradeCap");
|
|
43
|
+
}
|
|
44
|
+
return upgradeCap.fields.package;
|
|
45
|
+
}
|
|
46
|
+
function hasProperty(value, name) {
|
|
47
|
+
return typeof value === "object" && value !== null && name in value;
|
|
48
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IotaClient } from "@iota/iota-sdk/client";
|
|
2
|
+
import type { Transaction } from "@iota/iota-sdk/transactions";
|
|
3
|
+
export declare function addParseAndVerifyLeEcdsaUpdateCall(opts: {
|
|
4
|
+
client: IotaClient;
|
|
5
|
+
tx: Transaction;
|
|
6
|
+
stateObjectId: string;
|
|
7
|
+
update: Uint8Array;
|
|
8
|
+
}): Promise<import("@iota/iota-sdk/transactions").TransactionResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "type": "commonjs" }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IotaClient } from "@iota/iota-sdk/client";
|
|
2
|
+
import type { Transaction } from "@iota/iota-sdk/transactions";
|
|
3
|
+
export declare function addParseAndVerifyLeEcdsaUpdateCall(opts: {
|
|
4
|
+
client: IotaClient;
|
|
5
|
+
tx: Transaction;
|
|
6
|
+
stateObjectId: string;
|
|
7
|
+
update: Uint8Array;
|
|
8
|
+
}): Promise<import("@iota/iota-sdk/transactions").TransactionResult>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export async function addParseAndVerifyLeEcdsaUpdateCall(opts) {
|
|
2
|
+
const { client, tx, stateObjectId, update } = opts;
|
|
3
|
+
const packageId = await getLatestPackageId(client, stateObjectId);
|
|
4
|
+
return tx.moveCall({
|
|
5
|
+
arguments: [
|
|
6
|
+
tx.object(stateObjectId),
|
|
7
|
+
tx.object.clock(),
|
|
8
|
+
tx.pure.vector("u8", update)
|
|
9
|
+
],
|
|
10
|
+
target: `${packageId}::pyth_lazer::parse_and_verify_le_ecdsa_update`
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
async function getLatestPackageId(client, stateObjectId) {
|
|
14
|
+
const { data: stateObject, error } = await client.getObject({
|
|
15
|
+
id: stateObjectId,
|
|
16
|
+
options: {
|
|
17
|
+
showContent: true
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
if (!stateObject?.content || error) {
|
|
21
|
+
throw new Error(`Failed to get IOTA Lazer State: ${error?.code ?? "undefined"}`);
|
|
22
|
+
}
|
|
23
|
+
if (stateObject.content.dataType !== "moveObject") {
|
|
24
|
+
throw new Error(`IOTA Lazer State must be an object, got: ${stateObject.content.dataType}`);
|
|
25
|
+
}
|
|
26
|
+
const { fields } = stateObject.content;
|
|
27
|
+
if (!hasProperty(fields, "upgrade_cap")) {
|
|
28
|
+
throw new Error("Missing 'upgrade_cap' in IOTA Lazer State");
|
|
29
|
+
}
|
|
30
|
+
const upgradeCap = fields.upgrade_cap;
|
|
31
|
+
if (!hasProperty(upgradeCap, "fields") || !hasProperty(upgradeCap.fields, "package") || typeof upgradeCap.fields.package !== "string") {
|
|
32
|
+
throw new Error("Could not find 'package' string in IOTA Lazer UpgradeCap");
|
|
33
|
+
}
|
|
34
|
+
return upgradeCap.fields.package;
|
|
35
|
+
}
|
|
36
|
+
function hasProperty(value, name) {
|
|
37
|
+
return typeof value === "object" && value !== null && name in value;
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "type": "module" }
|
package/package.json
CHANGED
|
@@ -1,6 +1,60 @@
|
|
|
1
1
|
{
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"@iota/iota-sdk": "^1.11.0"
|
|
4
|
+
},
|
|
5
|
+
"description": "TypeScript SDK for the Pyth Lazer IOTA contract",
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"@cprussin/tsconfig": "^4.0.2",
|
|
8
|
+
"@pythnetwork/jest-config": "",
|
|
9
|
+
"@pythnetwork/pyth-lazer-sdk": "^5.2.0",
|
|
10
|
+
"@types/jest": "^29.5.14",
|
|
11
|
+
"@types/node": "^22.14.0",
|
|
12
|
+
"@types/yargs": "^17.0.33",
|
|
13
|
+
"jest": "^29.7.0",
|
|
14
|
+
"tsx": "4.20.6",
|
|
15
|
+
"yargs": "^18.0.0"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": "^24.0.0"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"import": {
|
|
23
|
+
"default": "./dist/esm/index.mjs",
|
|
24
|
+
"types": "./dist/esm/index.d.ts"
|
|
25
|
+
},
|
|
26
|
+
"require": {
|
|
27
|
+
"default": "./dist/cjs/index.cjs",
|
|
28
|
+
"types": "./dist/cjs/index.d.ts"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist/**"
|
|
35
|
+
],
|
|
36
|
+
"license": "Apache-2.0",
|
|
37
|
+
"main": "./dist/cjs/index.cjs",
|
|
38
|
+
"module": "./dist/esm/index.mjs",
|
|
2
39
|
"name": "@pythnetwork/pyth-lazer-iota-js",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
|
|
40
|
+
"private": false,
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"directory": "lazer/contracts/iota/sdk/js",
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/pyth-network/pyth-crosschain"
|
|
48
|
+
},
|
|
49
|
+
"sideEffects": false,
|
|
50
|
+
"type": "module",
|
|
51
|
+
"types": "./dist/cjs/index.d.ts",
|
|
52
|
+
"version": "0.1.0",
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "ts-duality --copyOtherFiles",
|
|
55
|
+
"clean": "rm -rf ./dist",
|
|
56
|
+
"example:fetch-and-verify": "tsx src/examples/fetch-and-verify.ts",
|
|
57
|
+
"test:types": "tsc",
|
|
58
|
+
"test:unit": "test-unit"
|
|
59
|
+
}
|
|
6
60
|
}
|