From 40a2afd93b7544dab280ab2ef05e112aa0cd1e07 Mon Sep 17 00:00:00 2001 From: imsk17 Date: Tue, 17 Sep 2024 17:48:27 +0530 Subject: [PATCH] feat(near): configure chain --- .env.example | 4 +- src/config.ts | 16 ++++- src/deps.ts | 72 +++++++++++++++++++ src/environment.ts | 1 + src/handler/near/index.ts | 4 +- src/handler/near/utils/addSelfAsValidator.ts | 2 +- src/handler/near/utils/generateWallet.ts | 7 +- src/handler/near/utils/listenForLockEvents.ts | 29 +++++--- src/handler/near/utils/nftData.ts | 2 +- src/handler/near/utils/selfIsValidator.ts | 11 ++- src/index.ts | 16 ++--- src/types/index.ts | 19 ++++- src/utils.ts | 5 ++ 13 files changed, 156 insertions(+), 32 deletions(-) diff --git a/.env.example b/.env.example index 1a343e27..41611f94 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,6 @@ # The API Key for Ton RPC TON_API_KEY= # The PORT to serve HTTP Requests -SERVER_PORT= \ No newline at end of file +SERVER_PORT= +# Nearblocks API Key +NEARBLOCKS_API_KEY= \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index 19ff20ce..0c768d09 100644 --- a/src/config.ts +++ b/src/config.ts @@ -84,6 +84,18 @@ export const bridgeTestChains = [ nativeCoinSymbol: "ICP", rpcURL: "https://tools.xp.network/", }, + // { + // chain: "NEAR", + // chainType: "near", + // contractAddress: "xp-bridge-test.testnet", + // decimals: 24, + // intialFund: "100000000000000000000000", + // lastBlock: 960126871, + // nativeCoinSymbol: "NEAR", + // nearBlocksUrl: "https://api-testnet.nearblocks.io/v1/", + // networkId: "testnet", + // rpcURL: "https://archival-rpc.testnet.near.org", + // }, ] as const satisfies TChain[]; export const storageTestnetConfig: IEvmChainConfig = { @@ -217,5 +229,5 @@ export const prodBridgeConfig: IBridgeConfig = { }; export type TSupportedChainsConfig = (typeof bridgeTestChains)[number]; -export type TSupportedChains = TSupportedChainsConfig["chain"]; -export type TSupportedChainTypes = TSupportedChainsConfig["chainType"]; +export type TSupportedChains = TChain["chain"]; +export type TSupportedChainTypes = TChain["chainType"]; diff --git a/src/deps.ts b/src/deps.ts index 1a519940..b520e06c 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -27,10 +27,17 @@ import { Ed25519KeyIdentity } from "@dfinity/identity"; import type { EntityManager } from "@mikro-orm/sqlite"; import { Mutex } from "async-mutex"; import axios, { type AxiosInstance } from "axios"; +import { + KeyPair, + InMemorySigner as NearInMemorySigner, + connect, + keyStores, +} from "near-api-js"; import { ERC20Staking__factory } from "./contractsTypes/evm"; import { cosmWasmHandler } from "./handler/cosmos"; import { evmStakingHandler } from "./handler/evm/stakingHandler"; import { icpHandler } from "./handler/icp"; +import { nearHandler } from "./handler/near"; import type { LogInstance, THandler } from "./handler/types"; import MikroOrmConfig from "./mikro-orm.config"; import { Block } from "./persistence/entities/block"; @@ -46,6 +53,8 @@ import type { IICPWallet, IMultiversXChainConfig, IMultiversXWallet, + INearChainConfig, + INearWallet, ISecretChainConfig, ISecretWallet, IStakingConfig, @@ -337,6 +346,53 @@ export async function configMultiversXHandler( }); } +export async function configNearHandler( + conf: INearChainConfig, + storage: BridgeStorage, + em: EntityManager, + nearWallet: INearWallet, + serverLinkHandler: AxiosInstance | undefined, + nearLogger: LogInstance, + staking: ERC20Staking, + validatorAddress: string, +) { + const near = await connect({ + networkId: conf.networkId, + nodeUrl: conf.rpcURL, + }); + + const ks = new keyStores.InMemoryKeyStore(); + + ks.setKey( + conf.networkId, + nearWallet.accountId, + KeyPair.fromString(nearWallet.secretKey as never), + ); + + return nearHandler({ + near, + address: nearWallet.accountId, + bridge: conf.contractAddress, + chainIdent: "NEAR", + chainType: "near", + decimals: 24, + em: em.fork(), + initialFunds: BigInt(conf.intialFund), + lastBlock_: conf.lastBlock, + logger: nearLogger, + // biome-ignore lint/style/noNonNullAssertion: + nearBlocksApiKey: process.env.nearBlocksApiKey!, + nearBlocksUrl: conf.nearBlocksUrl, + networkId: conf.networkId, + storage, + privateKey: nearWallet.secretKey, + signer: new NearInMemorySigner(ks), + serverLinkHandler: serverLinkHandler, + validatorAddress, + staking, + }); +} + export async function configTonHandler( conf: ITonChainConfig, storage: BridgeStorage, @@ -482,6 +538,21 @@ export async function configDeps( ) : undefined; + const nearc = config.bridgeChains.find((e) => e.chainType === "near"); + + const near = nearc + ? await configNearHandler( + nearc, + storage, + em.fork(), + secrets.nearWallet, + serverLinkHandler, + logger.getSubLogger({ name: "NEAR" }), + staking, + secrets.evmWallet.address, + ) + : undefined; + return { storage, em, @@ -548,6 +619,7 @@ export async function configDeps( mx, ton, icp, + near, ].filter((e) => e !== undefined) as THandler[], }; } diff --git a/src/environment.ts b/src/environment.ts index f152745a..38df84f2 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -6,6 +6,7 @@ config(); export const Env = z.object({ TON_API_KEY: z.string().optional(), + NEARBLOCKS_API_KEY: z.string().optional(), SERVER_PORT: z.string().optional(), SERVER_LINK: z.string().optional(), NETWORK: z.union([z.literal("testnet"), z.literal("mainnet"), z.undefined()]), diff --git a/src/handler/near/index.ts b/src/handler/near/index.ts index b06481a7..5e5cc414 100644 --- a/src/handler/near/index.ts +++ b/src/handler/near/index.ts @@ -37,8 +37,8 @@ export async function nearHandler({ }: NearHandlerParams): Promise { const bc = new Contract(near.connection, bridge, { changeMethods: [], - viewMethods: [], - useLocalViewExecution: true, + viewMethods: ["validator", "validator_count"], + useLocalViewExecution: false, }); const nearBlocksApi = axios.create({ baseURL: nearBlocksUrl, diff --git a/src/handler/near/utils/addSelfAsValidator.ts b/src/handler/near/utils/addSelfAsValidator.ts index ad183fb4..3bc2dfbd 100644 --- a/src/handler/near/utils/addSelfAsValidator.ts +++ b/src/handler/near/utils/addSelfAsValidator.ts @@ -34,7 +34,7 @@ export default async function addSelfAsValidator( } try { async function getStakingSignatureCount() { - return Number(await bridge.get_validator_count()); + return Number(await bridge.validator_count()); } let validatorsCount = await getStakingSignatureCount(); let signatureCount = Number( diff --git a/src/handler/near/utils/generateWallet.ts b/src/handler/near/utils/generateWallet.ts index 2c52f014..a339750e 100644 --- a/src/handler/near/utils/generateWallet.ts +++ b/src/handler/near/utils/generateWallet.ts @@ -1,13 +1,14 @@ import { KeyPair } from "near-api-js"; import type { KeyPairEd25519 } from "near-api-js/lib/utils"; -export default function nearGw() { +export default function generateWallet() { const kp = KeyPair.fromRandom("ed25519") as KeyPairEd25519; const publicKey = kp.getPublicKey().data; const privateKey = kp.secretKey; return Promise.resolve({ - publicKey: Buffer.from(publicKey).toString("hex"), - privateKey: Buffer.from(privateKey).toString("hex"), + publicKey: publicKey.toString(), + secretKey: privateKey, + accountId: Buffer.from(kp.getPublicKey().data).toString("hex"), }); } diff --git a/src/handler/near/utils/listenForLockEvents.ts b/src/handler/near/utils/listenForLockEvents.ts index 02bb38b7..10c54022 100644 --- a/src/handler/near/utils/listenForLockEvents.ts +++ b/src/handler/near/utils/listenForLockEvents.ts @@ -19,15 +19,20 @@ export default async function listenForLockEvents( em: EntityManager, logger: LogInstance, ) { - const lastBlock = lastBlock_; + let lastBlock = lastBlock_; while (true) try { const response = await nearBlocksApi.get<{ - cursor: number; + cursor?: number; txns: unknown[]; }>(`/account/${bridge}/txns`); + if (!response.data.cursor) { + logger.trace(`0 Txns Since: ${lastBlock}. Awaiting 10s`); + await setTimeout(10000); + continue; + } - if (response.data.cursor <= lastBlock) { + if (response.data.cursor < lastBlock) { logger.trace(`0 Txns Since: ${lastBlock}. Awaiting 10s`); await setTimeout(10000); continue; @@ -35,38 +40,42 @@ export default async function listenForLockEvents( const { data } = await nearBlocksApi.get<{ txns: Transaction[]; - }>(`/account/${bridge}/txn?method=lock_nft&cursor=${lastBlock}`); + }>(`/account/${bridge}/txns?cursor=${lastBlock}`); const newCursor = data.txns[0].id; for (const tx of data.txns) { const txn = await near.connection.provider.txStatusReceipts( tx.transaction_hash, bridge, - "FINAL", + "NONE", ); + //@ts-ignore + if (!("SuccessValue" in txn.status)) { + continue; + } const log = txn.receipts_outcome .flatMap((e) => e.outcome.logs) .filter((e) => e.includes("locked"))[0]; if (!log) continue; const parsed = JSON.parse(log).data; - cb( + await cb( await builder.nftLocked( parsed.token_id, parsed.destination_chain, - parsed.destionation_address, + parsed.destination_user_address, parsed.source_nft_contract_address, parsed.token_amount, "singular", parsed.source_chain, tx.transaction_hash, "NEAR", - "", + parsed.metadata_uri, undefined, ), ); } - + lastBlock = Number(newCursor); await em.upsert(Block, { chain: CHAIN_IDENT, contractAddress: bridge, @@ -74,7 +83,7 @@ export default async function listenForLockEvents( }); await em.flush(); } catch (e) { - logger.error(`${e} while listening for events. Sleeping for 10 seconds`); + logger.error("Error while listening for events", e); await setTimeout(10000); } } diff --git a/src/handler/near/utils/nftData.ts b/src/handler/near/utils/nftData.ts index 321e1329..0bbe0de8 100644 --- a/src/handler/near/utils/nftData.ts +++ b/src/handler/near/utils/nftData.ts @@ -21,7 +21,7 @@ export default async function nftData( symbol: collection_metadata.symbol, metadata: nft_metadata.metadata.media || nft_metadata.metadata.extra, royalty: BigInt( - Object.values(nft_metadata.metadata.royalty).reduce( + Object.values(nft_metadata.metadata.royalty || { a: 0 }).reduce( //@ts-ignore ik it works (e: number, c: number) => c + e, ) as number, diff --git a/src/handler/near/utils/selfIsValidator.ts b/src/handler/near/utils/selfIsValidator.ts index 8f535a2e..0482e862 100644 --- a/src/handler/near/utils/selfIsValidator.ts +++ b/src/handler/near/utils/selfIsValidator.ts @@ -4,7 +4,12 @@ export default async function selfIsValidator( bridge: Contract & Record, publicKeyInHex: string, ) { - const validator = await bridge.validator({ public_key: publicKeyInHex }); - if (!validator) return false; - return true; + try { + const validator = await bridge.validator({ public_key: publicKeyInHex }); + if (!validator) return false; + return true; + } catch (e) { + console.log(e); + return false; + } } diff --git a/src/index.ts b/src/index.ts index b630b1bc..2bbbb5ac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,14 @@ async function main() { stylePrettyLogs: true, }); await configureValidator(logger); + + let config: IBridgeConfig = prodBridgeConfig; + + const network = process.env.NETWORK; + if (network === "testnet") { + logger.info("Starting up testnet"); + config = testnetBridgeConfig; + } await syncWallets(logger); const secrets: IGeneratedWallets = JSON.parse( @@ -27,14 +35,6 @@ async function main() { process.exit(0); } - let config: IBridgeConfig = prodBridgeConfig; - - const network = process.env.NETWORK; - if (network === "testnet") { - logger.info("Starting up testnet"); - config = testnetBridgeConfig; - } - const deps = await configDeps(config, secrets, logger); if (process.env.SERVER_PORT) { const server = await configureRouter(deps.em.fork()); diff --git a/src/types/index.ts b/src/types/index.ts index f3f89e06..77f1b3fb 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -33,6 +33,12 @@ type ITonWallet = { secretKey: string; }; +type INearWallet = { + publicKey: string; + secretKey: string; + accountId: string; +}; + type IEvmWallet = { address: string; privateKey: string; @@ -68,6 +74,7 @@ type IGeneratedWallets = { secretWallet: ISecretWallet; tezosWallet: ITezosWallet; icpWallet: IICPWallet; + nearWallet: INearWallet; }; type IConfigAndWallets = { @@ -110,6 +117,13 @@ type ITonChainConfig = { rpcURL: string; } & IChainConfig; +type INearChainConfig = { + chainType: "near"; + rpcURL: string; + networkId: "testnet" | "mainnet"; + nearBlocksUrl: string; +} & IChainConfig; + type ITezosChainConfig = { chainType: "tezos"; restApiURL: string; @@ -151,7 +165,8 @@ type TChain = | ITezosChainConfig | IHederaChainConfig | ICosmWasmChainConfig - | IICPChainConfig; + | IICPChainConfig + | INearChainConfig; type IBridgeConfig = { bridgeChains: TChain[]; @@ -184,5 +199,7 @@ export type { ITezosWallet, ITonChainConfig, ITonWallet, + INearChainConfig, + INearWallet, TChain, }; diff --git a/src/utils.ts b/src/utils.ts index c67b2076..a418e862 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,6 +4,7 @@ import path from "node:path"; import { generateWallet as evmGw } from "./handler/evm/utils"; import { generateWallet as icpGw } from "./handler/icp/utils"; import { generateWallet as mxGw } from "./handler/multiversx/utils"; +import { generateWallet as nearGw } from "./handler/near/utils"; import { generateWallet as secretGw } from "./handler/secrets/utils"; import { generateWallet as tzGw } from "./handler/tezos/utils"; import { generateWallet as tonGw } from "./handler/ton/utils"; @@ -35,6 +36,7 @@ export async function syncWallets(logger: LogInstance) { multiversXWallet: await mxGw(), tonWallet: await tonGw(), icpWallet: await icpGw(), + nearWallet: await nearGw(), }; return writeFile(secretsPath, JSON.stringify(wallets)); } @@ -58,6 +60,9 @@ export async function syncWallets(logger: LogInstance) { } else if (!("icpWallet" in secrets)) { logger.warn("Generating new wallet for ICP"); secrets.icpWallet = await icpGw(); + } else if (!("nearWallet" in secrets)) { + logger.error("No wallet for near found, please add it to secrets.json"); + return; } return writeFile(secretsPath, JSON.stringify(secrets)); }