Skip to content

Commit 08ceaa7

Browse files
authored
feat(contract_manager): add near chains and contracts (#2331)
* wip * feat(contract_manager): near chain support (wip) * feat(contract_manager): add near contract support (wip) * feat(contract_manager): support near wormhole contract and upgrade payload * feat(contract_manager): use real near wormhole chain id * chore: fix ci * feat(contract_manager): store near rpc url and network id in yaml * chore: fix typo * chore: move near wormhole id to a separate section with comment
1 parent c171c3e commit 08ceaa7

File tree

10 files changed

+389
-2
lines changed

10 files changed

+389
-2
lines changed

contract_manager/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@
3636
"@pythnetwork/pyth-sdk-solidity": "workspace:^",
3737
"@pythnetwork/pyth-starknet-js": "^0.2.1",
3838
"@pythnetwork/pyth-sui-js": "workspace:*",
39-
"@pythnetwork/pyth-ton-js": "workspace:*",
4039
"@pythnetwork/pyth-ton": "workspace:*",
40+
"@pythnetwork/pyth-ton-js": "workspace:*",
4141
"@pythnetwork/solana-utils": "workspace:^",
4242
"@pythnetwork/xc-admin-common": "workspace:*",
4343
"@solana/web3.js": "^1.73.0",
@@ -52,6 +52,7 @@
5252
"bs58": "^5.0.0",
5353
"extract-files": "^13.0.0",
5454
"fuels": "^0.94.0",
55+
"near-api-js": "^3.0.2",
5556
"ramda": "^0.30.1",
5657
"starknet": "^6.9.0",
5758
"ts-node": "^10.9.1",

contract_manager/src/chains.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import {
3636
} from "@ton/ton";
3737
import { keyPairFromSeed } from "@ton/crypto";
3838
import { PythContract } from "@pythnetwork/pyth-ton-js";
39+
import * as nearAPI from "near-api-js";
40+
import * as bs58 from "bs58";
3941

4042
/**
4143
* Returns the chain rpc url with any environment variables replaced or throws an error if any are missing
@@ -858,3 +860,88 @@ export class TonChain extends Chain {
858860
return Number(balance) / 10 ** 9;
859861
}
860862
}
863+
864+
export class NearChain extends Chain {
865+
static type = "NearChain";
866+
867+
constructor(
868+
id: string,
869+
mainnet: boolean,
870+
wormholeChainName: string,
871+
nativeToken: TokenId | undefined,
872+
private rpcUrl: string,
873+
private networkId: string
874+
) {
875+
super(id, mainnet, wormholeChainName, nativeToken);
876+
}
877+
878+
static fromJson(parsed: ChainConfig): NearChain {
879+
if (parsed.type !== NearChain.type) throw new Error("Invalid type");
880+
return new NearChain(
881+
parsed.id,
882+
parsed.mainnet,
883+
parsed.wormholeChainName,
884+
parsed.nativeToken,
885+
parsed.rpcUrl,
886+
parsed.networkId
887+
);
888+
}
889+
890+
getType(): string {
891+
return NearChain.type;
892+
}
893+
894+
toJson(): KeyValueConfig {
895+
return {
896+
id: this.id,
897+
wormholeChainName: this.wormholeChainName,
898+
mainnet: this.mainnet,
899+
type: NearChain.type,
900+
rpcUrl: this.rpcUrl,
901+
networkId: this.networkId,
902+
};
903+
}
904+
905+
/**
906+
* Returns the payload for a governance contract upgrade instruction for contracts deployed on this chain
907+
* @param codeHash hex string of the 32 byte code hash for the new contract without the 0x prefix
908+
*/
909+
generateGovernanceUpgradePayload(codeHash: string): Buffer {
910+
return new UpgradeContract256Bit(this.wormholeChainName, codeHash).encode();
911+
}
912+
913+
async getAccountAddress(privateKey: PrivateKey): Promise<string> {
914+
return Buffer.from(
915+
Ed25519Keypair.fromSecretKey(Buffer.from(privateKey, "hex"))
916+
.getPublicKey()
917+
.toRawBytes()
918+
).toString("hex");
919+
}
920+
921+
async getAccountBalance(privateKey: PrivateKey): Promise<number> {
922+
const accountId = await this.getAccountAddress(privateKey);
923+
const account = await this.getNearAccount(accountId);
924+
const balance = await account.getAccountBalance();
925+
return Number(balance.available) / 1e24;
926+
}
927+
928+
async getNearAccount(
929+
accountId: string,
930+
senderPrivateKey?: PrivateKey
931+
): Promise<nearAPI.Account> {
932+
const keyStore = new nearAPI.keyStores.InMemoryKeyStore();
933+
if (typeof senderPrivateKey !== "undefined") {
934+
const key = bs58.encode(Buffer.from(senderPrivateKey, "hex"));
935+
const keyPair = nearAPI.KeyPair.fromString(key);
936+
const address = await this.getAccountAddress(senderPrivateKey);
937+
await keyStore.setKey(this.networkId, address, keyPair);
938+
}
939+
const connectionConfig = {
940+
networkId: this.networkId,
941+
keyStore,
942+
nodeUrl: this.rpcUrl,
943+
};
944+
const nearConnection = await nearAPI.connect(connectionConfig);
945+
return await nearConnection.account(accountId);
946+
}
947+
}
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
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, NearChain } from "../chains";
10+
import * as nearAPI from "near-api-js";
11+
import { BN } from "fuels";
12+
import { WormholeContract } from "./wormhole";
13+
14+
export class NearWormholeContract extends WormholeContract {
15+
static type = "NearWormholeContract";
16+
17+
constructor(public chain: NearChain, public address: string) {
18+
super();
19+
}
20+
21+
getId(): string {
22+
return `${this.chain.getId()}__${this.address.replace(/-|\./g, "_")}`;
23+
}
24+
25+
getChain(): NearChain {
26+
return this.chain;
27+
}
28+
29+
getType(): string {
30+
return NearWormholeContract.type;
31+
}
32+
33+
static fromJson(
34+
chain: Chain,
35+
parsed: { type: string; address: string }
36+
): NearWormholeContract {
37+
if (parsed.type !== NearWormholeContract.type)
38+
throw new Error("Invalid type");
39+
if (!(chain instanceof NearChain))
40+
throw new Error(`Wrong chain type ${chain}`);
41+
return new NearWormholeContract(chain, parsed.address);
42+
}
43+
44+
toJson(): KeyValueConfig {
45+
return {
46+
chain: this.chain.getId(),
47+
address: this.address,
48+
type: NearWormholeContract.type,
49+
};
50+
}
51+
52+
async upgradeGuardianSets(
53+
senderPrivateKey: PrivateKey,
54+
vaa: Buffer
55+
): Promise<TxResult> {
56+
const senderAddress = await this.chain.getAccountAddress(senderPrivateKey);
57+
const account = await this.chain.getNearAccount(
58+
senderAddress,
59+
senderPrivateKey
60+
);
61+
const outcome = await account.functionCall({
62+
contractId: this.address,
63+
methodName: "submit_vaa",
64+
args: { vaa: vaa.toString("hex") },
65+
gas: new BN(300e12),
66+
attachedDeposit: new BN(1e12),
67+
});
68+
return { id: outcome.transaction.hash, info: outcome };
69+
}
70+
71+
getCurrentGuardianSetIndex(): Promise<number> {
72+
throw new Error(
73+
"near wormhole contract doesn't implement getCurrentGuardianSetIndex method"
74+
);
75+
}
76+
getChainId(): Promise<number> {
77+
throw new Error(
78+
"near wormhole contract doesn't implement getChainId method"
79+
);
80+
}
81+
getGuardianSet(): Promise<string[]> {
82+
throw new Error(
83+
"near wormhole contract doesn't implement getGuardianSet method"
84+
);
85+
}
86+
}
87+
88+
export class NearPriceFeedContract extends PriceFeedContract {
89+
public static type = "NearPriceFeedContract";
90+
91+
constructor(public chain: NearChain, public address: string) {
92+
super();
93+
}
94+
95+
getId(): string {
96+
return `${this.chain.getId()}__${this.address.replace(/-|\./g, "_")}`;
97+
}
98+
99+
getType(): string {
100+
return NearPriceFeedContract.type;
101+
}
102+
103+
getChain(): NearChain {
104+
return this.chain;
105+
}
106+
107+
toJson(): KeyValueConfig {
108+
return {
109+
chain: this.chain.getId(),
110+
address: this.address,
111+
type: NearPriceFeedContract.type,
112+
};
113+
}
114+
115+
static fromJson(
116+
chain: Chain,
117+
parsed: { type: string; address: string }
118+
): NearPriceFeedContract {
119+
if (parsed.type !== NearPriceFeedContract.type) {
120+
throw new Error("Invalid type");
121+
}
122+
if (!(chain instanceof NearChain)) {
123+
throw new Error(`Wrong chain type ${chain}`);
124+
}
125+
return new NearPriceFeedContract(chain, parsed.address);
126+
}
127+
128+
async getContractNearAccount(
129+
senderPrivateKey?: PrivateKey
130+
): Promise<nearAPI.Account> {
131+
return await this.chain.getNearAccount(this.address, senderPrivateKey);
132+
}
133+
134+
async getValidTimePeriod(): Promise<number> {
135+
const account = await this.getContractNearAccount();
136+
return account.viewFunction({
137+
contractId: this.address,
138+
methodName: "get_stale_threshold",
139+
});
140+
}
141+
142+
async getDataSources(): Promise<DataSource[]> {
143+
const account = await this.getContractNearAccount();
144+
const outcome: [{ emitter: number[]; chain: number }] =
145+
await account.viewFunction({
146+
contractId: this.address,
147+
methodName: "get_sources",
148+
});
149+
return outcome.map((item) => {
150+
return {
151+
emitterChain: item.chain,
152+
emitterAddress: Buffer.from(item.emitter).toString("hex"),
153+
};
154+
});
155+
}
156+
157+
async getPriceFeed(feedId: string): Promise<PriceFeed | undefined> {
158+
const account = await this.getContractNearAccount();
159+
const price: {
160+
price: string;
161+
conf: string;
162+
expo: number;
163+
publish_time: number;
164+
} | null = await account.viewFunction({
165+
contractId: this.address,
166+
methodName: "get_price_unsafe",
167+
args: { price_identifier: feedId },
168+
});
169+
const emaPrice: {
170+
price: string;
171+
conf: string;
172+
expo: number;
173+
publish_time: number;
174+
} | null = await account.viewFunction({
175+
contractId: this.address,
176+
methodName: "get_ema_price_unsafe",
177+
args: { price_id: feedId },
178+
});
179+
if (price === null || emaPrice === null) {
180+
return undefined;
181+
} else {
182+
return {
183+
price: {
184+
price: price.price,
185+
conf: price.conf,
186+
expo: price.expo.toString(),
187+
publishTime: price.publish_time.toString(),
188+
},
189+
emaPrice: {
190+
price: emaPrice.price,
191+
conf: emaPrice.conf,
192+
expo: emaPrice.expo.toString(),
193+
publishTime: emaPrice.publish_time.toString(),
194+
},
195+
};
196+
}
197+
}
198+
199+
async executeUpdatePriceFeed(
200+
senderPrivateKey: PrivateKey,
201+
vaas: Buffer[]
202+
): Promise<TxResult> {
203+
if (vaas.length === 0) {
204+
throw new Error("no vaas specified");
205+
}
206+
const senderAddress = await this.chain.getAccountAddress(senderPrivateKey);
207+
const account = await this.chain.getNearAccount(
208+
senderAddress,
209+
senderPrivateKey
210+
);
211+
const results = [];
212+
for (const vaa of vaas) {
213+
const outcome = await account.functionCall({
214+
contractId: this.address,
215+
methodName: "update_price_feeds",
216+
args: { data: vaa.toString("hex") },
217+
gas: new BN(300e12),
218+
attachedDeposit: new BN(1e12),
219+
});
220+
results.push({ id: outcome.transaction.hash, info: outcome });
221+
}
222+
if (results.length === 1) {
223+
return results[0];
224+
} else {
225+
return {
226+
id: results.map((x) => x.id).join(","),
227+
info: results.map((x) => x.info),
228+
};
229+
}
230+
}
231+
232+
async executeGovernanceInstruction(
233+
senderPrivateKey: PrivateKey,
234+
vaa: Buffer
235+
): Promise<TxResult> {
236+
const senderAddress = await this.chain.getAccountAddress(senderPrivateKey);
237+
const account = await this.chain.getNearAccount(
238+
senderAddress,
239+
senderPrivateKey
240+
);
241+
const outcome = await account.functionCall({
242+
contractId: this.address,
243+
methodName: "execute_governance_instruction",
244+
args: { vaa: vaa.toString("hex") },
245+
gas: new BN(300e12),
246+
attachedDeposit: new BN(1e12),
247+
});
248+
return { id: outcome.transaction.hash, info: outcome };
249+
}
250+
251+
getBaseUpdateFee(): Promise<{ amount: string; denom?: string }> {
252+
throw new Error("near contract doesn't implement getBaseUpdateFee method");
253+
}
254+
getLastExecutedGovernanceSequence(): Promise<number> {
255+
throw new Error(
256+
"near contract doesn't implement getLastExecutedGovernanceSequence method"
257+
);
258+
}
259+
getGovernanceDataSource(): Promise<DataSource> {
260+
throw new Error(
261+
"near contract doesn't implement getGovernanceDataSource method"
262+
);
263+
}
264+
}

0 commit comments

Comments
 (0)