Skip to content

Commit 29c5e0f

Browse files
authored
feat(contract_manager): add starknet contract (#1715)
1 parent 6c7ead7 commit 29c5e0f

File tree

8 files changed

+227
-2
lines changed

8 files changed

+227
-2
lines changed

contract_manager/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
"@pythnetwork/cosmwasm-deploy-tools": "workspace:*",
3333
"@pythnetwork/entropy-sdk-solidity": "workspace:*",
3434
"@pythnetwork/price-service-client": "workspace:*",
35-
"@pythnetwork/pyth-sdk-solidity": "workspace:^",
3635
"@pythnetwork/pyth-fuel-js": "workspace:*",
36+
"@pythnetwork/pyth-sdk-solidity": "workspace:^",
3737
"@pythnetwork/pyth-sui-js": "workspace:*",
3838
"@pythnetwork/solana-utils": "workspace:^",
3939
"@pythnetwork/xc-admin-common": "workspace:*",
@@ -45,6 +45,7 @@
4545
"extract-files": "^13.0.0",
4646
"fuels": "^0.89.2",
4747
"ramda": "^0.30.1",
48+
"starknet": "^5.24.3",
4849
"ts-node": "^10.9.1",
4950
"typescript": "^5.3.3",
5051
"web3": "^1.8.2",

contract_manager/src/chains.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
2424
import { TokenId } from "./token";
2525
import { BN, Provider, Wallet, WalletUnlocked } from "fuels";
2626
import { FUEL_ETH_ASSET_ID } from "@pythnetwork/pyth-fuel-js";
27+
import { RpcProvider } from "starknet";
2728

2829
export type ChainConfig = Record<string, string> & {
2930
mainnet: boolean;
@@ -630,3 +631,62 @@ export class FuelChain extends Chain {
630631
return Number(balance) / 10 ** 9;
631632
}
632633
}
634+
635+
export class StarknetChain extends Chain {
636+
static type = "StarknetChain";
637+
638+
constructor(
639+
id: string,
640+
mainnet: boolean,
641+
wormholeChainName: string,
642+
public rpcUrl: string
643+
) {
644+
super(id, mainnet, wormholeChainName, undefined);
645+
}
646+
647+
getType(): string {
648+
return StarknetChain.type;
649+
}
650+
651+
toJson(): KeyValueConfig {
652+
return {
653+
id: this.id,
654+
wormholeChainName: this.wormholeChainName,
655+
mainnet: this.mainnet,
656+
rpcUrl: this.rpcUrl,
657+
type: StarknetChain.type,
658+
};
659+
}
660+
661+
static fromJson(parsed: ChainConfig): StarknetChain {
662+
if (parsed.type !== StarknetChain.type) throw new Error("Invalid type");
663+
return new StarknetChain(
664+
parsed.id,
665+
parsed.mainnet,
666+
parsed.wormholeChainName,
667+
parsed.rpcUrl
668+
);
669+
}
670+
671+
/**
672+
* Returns the payload for a governance contract upgrade instruction for contracts deployed on this chain
673+
* @param digest hex string of the felt252 class hash of the new contract class extended to uint256 in BE
674+
*/
675+
generateGovernanceUpgradePayload(digest: string): Buffer {
676+
return new UpgradeContract256Bit(this.wormholeChainName, digest).encode();
677+
}
678+
679+
// Account address derivation on Starknet depends
680+
// on the wallet application and constructor arguments used.
681+
async getAccountAddress(privateKey: PrivateKey): Promise<string> {
682+
throw new Error("Unsupported");
683+
}
684+
685+
async getAccountBalance(privateKey: PrivateKey): Promise<number> {
686+
throw new Error("Unsupported");
687+
}
688+
689+
getProvider(): RpcProvider {
690+
return new RpcProvider({ nodeUrl: this.rpcUrl });
691+
}
692+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { DataSource } from "@pythnetwork/xc-admin-common";
2+
import {
3+
KeyValueConfig,
4+
PriceFeed,
5+
PriceFeedContract,
6+
PrivateKey,
7+
TxResult,
8+
} from "../base";
9+
import { Chain, StarknetChain } from "../chains";
10+
import { Contract } from "starknet";
11+
12+
export class StarknetPriceFeedContract extends PriceFeedContract {
13+
static type = "StarknetPriceFeedContract";
14+
15+
constructor(public chain: StarknetChain, public address: string) {
16+
super();
17+
}
18+
19+
static fromJson(
20+
chain: Chain,
21+
parsed: {
22+
type: string;
23+
address: string;
24+
}
25+
): StarknetPriceFeedContract {
26+
if (parsed.type !== StarknetPriceFeedContract.type)
27+
throw new Error("Invalid type");
28+
if (!(chain instanceof StarknetChain))
29+
throw new Error(`Wrong chain type ${chain}`);
30+
return new StarknetPriceFeedContract(chain, parsed.address);
31+
}
32+
33+
toJson(): KeyValueConfig {
34+
return {
35+
chain: this.chain.getId(),
36+
address: this.address,
37+
type: StarknetPriceFeedContract.type,
38+
};
39+
}
40+
41+
// Not implemented in the Starknet contract.
42+
getValidTimePeriod(): Promise<number> {
43+
throw new Error("Unsupported");
44+
}
45+
46+
getChain(): StarknetChain {
47+
return this.chain;
48+
}
49+
50+
async getContractClient(): Promise<Contract> {
51+
const provider = this.chain.getProvider();
52+
const classData = await provider.getClassAt(this.address);
53+
return new Contract(classData.abi, this.address, provider);
54+
}
55+
56+
async getDataSources(): Promise<DataSource[]> {
57+
const contract = await this.getContractClient();
58+
const sources: { emitter_chain_id: bigint; emitter_address: bigint }[] =
59+
await contract.valid_data_sources();
60+
return sources.map((source) => {
61+
return {
62+
emitterChain: Number(source.emitter_chain_id),
63+
emitterAddress: source.emitter_address.toString(16),
64+
};
65+
});
66+
}
67+
68+
getBaseUpdateFee(): Promise<{ amount: string; denom?: string | undefined }> {
69+
throw new Error("Method not implemented.");
70+
}
71+
getLastExecutedGovernanceSequence(): Promise<number> {
72+
throw new Error("Method not implemented.");
73+
}
74+
getPriceFeed(feedId: string): Promise<PriceFeed | undefined> {
75+
throw new Error("Method not implemented.");
76+
}
77+
executeUpdatePriceFeed(
78+
senderPrivateKey: PrivateKey,
79+
vaas: Buffer[]
80+
): Promise<TxResult> {
81+
throw new Error("Method not implemented.");
82+
}
83+
executeGovernanceInstruction(
84+
senderPrivateKey: PrivateKey,
85+
vaa: Buffer
86+
): Promise<TxResult> {
87+
throw new Error("Method not implemented.");
88+
}
89+
getGovernanceDataSource(): Promise<DataSource> {
90+
throw new Error("Method not implemented.");
91+
}
92+
93+
getId(): string {
94+
return `${this.chain.getId()}_${this.address}`;
95+
}
96+
97+
getType(): string {
98+
return StarknetPriceFeedContract.type;
99+
}
100+
}

contract_manager/src/shell.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ repl.setService(service);
66
repl.start();
77
repl.evalCode(
88
"import { loadHotWallet, Vault } from './src/governance';" +
9-
"import { SuiChain, CosmWasmChain, AptosChain, EvmChain } from './src/chains';" +
9+
"import { SuiChain, CosmWasmChain, AptosChain, EvmChain, StarknetChain } from './src/chains';" +
1010
"import { SuiPriceFeedContract } from './src/contracts/sui';" +
1111
"import { CosmWasmWormholeContract, CosmWasmPriceFeedContract } from './src/contracts/cosmwasm';" +
1212
"import { EvmWormholeContract, EvmPriceFeedContract } from './src/contracts/evm';" +
1313
"import { AptosWormholeContract, AptosPriceFeedContract } from './src/contracts/aptos';" +
14+
"import { StarknetPriceFeedContract } from './src/contracts/starknet';" +
1415
"import { DefaultStore } from './src/store';" +
1516
"import { toPrivateKey } from './src/base';" +
1617
"DefaultStore"

contract_manager/src/store.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
AptosChain,
33
Chain,
44
CosmWasmChain,
5+
StarknetChain,
56
EvmChain,
67
FuelChain,
78
GlobalChain,
@@ -26,6 +27,7 @@ import { PriceFeedContract, Storable } from "./base";
2627
import { parse, stringify } from "yaml";
2728
import { readdirSync, readFileSync, statSync, writeFileSync } from "fs";
2829
import { Vault } from "./governance";
30+
import { StarknetPriceFeedContract } from "./contracts/starknet";
2931

3032
export class Store {
3133
public chains: Record<string, Chain> = { global: new GlobalChain() };
@@ -73,6 +75,7 @@ export class Store {
7375
[EvmChain.type]: EvmChain,
7476
[AptosChain.type]: AptosChain,
7577
[FuelChain.type]: FuelChain,
78+
[StarknetChain.type]: StarknetChain,
7679
};
7780

7881
this.getYamlFiles(`${this.path}/chains/`).forEach((yamlFile) => {
@@ -135,6 +138,7 @@ export class Store {
135138
[EvmWormholeContract.type]: EvmWormholeContract,
136139
[FuelPriceFeedContract.type]: FuelPriceFeedContract,
137140
[FuelWormholeContract.type]: FuelWormholeContract,
141+
[StarknetPriceFeedContract.type]: StarknetPriceFeedContract,
138142
};
139143
this.getYamlFiles(`${this.path}/contracts/`).forEach((yamlFile) => {
140144
const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
- id: starknet_sepolia
2+
wormholeChainName: starknet_sepolia
3+
mainnet: false
4+
rpcUrl: https://starknet-sepolia.public.blastapi.io/
5+
type: StarknetChain
6+
- id: starknet_mainnet
7+
wormholeChainName: starknet
8+
mainnet: true
9+
rpcUrl: https://starknet-mainnet.public.blastapi.io/
10+
type: StarknetChain
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- chain: starknet_sepolia
2+
type: StarknetPriceFeedContract
3+
address: "0x07f2b07b6b5365e7ee055bda4c0ecabd867e6d3ee298d73aea32b027667186d6"

pnpm-lock.yaml

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)