Skip to content

Commit 5cbab9e

Browse files
committed
feat(contract_manager): add near contract support (wip)
1 parent 3f048bc commit 5cbab9e

File tree

4 files changed

+210
-18
lines changed

4 files changed

+210
-18
lines changed

contract_manager/src/chains.ts

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
import { keyPairFromSeed } from "@ton/crypto";
3838
import { PythContract } from "@pythnetwork/pyth-ton-js";
3939
import * as nearAPI from "near-api-js";
40+
import * as bs58 from "bs58";
4041

4142
/**
4243
* Returns the chain rpc url with any environment variables replaced or throws an error if any are missing
@@ -862,14 +863,16 @@ export class TonChain extends Chain {
862863

863864
export class NearChain extends Chain {
864865
static type = "NearChain";
866+
networkId: string;
865867

866868
constructor(
867869
id: string,
868870
mainnet: boolean,
869871
wormholeChainName: string,
870-
nativeToken: TokenId | undefined,
872+
nativeToken: TokenId | undefined
871873
) {
872874
super(id, mainnet, wormholeChainName, nativeToken);
875+
this.networkId = this.mainnet ? "mainnet" : "testnet";
873876
}
874877

875878
static fromJson(parsed: ChainConfig & { networkId: number }): NearChain {
@@ -878,7 +881,7 @@ export class NearChain extends Chain {
878881
parsed.id,
879882
parsed.mainnet,
880883
parsed.wormholeChainName,
881-
parsed.nativeToken,
884+
parsed.nativeToken
882885
);
883886
}
884887

@@ -896,31 +899,41 @@ export class NearChain extends Chain {
896899
}
897900

898901
generateGovernanceUpgradePayload(upgradeInfo: unknown): Buffer {
899-
throw new Error("unsupported")
902+
throw new Error("unsupported");
900903
}
901904

902905
async getAccountAddress(privateKey: PrivateKey): Promise<string> {
903-
return Buffer.from(Ed25519Keypair.fromSecretKey(
904-
Buffer.from(privateKey, "hex")
905-
).getPublicKey().toRawBytes()).toString("hex");
906+
return Buffer.from(
907+
Ed25519Keypair.fromSecretKey(Buffer.from(privateKey, "hex"))
908+
.getPublicKey()
909+
.toRawBytes()
910+
).toString("hex");
906911
}
907912

908913
async getAccountBalance(privateKey: PrivateKey): Promise<number> {
909914
const accountId = await this.getAccountAddress(privateKey);
910-
const { connect, keyStores, KeyPair, utils } = nearAPI;
915+
const account = await this.getNearAccount(accountId);
916+
const balance = await account.getAccountBalance();
917+
return Number(balance.available) / 1e24;
918+
}
911919

912-
const myKeyStore = new keyStores.InMemoryKeyStore();
913-
// const keyPair = KeyPair.fromString(privateKey);
914-
// await myKeyStore.setKey("testnet", accountId, keyPair);
915-
const networkId = this.mainnet ? "mainnet" : "testnet";
920+
async getNearAccount(
921+
accountId: string,
922+
senderPrivateKey?: PrivateKey
923+
): Promise<nearAPI.Account> {
924+
const keyStore = new nearAPI.keyStores.InMemoryKeyStore();
925+
if (typeof senderPrivateKey !== "undefined") {
926+
const key = bs58.encode(Buffer.from(senderPrivateKey, "hex"));
927+
const keyPair = nearAPI.KeyPair.fromString(key);
928+
const address = await this.getAccountAddress(senderPrivateKey);
929+
await keyStore.setKey(this.networkId, address, keyPair);
930+
}
916931
const connectionConfig = {
917-
networkId: this.mainnet ? "mainnet" : "testnet",
918-
keyStore: myKeyStore,
919-
nodeUrl: `https://rpc.${networkId}.near.org`,
932+
networkId: this.networkId,
933+
keyStore,
934+
nodeUrl: `https://rpc.${this.networkId}.near.org`,
920935
};
921-
const nearConnection = await connect(connectionConfig);
922-
const account = await nearConnection.account(accountId);
923-
const balance = await account.getAccountBalance();
924-
return Number(balance.available) / 1e24;
936+
const nearConnection = await nearAPI.connect(connectionConfig);
937+
return await nearConnection.account(accountId);
925938
}
926939
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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 * as bs58 from "bs58";
12+
import { BN } from "fuels";
13+
14+
export class NearPriceFeedContract extends PriceFeedContract {
15+
public static type = "NearPriceFeedContract";
16+
17+
constructor(public chain: NearChain, public address: string) {
18+
super();
19+
}
20+
21+
static fromJson(
22+
chain: Chain,
23+
parsed: { type: string; address: string }
24+
): NearPriceFeedContract {
25+
if (parsed.type !== NearPriceFeedContract.type) {
26+
throw new Error("Invalid type");
27+
}
28+
if (!(chain instanceof NearChain)) {
29+
throw new Error(`Wrong chain type ${chain}`);
30+
}
31+
return new NearPriceFeedContract(chain, parsed.address);
32+
}
33+
34+
getChain(): NearChain {
35+
return this.chain;
36+
}
37+
38+
async getContractNearAccount(
39+
senderPrivateKey?: PrivateKey
40+
): Promise<nearAPI.Account> {
41+
return await this.chain.getNearAccount(this.address, senderPrivateKey);
42+
}
43+
44+
async getValidTimePeriod(): Promise<number> {
45+
const account = await this.getContractNearAccount();
46+
return account.viewFunction({
47+
contractId: this.address,
48+
methodName: "get_stale_threshold",
49+
});
50+
}
51+
52+
async getDataSources(): Promise<DataSource[]> {
53+
const account = await this.getContractNearAccount();
54+
const outcome: [{ emitter: number[]; chain: number }] =
55+
await account.viewFunction({
56+
contractId: this.address,
57+
methodName: "get_sources",
58+
});
59+
return outcome.map((item) => {
60+
return {
61+
emitterChain: item.chain,
62+
emitterAddress: Buffer.from(item.emitter).toString("hex"),
63+
};
64+
});
65+
}
66+
67+
async getPriceFeed(feedId: string): Promise<PriceFeed | undefined> {
68+
const account = await this.getContractNearAccount();
69+
const price: {
70+
price: string;
71+
conf: string;
72+
expo: number;
73+
publish_time: number;
74+
} | null = await account.viewFunction({
75+
contractId: this.address,
76+
methodName: "get_price_unsafe",
77+
args: { price_identifier: feedId },
78+
});
79+
const emaPrice: {
80+
price: string;
81+
conf: string;
82+
expo: number;
83+
publish_time: number;
84+
} | null = await account.viewFunction({
85+
contractId: this.address,
86+
methodName: "get_ema_price_unsafe",
87+
args: { price_id: feedId },
88+
});
89+
if (price === null || emaPrice === null) {
90+
return undefined;
91+
} else {
92+
return {
93+
price: {
94+
price: price.price,
95+
conf: price.conf,
96+
expo: price.expo.toString(),
97+
publishTime: price.publish_time.toString(),
98+
},
99+
emaPrice: {
100+
price: emaPrice.price,
101+
conf: emaPrice.conf,
102+
expo: emaPrice.expo.toString(),
103+
publishTime: emaPrice.publish_time.toString(),
104+
},
105+
};
106+
}
107+
}
108+
109+
async executeUpdatePriceFeed(
110+
senderPrivateKey: PrivateKey,
111+
vaas: Buffer[]
112+
): Promise<TxResult> {
113+
if (vaas.length === 0) {
114+
throw new Error("no vaas specified");
115+
}
116+
const address = await this.chain.getAccountAddress(senderPrivateKey);
117+
const account = await this.chain.getNearAccount(address, senderPrivateKey);
118+
let results = [];
119+
for (let vaa of vaas) {
120+
const outcome = await account.functionCall({
121+
contractId: this.address,
122+
methodName: "update_price_feeds",
123+
args: { data: vaa.toString("hex") },
124+
gas: new BN(300e12),
125+
attachedDeposit: new BN(1e12),
126+
});
127+
console.log("outcome", outcome);
128+
results.push({ id: outcome.transaction.hash, info: outcome });
129+
}
130+
if (results.length === 1) {
131+
return results[0];
132+
} else {
133+
return {
134+
id: results.map((x) => x.id).join(","),
135+
info: results.map((x) => x.info),
136+
};
137+
}
138+
}
139+
140+
getBaseUpdateFee(): Promise<{ amount: string; denom?: string }> {
141+
throw new Error("near contract doesn't implement getBaseUpdateFee method");
142+
}
143+
getLastExecutedGovernanceSequence(): Promise<number> {
144+
throw new Error(
145+
"near contract doesn't implement getLastExecutedGovernanceSequence method"
146+
);
147+
}
148+
149+
executeGovernanceInstruction(
150+
senderPrivateKey: PrivateKey,
151+
vaa: Buffer
152+
): Promise<TxResult> {
153+
throw new Error("Method not implemented.");
154+
}
155+
getGovernanceDataSource(): Promise<DataSource> {
156+
throw new Error("Method not implemented.");
157+
}
158+
getId(): string {
159+
return `${this.chain.getId()}_${this.address.replace(/-|\./g, "_")}`;
160+
}
161+
getType(): string {
162+
return NearPriceFeedContract.type;
163+
}
164+
toJson(): KeyValueConfig {
165+
return {
166+
chain: this.chain.getId(),
167+
address: this.address,
168+
type: NearPriceFeedContract.type,
169+
};
170+
}
171+
}

contract_manager/src/store.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
StarknetPriceFeedContract,
3737
StarknetWormholeContract,
3838
} from "./contracts/starknet";
39+
import { NearPriceFeedContract } from "./contracts/near";
3940

4041
export class Store {
4142
public chains: Record<string, Chain> = { global: new GlobalChain() };
@@ -158,6 +159,7 @@ export class Store {
158159
[StarknetWormholeContract.type]: StarknetWormholeContract,
159160
[TonPriceFeedContract.type]: TonPriceFeedContract,
160161
[TonWormholeContract.type]: TonWormholeContract,
162+
[NearPriceFeedContract.type]: NearPriceFeedContract,
161163
};
162164
this.getYamlFiles(`${this.path}/contracts/`).forEach((yamlFile) => {
163165
const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- chain: near
2+
address: pyth-oracle.near
3+
type: NearPriceFeedContract
4+
- chain: near_testnet
5+
address: pyth-oracle.testnet
6+
type: NearPriceFeedContract

0 commit comments

Comments
 (0)