From ac3bf14d17f09415a63cfcb2cfb7f7618976a88c Mon Sep 17 00:00:00 2001 From: Matt Reetz Date: Thu, 18 Jul 2024 12:33:49 -0500 Subject: [PATCH] Add hotspot config to chain --- .../onboarding/src/HotspotOnboardingUtil.ts | 99 ++++++++--------- .../onboarding/src/MobileHotspotOnboarding.ts | 56 +++++----- packages/onboarding/src/OnboardingClient.ts | 100 +++++++++--------- packages/onboarding/src/SolanaOnboarding.ts | 17 +-- .../src/__tests__/OnboardingClient.spec.ts | 22 +++- packages/onboarding/src/types.ts | 33 +++++- 6 files changed, 178 insertions(+), 149 deletions(-) diff --git a/packages/onboarding/src/HotspotOnboardingUtil.ts b/packages/onboarding/src/HotspotOnboardingUtil.ts index db3ccb16..1b18bf08 100644 --- a/packages/onboarding/src/HotspotOnboardingUtil.ts +++ b/packages/onboarding/src/HotspotOnboardingUtil.ts @@ -1,4 +1,4 @@ -import OnboardingClient from './OnboardingClient' +import OnboardingClient, { OnboardingResponse } from './OnboardingClient' import BN from 'bn.js' import { DC_MINT, @@ -19,20 +19,10 @@ import { rewardableEntityConfigKey, } from '@helium/helium-entity-manager-sdk' import * as Currency from '@helium/currency-utils' -import { HNT_AS_BONES, DcProgram, DeviceType, HemProgram, Maker, NetworkType } from './types' +import { HNT_AS_BONES, DcProgram, HemProgram, Maker, NetworkType } from './types' const lowerFirst = (str: string) => str.charAt(0).toLowerCase() + str.slice(1) -const deviceTypeToNetworkType = (deviceType: DeviceType): NetworkType => { - // deviceType is null for IOT hotspots - // cbrs devices are both IOT and MOBILE hotspots, but the location, gain, and elevation are all stored on the IOT side - if (deviceType === null || deviceType === 'Cbrs') { - return 'IOT' - } - - return 'MOBILE' -} - const hotspotInfoToDetails = (value: { asset: PublicKey bumpSeed: number @@ -55,18 +45,12 @@ const hotspotInfoToDetails = (value: { export const getHotspotNetworkDetails = async ({ address, hemProgram, - ...opts -}: - | { - address: string - networkType: NetworkType - hemProgram: HemProgram - } - | { - address: string - deviceType: DeviceType - hemProgram: HemProgram - }): Promise< + networkType, +}: { + address: string + networkType: NetworkType + hemProgram: HemProgram +}): Promise< | { elevation?: number gain?: number @@ -77,16 +61,6 @@ export const getHotspotNetworkDetails = async ({ } | undefined > => { - const types = { networkType: undefined, deviceType: undefined, ...opts } - let networkType = types.networkType - if (!networkType && types.deviceType !== undefined) { - networkType = deviceTypeToNetworkType(types.deviceType) - } - - if (!networkType) { - throw new Error('Could not determine network type') - } - const mint = networkType === 'IOT' ? IOT_MINT : MOBILE_MINT const subDao = subDaoKey(mint)[0] @@ -208,9 +182,10 @@ const burnHNTForDataCredits = async ({ return txn } -export const getAssertData = async ({ - deviceType, +export const getUpdateMetaData = async ({ + networkType, gateway, + antenna, azimuth, hemProgram, onboardingClient, @@ -228,9 +203,10 @@ export const getAssertData = async ({ owner: PublicKey decimalGain?: number elevation?: number + antenna?: number azimuth?: number nextLocation: string - deviceType: DeviceType + networkType: NetworkType onboardingClient: OnboardingClient dcProgram: DcProgram cluster: Cluster @@ -246,22 +222,41 @@ export const getAssertData = async ({ makerKey = heliumAddressToSolPublicKey(onboardingRecord.data?.maker.address) } - const networkType = deviceTypeToNetworkType(deviceType) const solanaAddress = owner.toBase58() - let gain: number | undefined = undefined - if (decimalGain) { - gain = Math.round((decimalGain || 0) * 10.0) - } - const solResponse = await onboardingClient.updateMetadata({ - type: networkType, - solanaAddress, - hotspotAddress: gateway, - location: nextLocation, - elevation, - gain, - azimuth, - }) + let solResponse: OnboardingResponse<{ + solanaTransactions: number[][] + }> + + if (networkType === 'IOT') { + let gain: number | undefined = undefined + if (decimalGain) { + gain = Math.round((decimalGain || 0) * 10.0) + } + + solResponse = await onboardingClient.updateIotMetadata({ + solanaAddress, + hotspotAddress: gateway, + location: nextLocation, + elevation, + gain, + }) + } else { + solResponse = await onboardingClient.updateMobileMetadata({ + solanaAddress, + hotspotAddress: gateway, + location: nextLocation, + deploymentInfo: { + wifiInfoV0: { + elevation: elevation || 0, + azimuth: azimuth || 0, + antenna: antenna || 0, + mechanicalDownTilt: 0, + electricalDownTilt: 0, + }, + }, + }) + } const errFound = !solResponse.success ? solResponse : undefined @@ -274,7 +269,7 @@ export const getAssertData = async ({ ) const networkDetails = await getHotspotNetworkDetails({ - deviceType, + networkType, address: gateway, hemProgram, }) diff --git a/packages/onboarding/src/MobileHotspotOnboarding.ts b/packages/onboarding/src/MobileHotspotOnboarding.ts index 8e7316fa..6ec0320d 100644 --- a/packages/onboarding/src/MobileHotspotOnboarding.ts +++ b/packages/onboarding/src/MobileHotspotOnboarding.ts @@ -9,6 +9,7 @@ import { DeviceType, HeightType, ManufacturedDeviceType, + NetworkType, OutdoorManufacturedDeviceType, ProgressKeys, ProgressStep, @@ -198,47 +199,29 @@ export default class MobileHotspotOnboarding { return this.getWifiClient().getApiVersion() } - getWifiAssertData = async ({ + getAssertData = async ({ gateway, elevation, location, - deviceType, + networkType, azimuth, + antenna, }: { azimuth?: number gateway: string elevation?: number location: string - deviceType: 'WifiIndoor' | 'WifiOutdoor' - }) => { - return this._solanaOnboarding.getAssertData({ - azimuth, - gateway, - elevation, - location, - deviceType, - }) - } - - getCbrsAssertData = async ({ - gateway, - decimalGain, - elevation, - location, - }: { - gateway: string + antenna?: number + networkType: NetworkType decimalGain?: number - elevation?: number - location: string }) => { - // We update assert data for the IOT network only - // For the MOBILE network we don't set location. They must update their radio location at https://hotspots.hellohelium.com - return this._solanaOnboarding.getAssertData({ + return this._solanaOnboarding.getUpdateMetaData({ + antenna, + azimuth, gateway, - decimalGain, elevation, location, - deviceType: 'Cbrs', + networkType, }) } @@ -247,21 +230,29 @@ export default class MobileHotspotOnboarding { azimuth, location, elevation, + antenna, }: { hotspotAddress: string location?: string elevation?: number + antenna?: number azimuth?: number }) => { this.writeLog('Getting MOBILE onboard txns') this.setProgressToStep('fetch_mobile') - const onboardTxns = await this._onboardingClient.onboard({ + const onboardTxns = await this._onboardingClient.onboardMobile({ hotspotAddress, location, - type: 'MOBILE', - elevation, - azimuth, + deploymentInfo: { + wifiInfoV0: { + elevation: elevation || 0, + azimuth: azimuth || 0, + antenna: antenna || 0, + mechanicalDownTilt: 0, + electricalDownTilt: 0, + }, + }, }) if (!onboardTxns.data?.solanaTransactions?.length) { @@ -455,9 +446,11 @@ export default class MobileHotspotOnboarding { azimuth, location, elevation, + antenna, ...opts }: AddToOnboardingServerOpts & { addGatewayTxn: string + antenna?: number location?: string azimuth?: number | undefined elevation?: number | undefined @@ -520,6 +513,7 @@ export default class MobileHotspotOnboarding { hotspotAddress, azimuth, elevation, + antenna, }) return txns diff --git a/packages/onboarding/src/OnboardingClient.ts b/packages/onboarding/src/OnboardingClient.ts index 59a27c95..e3e2afba 100644 --- a/packages/onboarding/src/OnboardingClient.ts +++ b/packages/onboarding/src/OnboardingClient.ts @@ -1,12 +1,12 @@ import axios, { AxiosInstance, AxiosResponse, Method } from 'axios' import axiosRetry from 'axios-retry' import qs from 'qs' -import { OnboardingRecord, Maker, Metadata, NetworkType, DeviceType } from './types' +import { OnboardingRecord, Maker, MobileMetadata, DeviceType, IotMetadata } from './types' import MockAdapter from 'axios-mock-adapter' import updateTxn from './updateTxn' import BN from 'bn.js' -type Response = { +export type OnboardingResponse = { code: number data: T | null success: boolean @@ -77,7 +77,7 @@ export default class OnboardingClient { private async execute(method: Method, path: string, params?: Object) { try { - const response: AxiosResponse> = await this.axios({ + const response: AxiosResponse> = await this.axios({ method, url: path, data: params, @@ -91,7 +91,7 @@ export default class OnboardingClient { return response.data } catch (err) { if (axios.isAxiosError(err)) { - return err.response?.data as Response + return err.response?.data as OnboardingResponse } throw err } @@ -134,48 +134,43 @@ export default class OnboardingClient { return this.post<{ solanaTransactions: number[][] }>('transactions/create-hotspot', opts) } - async onboard( - opts: { - hotspotAddress: string - type: NetworkType - payer?: string - } & Partial, - ) { + async onboardIot(opts: { hotspotAddress: string; payer?: string } & Partial) { let location: string | undefined = undefined if (opts.location) { location = new BN(opts.location, 'hex').toString() } - return this.post<{ solanaTransactions: number[][] }>( - `transactions/${opts.type.toLowerCase()}/onboard`, - { - entityKey: opts.hotspotAddress, - location, - elevation: opts.elevation, - gain: opts.gain, - payer: opts.payer, - azimuth: opts.azimuth, - }, - ) + return this.post<{ solanaTransactions: number[][] }>('transactions/iot/onboard', { + entityKey: opts.hotspotAddress, + location, + payer: opts.payer, + gain: opts.gain, + elevation: opts.elevation, + }) } - async onboardIot(opts: { hotspotAddress: string; payer?: string } & Partial) { - return this.onboard({ ...opts, type: 'IOT' }) - } + async onboardMobile(opts: { hotspotAddress: string; payer?: string } & Partial) { + let location: string | undefined = undefined + if (opts.location) { + location = new BN(opts.location, 'hex').toString() + } - async onboardMobile(opts: { hotspotAddress: string; payer?: string } & Partial) { - return this.onboard({ ...opts, type: 'MOBILE' }) + return this.post<{ solanaTransactions: number[][] }>('transactions/mobile/onboard', { + entityKey: opts.hotspotAddress, + location, + deploymentInfo: opts.deploymentInfo, + payer: opts.payer, + }) } - async updateMetadata( - opts: Partial & { - type: NetworkType + async updateIotMetadata( + opts: Partial & { hotspotAddress: string solanaAddress: string payer?: string }, ) { - const { solanaAddress, elevation, gain, hotspotAddress, type, payer, azimuth } = opts + const { solanaAddress, hotspotAddress, payer } = opts let location: string | undefined = undefined if (opts.location) { @@ -183,38 +178,41 @@ export default class OnboardingClient { } const body = { - azimuth, entityKey: hotspotAddress, location, - elevation, - gain, - wallet: solanaAddress, payer, + wallet: solanaAddress, + gain: opts.gain, + elevation: opts.elevation, } - return this.post<{ solanaTransactions: number[][] }>( - `transactions/${type.toLowerCase()}/update-metadata`, - body, - ) - } - - async updateIotMetadata( - opts: Partial & { - hotspotAddress: string - solanaAddress: string - payer?: string - }, - ) { - return this.updateMetadata({ ...opts, type: 'IOT' }) + return this.post<{ solanaTransactions: number[][] }>('transactions/iot/update-metadata', body) } async updateMobileMetadata( - opts: Partial & { + opts: Partial & { hotspotAddress: string solanaAddress: string payer?: string }, ) { - return this.updateMetadata({ ...opts, type: 'MOBILE' }) + const { solanaAddress, deploymentInfo, hotspotAddress, payer } = opts + + let location: string | undefined = undefined + if (opts.location) { + location = new BN(opts.location, 'hex').toString() + } + + const body = { + deploymentInfo, + entityKey: hotspotAddress, + location, + payer, + wallet: solanaAddress, + } + return this.post<{ solanaTransactions: number[][] }>( + 'transactions/mobile/update-metadata', + body, + ) } async addToOnboardingServer({ diff --git a/packages/onboarding/src/SolanaOnboarding.ts b/packages/onboarding/src/SolanaOnboarding.ts index 35855e36..22f1b59b 100644 --- a/packages/onboarding/src/SolanaOnboarding.ts +++ b/packages/onboarding/src/SolanaOnboarding.ts @@ -10,7 +10,7 @@ import { } from '@helium/spl-utils' import { init as initDc } from '@helium/data-credits-sdk' import { init as initHem, keyToAssetKey } from '@helium/helium-entity-manager-sdk' -import { AssertData, DcProgram, DeviceType, HemProgram, SubmitStatus } from './types' +import { AssertData, DcProgram, HemProgram, NetworkType, SubmitStatus } from './types' import { daoKey } from '@helium/helium-sub-daos-sdk' import * as AssertMock from './__mocks__/AssertMock' import { @@ -78,20 +78,22 @@ export default class SolanaOnboarding { return this.dcProgram! } - getAssertData = async ({ + getUpdateMetaData = async ({ gateway, decimalGain, azimuth, + antenna, elevation, location, - deviceType, + networkType, }: { gateway: string azimuth?: number decimalGain?: number + antenna?: number elevation?: number location: string - deviceType: DeviceType + networkType: NetworkType }): Promise => { if (this.shouldMock) { return AssertMock.getAssertData() @@ -100,7 +102,7 @@ export default class SolanaOnboarding { const dcProgram = await this.getDcProgram() const hemProgram = await this.getHemProgram() - return HotspotOnboardingUtil.getAssertData({ + return HotspotOnboardingUtil.getUpdateMetaData({ onboardingClient: this.onboardingClient, connection: this.connection, owner: this.wallet, @@ -108,10 +110,11 @@ export default class SolanaOnboarding { hemProgram, decimalGain, gateway, + antenna, azimuth, elevation, nextLocation: location, - deviceType, + networkType, cluster: this.cluster, }) } @@ -180,7 +183,7 @@ export default class SolanaOnboarding { address, }: { address: string - networkType: 'MOBILE' | 'IOT' + networkType: NetworkType }) => { if (this.shouldMock) { return { diff --git a/packages/onboarding/src/__tests__/OnboardingClient.spec.ts b/packages/onboarding/src/__tests__/OnboardingClient.spec.ts index 4f7bd9bb..51c18a9a 100644 --- a/packages/onboarding/src/__tests__/OnboardingClient.spec.ts +++ b/packages/onboarding/src/__tests__/OnboardingClient.spec.ts @@ -192,9 +192,16 @@ describe('Onboard', () => { const client = new OnboardingClient(DEWI_ONBOARDING_API_BASE_URL_V3) const onboardingTxn = await client.onboardMobile({ + deploymentInfo: { + wifiInfoV0: { + elevation: 1, + antenna: 1, + azimuth: 1, + electricalDownTilt: 1, + mechanicalDownTilt: 1, + }, + }, location: '8a2a1072b59ffff', - gain: 1, - elevation: 1, hotspotAddress: 'asdf1234', }) expect(onboardingTxn.data).toBeDefined() @@ -240,9 +247,16 @@ describe('Onboard', () => { const onboardingTxn = await client.updateMobileMetadata({ solanaAddress: 'asfd', location: '8a2a1072b59ffff', - elevation: 1, - gain: 1, hotspotAddress: 'asdf', + deploymentInfo: { + wifiInfoV0: { + elevation: 1, + antenna: 1, + azimuth: 1, + electricalDownTilt: 1, + mechanicalDownTilt: 1, + }, + }, }) expect(onboardingTxn.data).toBeDefined() expect(onboardingTxn.data?.solanaTransactions[0][0]).toBe(0) diff --git a/packages/onboarding/src/types.ts b/packages/onboarding/src/types.ts index e255bf78..b0bf55aa 100644 --- a/packages/onboarding/src/types.ts +++ b/packages/onboarding/src/types.ts @@ -1,6 +1,6 @@ import { init as initDc } from '@helium/data-credits-sdk' import { init as initHem } from '@helium/helium-entity-manager-sdk' -import { getAssertData } from './HotspotOnboardingUtil' +import { getUpdateMetaData } from './HotspotOnboardingUtil' import BN from 'bn.js' export const DEWI_ONBOARDING_API_BASE_URL = 'https://onboarding.dewi.org/api' @@ -34,11 +34,36 @@ export type Maker = { updatedAt: string } -export type Metadata = { +export type MobileDeploymentInfoV0 = { + cbrsInfoV0?: { radioInfos: RadioInfoV0[] } + wifiInfoV0?: WifiInfoV0 +} + +export type WifiInfoV0 = { + antenna: number + elevation: number + azimuth: number + mechanicalDownTilt: number + electricalDownTilt: number +} + +export type RadioInfoV0 = { + radioId: string + elevation: number +} + +export type MobileMetadata = { + location: string + deploymentInfo: MobileDeploymentInfoV0 + hotspotAddress: string +} + +export type IotMetadata = { location: string + hotspotAddress: string elevation: number gain: number - azimuth: number + payer: string } export const IndoorManufacturedDeviceTypes = ['HeliumMobileIndoor'] as const @@ -62,7 +87,7 @@ export const TXN_FEE_IN_LAMPORTS = new BN(5000) export type HemProgram = Awaited> export type DcProgram = Awaited> -export type AssertData = Awaited> +export type AssertData = Awaited> export type SubmitStatus = { totalProgress: number