Skip to content

Commit b0aa4b1

Browse files
authored
initial sdk prototype (#1850)
* initial sdk prototype * generalize svm endpoints, some reorg * some renamings, reorg * address some lint issues * lint + name changes * fixed tx serialization to include signatures & template * fixed type hints * address comments * unused import * update to new server bidsvm format * improve org * refactored js sdk + addressed comments * fix lint * idl folders * idl folders * bump package version * rename package * address comments
1 parent f621690 commit b0aa4b1

File tree

15 files changed

+1576
-168
lines changed

15 files changed

+1576
-168
lines changed

express_relay/examples/easy_lend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
},
2222
"dependencies": {
2323
"@openzeppelin/contracts": "^4.5.0",
24-
"@pythnetwork/express-relay-evm-js": "workspace:*",
24+
"@pythnetwork/express-relay-js": "workspace:*",
2525
"@pythnetwork/express-relay-sdk-solidity": "workspace:*",
2626
"@pythnetwork/price-service-client": "workspace:^",
2727
"@pythnetwork/pyth-evm-js": "workspace:*",

express_relay/examples/easy_lend/src/monitor.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import {
44
checkAddress,
55
Client,
66
OpportunityParams,
7-
} from "@pythnetwork/express-relay-evm-js";
8-
import { privateKeyToAccount } from "viem/accounts";
7+
} from "@pythnetwork/express-relay-js";
98
import type { ContractFunctionReturnType } from "viem";
109
import {
1110
Address,
@@ -15,7 +14,6 @@ import {
1514
getContract,
1615
Hex,
1716
http,
18-
isHex,
1917
} from "viem";
2018
import { optimismSepolia } from "viem/chains";
2119
import { abi } from "./abi";

express_relay/sdk/js/README.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ Utility library for interacting with the Pyth Express Relay API.
77
### npm
88

99
```
10-
$ npm install --save @pythnetwork/express-relay-evm-js
10+
$ npm install --save @pythnetwork/express-relay-js
1111
```
1212

1313
### Yarn
1414

1515
```
16-
$ yarn add @pythnetwork/express-relay-evm-js
16+
$ yarn add @pythnetwork/express-relay-js
1717
```
1818

1919
## Development
@@ -24,14 +24,20 @@ To generate the latest type declarations from the server openapi schema, run:
2424
npm run generate-api-types
2525
```
2626

27+
You can generate the Solana Typescript declaration files from the IDLs via:
28+
29+
```bash
30+
npm run generate-anchor-types
31+
```
32+
2733
## Quickstart
2834

2935
```typescript
3036
import {
3137
Client,
3238
OpportunityParams,
3339
BidParams,
34-
} from "@pythnetwork/express-relay-evm-js";
40+
} from "@pythnetwork/express-relay-js";
3541

3642
function calculateOpportunityBid(opportunity: Opportunity): BidParams | null {
3743
// searcher implementation here
@@ -79,4 +85,16 @@ npm run simple-searcher -- \
7985
--private-key <YOUR-PRIVATE-KEY>
8086
```
8187

88+
#### SimpleSearcherSvm
89+
90+
The SimpleSearcherSvm example submits a dummy SVM transaction to the auction server after appending the appropriate `SubmitBid` instruction that permissions the transaction. You can run it with `npm run simple-searcher-svm`, and the full command looks like:
91+
92+
```bash
93+
npm run simple-searcher-svm -- \
94+
--endpoint-express-relay http://per-staging.dourolabs.app/ \
95+
--chain-id solana \
96+
--private-key <YOUR-PRIVATE-KEY> \
97+
--endpoint-svm "https://api.mainnet-beta.solana.com"
98+
```
99+
82100
Note that if you are using a localhost server at `http://127.0.0.1`, you should specify `--endpoint http://127.0.0.1:{PORT}` rather than `http://localhost:{PORT}`, as Typescript maps `localhost` to `::1` in line with IPv6 rather than to `127.0.0.1` as with IPv4.

express_relay/sdk/js/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "@pythnetwork/express-relay-evm-js",
3-
"version": "0.8.1",
2+
"name": "@pythnetwork/express-relay-js",
3+
"version": "0.9.0",
44
"description": "Utilities for interacting with the express relay protocol",
55
"homepage": "https://github.com/pyth-network/pyth-crosschain/tree/main/express_relay/sdk/js",
66
"author": "Douro Labs",
@@ -16,7 +16,9 @@
1616
"build": "tsc",
1717
"test": "jest src/ --passWithNoTests",
1818
"simple-searcher": "pnpm run build && node lib/examples/simpleSearcher.js",
19+
"simple-searcher-svm": "pnpm run build && node lib/examples/simpleSearcherSvm.js",
1920
"generate-api-types": "openapi-typescript http://127.0.0.1:9000/docs/openapi.json --output src/serverTypes.d.ts",
21+
"generate-anchor-types": "anchor idl type src/idl/idlExpressRelay.json --out src/expressRelayTypes.d.ts && anchor idl type src/examples/idl/idlDummy.json --out src/examples/dummyTypes.d.ts",
2022
"format": "prettier --write \"src/**/*.ts\"",
2123
"lint": "eslint src",
2224
"prepublishOnly": "pnpm run build && pnpm test && pnpm run lint",
@@ -34,6 +36,8 @@
3436
"directory": "express_relay/sdk/js"
3537
},
3638
"dependencies": {
39+
"@coral-xyz/anchor": "^0.30.1",
40+
"@solana/web3.js": "^1.95.3",
3741
"isomorphic-ws": "^5.0.0",
3842
"openapi-client-axios": "^7.5.5",
3943
"openapi-fetch": "^0.8.2",

express_relay/sdk/js/src/const.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { OpportunityAdapterConfig } from "./types";
1+
import { PublicKey } from "@solana/web3.js";
2+
import { OpportunityAdapterConfig, SvmConstantsConfig } from "./types";
23

34
export const OPPORTUNITY_ADAPTER_CONFIGS: Record<
45
string,
@@ -21,3 +22,17 @@ export const OPPORTUNITY_ADAPTER_CONFIGS: Record<
2122
weth: "0x4200000000000000000000000000000000000006",
2223
},
2324
};
25+
26+
export const SVM_CONSTANTS: Record<string, SvmConstantsConfig> = {
27+
solana: {
28+
relayerSigner: new PublicKey(
29+
"3pR5W8qPTHKEdoD7mNMx1SwkaPE18aQZwoAKWYpnHozY"
30+
),
31+
feeReceiverRelayer: new PublicKey(
32+
"3pR5W8qPTHKEdoD7mNMx1SwkaPE18aQZwoAKWYpnHozY"
33+
),
34+
expressRelayProgram: new PublicKey(
35+
"GwEtasTAxdS9neVE4GPUpcwR7DB7AizntQSPcG36ubZM"
36+
),
37+
},
38+
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* Program IDL in camelCase format in order to be used in JS/TS.
3+
*
4+
* Note that this is only a type helper and is not the actual IDL. The original
5+
* IDL can be found at `target/idl/dummy.json`.
6+
*/
7+
export type Dummy = {
8+
address: "HYCgALnu6CM2gkQVopa1HGaNf8Vzbs9bomWRiKP267P3";
9+
metadata: {
10+
name: "dummy";
11+
version: "0.1.0";
12+
spec: "0.1.0";
13+
description: "Created with Anchor";
14+
};
15+
instructions: [
16+
{
17+
name: "doNothing";
18+
discriminator: [112, 130, 224, 161, 71, 149, 192, 187];
19+
accounts: [
20+
{
21+
name: "payer";
22+
writable: true;
23+
signer: true;
24+
},
25+
{
26+
name: "expressRelay";
27+
address: "GwEtasTAxdS9neVE4GPUpcwR7DB7AizntQSPcG36ubZM";
28+
},
29+
{
30+
name: "sysvarInstructions";
31+
address: "Sysvar1nstructions1111111111111111111111111";
32+
},
33+
{
34+
name: "permission";
35+
},
36+
{
37+
name: "router";
38+
}
39+
];
40+
args: [];
41+
}
42+
];
43+
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"address": "HYCgALnu6CM2gkQVopa1HGaNf8Vzbs9bomWRiKP267P3",
3+
"metadata": {
4+
"name": "dummy",
5+
"version": "0.1.0",
6+
"spec": "0.1.0",
7+
"description": "Created with Anchor"
8+
},
9+
"instructions": [
10+
{
11+
"name": "do_nothing",
12+
"discriminator": [112, 130, 224, 161, 71, 149, 192, 187],
13+
"accounts": [
14+
{
15+
"name": "payer",
16+
"writable": true,
17+
"signer": true
18+
},
19+
{
20+
"name": "express_relay",
21+
"address": "GwEtasTAxdS9neVE4GPUpcwR7DB7AizntQSPcG36ubZM"
22+
},
23+
{
24+
"name": "sysvar_instructions",
25+
"address": "Sysvar1nstructions1111111111111111111111111"
26+
},
27+
{
28+
"name": "permission"
29+
},
30+
{
31+
"name": "router"
32+
}
33+
],
34+
"args": []
35+
}
36+
]
37+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import yargs from "yargs";
2+
import { hideBin } from "yargs/helpers";
3+
import { Client } from "../index";
4+
import { BidStatusUpdate } from "../types";
5+
import { SVM_CONSTANTS } from "../const";
6+
7+
import * as anchor from "@coral-xyz/anchor";
8+
import { Program, AnchorProvider } from "@coral-xyz/anchor";
9+
import { Keypair, PublicKey, Connection } from "@solana/web3.js";
10+
import dummyIdl from "./idl/idlDummy.json";
11+
import { Dummy } from "./dummyTypes";
12+
13+
const DAY_IN_SECONDS = 60 * 60 * 24;
14+
const DUMMY_PIDS: Record<string, PublicKey> = {
15+
solana: new PublicKey("HYCgALnu6CM2gkQVopa1HGaNf8Vzbs9bomWRiKP267P3"),
16+
};
17+
18+
class SimpleSearcherSvm {
19+
private client: Client;
20+
private connectionSvm: Connection;
21+
constructor(
22+
public endpointExpressRelay: string,
23+
public chainId: string,
24+
public privateKey: string,
25+
public endpointSvm: string,
26+
public apiKey?: string
27+
) {
28+
this.client = new Client(
29+
{
30+
baseUrl: endpointExpressRelay,
31+
apiKey,
32+
},
33+
undefined,
34+
() => {
35+
return Promise.resolve();
36+
},
37+
this.bidStatusHandler.bind(this)
38+
);
39+
this.connectionSvm = new Connection(endpointSvm, "confirmed");
40+
}
41+
42+
async bidStatusHandler(bidStatus: BidStatusUpdate) {
43+
let resultDetails = "";
44+
if (bidStatus.type == "submitted" || bidStatus.type == "won") {
45+
resultDetails = `, transaction ${bidStatus.result}`;
46+
} else if (bidStatus.type == "lost") {
47+
if (bidStatus.result) {
48+
resultDetails = `, transaction ${bidStatus.result}`;
49+
}
50+
}
51+
console.log(
52+
`Bid status for bid ${bidStatus.id}: ${bidStatus.type.replaceAll(
53+
"_",
54+
" "
55+
)}${resultDetails}`
56+
);
57+
}
58+
59+
async dummyBid() {
60+
const secretKey = anchor.utils.bytes.bs58.decode(this.privateKey);
61+
const searcher = Keypair.fromSecretKey(secretKey);
62+
63+
const provider = new AnchorProvider(
64+
this.connectionSvm,
65+
new anchor.Wallet(searcher),
66+
{}
67+
);
68+
const dummy = new Program<Dummy>(dummyIdl as Dummy, provider);
69+
70+
const permission = PublicKey.default;
71+
const router = Keypair.generate().publicKey;
72+
const bidAmount = new anchor.BN(argv.bid);
73+
74+
const svmConstants = SVM_CONSTANTS[this.chainId];
75+
if (!(this.chainId in DUMMY_PIDS)) {
76+
throw new Error(`Dummy program id not found for chain ${this.chainId}`);
77+
}
78+
const dummyPid = DUMMY_PIDS[this.chainId];
79+
80+
const ixDummy = await dummy.methods
81+
.doNothing()
82+
.accountsStrict({
83+
payer: searcher.publicKey,
84+
expressRelay: svmConstants.expressRelayProgram,
85+
sysvarInstructions: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
86+
permission,
87+
router,
88+
})
89+
.instruction();
90+
ixDummy.programId = dummyPid;
91+
92+
const txRaw = new anchor.web3.Transaction().add(ixDummy);
93+
94+
const bid = await this.client.constructSvmBid(
95+
txRaw,
96+
searcher.publicKey,
97+
router,
98+
permission,
99+
bidAmount,
100+
new anchor.BN(Math.round(Date.now() / 1000 + DAY_IN_SECONDS)),
101+
this.chainId
102+
);
103+
104+
try {
105+
const { blockhash } = await this.connectionSvm.getLatestBlockhash();
106+
bid.transaction.recentBlockhash = blockhash;
107+
bid.transaction.sign(Keypair.fromSecretKey(secretKey));
108+
const bidId = await this.client.submitBid(bid);
109+
console.log(`Successful bid. Bid id ${bidId}`);
110+
} catch (error) {
111+
console.error(`Failed to bid: ${error}`);
112+
}
113+
}
114+
115+
async start() {
116+
for (;;) {
117+
await this.dummyBid();
118+
await new Promise((resolve) => setTimeout(resolve, 2000));
119+
}
120+
}
121+
}
122+
123+
const argv = yargs(hideBin(process.argv))
124+
.option("endpoint-express-relay", {
125+
description:
126+
"Express relay endpoint. e.g: https://per-staging.dourolabs.app/",
127+
type: "string",
128+
demandOption: true,
129+
})
130+
.option("chain-id", {
131+
description: "Chain id to fetch opportunities for. e.g: solana",
132+
type: "string",
133+
demandOption: true,
134+
})
135+
.option("bid", {
136+
description: "Bid amount in lamports",
137+
type: "string",
138+
default: "100",
139+
})
140+
.option("private-key", {
141+
description: "Private key to sign the bid with. In 64-byte base58 format",
142+
type: "string",
143+
demandOption: true,
144+
})
145+
.option("api-key", {
146+
description:
147+
"The API key of the searcher to authenticate with the server for fetching and submitting bids",
148+
type: "string",
149+
demandOption: false,
150+
})
151+
.option("endpoint-svm", {
152+
description: "SVM RPC endpoint",
153+
type: "string",
154+
demandOption: true,
155+
})
156+
.help()
157+
.alias("help", "h")
158+
.parseSync();
159+
async function run() {
160+
if (SVM_CONSTANTS[argv.chainId] === undefined) {
161+
throw new Error(`SVM constants not found for chain ${argv.chainId}`);
162+
}
163+
const searcherSvm = Keypair.fromSecretKey(
164+
anchor.utils.bytes.bs58.decode(argv.privateKey)
165+
);
166+
console.log(`Using searcher pubkey: ${searcherSvm.publicKey.toBase58()}`);
167+
168+
const simpleSearcher = new SimpleSearcherSvm(
169+
argv.endpointExpressRelay,
170+
argv.chainId,
171+
argv.privateKey,
172+
argv.endpointSvm,
173+
argv.apiKey
174+
);
175+
await simpleSearcher.start();
176+
}
177+
178+
run();

0 commit comments

Comments
 (0)