@evefrontier/dapp-kit 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +2 -2
- package/docs/api/assets/hierarchy.js +1 -1
- package/docs/api/assets/navigation.js +1 -1
- package/docs/api/assets/search.js +1 -1
- package/docs/api/classes/WalletNoAccountSelectedError.html +3 -3
- package/docs/api/classes/WalletNotConnectedError.html +3 -3
- package/docs/api/classes/WalletSponsoredTransactionNotSupportedError.html +3 -3
- package/docs/api/enums/ActionTypes.html +2 -2
- package/docs/api/enums/Assemblies.html +3 -2
- package/docs/api/enums/QueryParams.html +2 -2
- package/docs/api/enums/Severity.html +2 -2
- package/docs/api/enums/SponsoredTransactionActions.html +3 -3
- package/docs/api/enums/State.html +2 -2
- package/docs/api/enums/SupportedWallets.html +2 -2
- package/docs/api/enums/TYPEIDS.html +3 -3
- package/docs/api/functions/EveFrontierProvider.html +2 -2
- package/docs/api/functions/NotificationProvider.html +2 -2
- package/docs/api/functions/SmartObjectProvider.html +4 -4
- package/docs/api/functions/VaultProvider.html +1 -1
- package/docs/api/functions/abbreviateAddress.html +2 -2
- package/docs/api/functions/assertAssemblyType.html +2 -2
- package/docs/api/functions/clickToCopy.html +1 -1
- package/docs/api/functions/executeGraphQLQuery.html +2 -2
- package/docs/api/functions/findOwnerByAddress.html +2 -2
- package/docs/api/functions/formatDuration.html +2 -2
- package/docs/api/functions/formatM3.html +2 -2
- package/docs/api/functions/getAdjustedBurnRate.html +6 -0
- package/docs/api/functions/getAssemblyType.html +2 -2
- package/docs/api/functions/getAssemblyTypeApiString.html +2 -2
- package/docs/api/functions/getAssemblyWithOwner.html +3 -3
- package/docs/api/functions/getCharacterAndOwnedObjects.html +3 -0
- package/docs/api/functions/getCharacterOwnedObjects.html +5 -0
- package/docs/api/functions/getCharacterOwnedObjectsJson.html +5 -0
- package/docs/api/functions/getCharacterOwnerCapType.html +2 -2
- package/docs/api/functions/getCharacterPlayerProfileType.html +3 -0
- package/docs/api/functions/getCommonItems.html +1 -1
- package/docs/api/functions/getDappUrl.html +2 -2
- package/docs/api/functions/getDatahubGameInfo.html +2 -2
- package/docs/api/functions/getEnergyConfig.html +4 -0
- package/docs/api/functions/getEnergyConfigType.html +3 -0
- package/docs/api/functions/getEnergyUsageForType.html +5 -0
- package/docs/api/functions/getEnv.html +1 -1
- package/docs/api/functions/getEveWorldPackageId.html +2 -2
- package/docs/api/functions/getFuelEfficiencyConfig.html +4 -0
- package/docs/api/functions/getFuelEfficiencyConfigType.html +3 -0
- package/docs/api/functions/getFuelEfficiencyForType.html +5 -0
- package/docs/api/functions/getObjectByAddress.html +2 -2
- package/docs/api/functions/getObjectId.html +2 -2
- package/docs/api/functions/getObjectOwnerAndOwnedObjectsByType.html +2 -2
- package/docs/api/functions/getObjectOwnerAndOwnedObjectsWithJson.html +7 -0
- package/docs/api/functions/getObjectRegistryType.html +2 -2
- package/docs/api/functions/getObjectWithDynamicFields.html +2 -2
- package/docs/api/functions/getObjectWithJson.html +2 -2
- package/docs/api/functions/getObjectsByType.html +2 -2
- package/docs/api/functions/getOwnedObjectsByPackage.html +2 -2
- package/docs/api/functions/getOwnedObjectsByType.html +2 -2
- package/docs/api/functions/getRegistryAddress.html +2 -2
- package/docs/api/functions/getSingletonObjectByType.html +2 -2
- package/docs/api/functions/getSponsoredTransactionFeature.html +2 -2
- package/docs/api/functions/getSuiGraphqlEndpoint.html +2 -2
- package/docs/api/functions/getTxUrl.html +2 -2
- package/docs/api/functions/getVolumeM3.html +2 -2
- package/docs/api/functions/getWalletCharacters.html +3 -0
- package/docs/api/functions/hasSponsoredTransactionFeature.html +2 -2
- package/docs/api/functions/isOwner.html +3 -3
- package/docs/api/functions/parseCharacterFromJson.html +5 -0
- package/docs/api/functions/parseErrorFromMessage.html +1 -1
- package/docs/api/functions/parseStatus.html +2 -2
- package/docs/api/functions/parseURL.html +1 -1
- package/docs/api/functions/removeTrailingZeros.html +1 -1
- package/docs/api/functions/transformToAssembly.html +2 -2
- package/docs/api/functions/transformToCharacter.html +2 -2
- package/docs/api/functions/useConnection.html +2 -2
- package/docs/api/functions/useNotification.html +2 -2
- package/docs/api/functions/useSmartObject.html +4 -9
- package/docs/api/functions/useSponsoredTransaction.html +5 -5
- package/docs/api/functions/walletSupportsSponsoredTransaction.html +2 -2
- package/docs/api/hierarchy.html +1 -1
- package/docs/api/index.html +3 -5
- package/docs/api/interfaces/AddressOwner.html +2 -2
- package/docs/api/interfaces/AddressOwnerWithJson.html +2 -2
- package/docs/api/interfaces/AddressWithObjects.html +3 -0
- package/docs/api/interfaces/AdjustedBurnRate.html +8 -0
- package/docs/api/interfaces/AsMoveObjectRef.html +3 -0
- package/docs/api/interfaces/AssemblyProperties.html +6 -3
- package/docs/api/interfaces/CharacterAndOwnedObjectsNode.html +2 -0
- package/docs/api/interfaces/CharacterInfo.html +3 -3
- package/docs/api/interfaces/CharacterOwnerNode.html +3 -0
- package/docs/api/interfaces/ConfigExtractDynamicFieldNode.html +3 -0
- package/docs/api/interfaces/ContentsBcs.html +2 -0
- package/docs/api/interfaces/ContentsJsonAndBcs.html +3 -0
- package/docs/api/interfaces/ContentsJsonOnly.html +3 -0
- package/docs/api/interfaces/ContentsTypeAndBcs.html +3 -0
- package/docs/api/interfaces/ContentsTypeJsonAndBcs.html +4 -0
- package/docs/api/interfaces/DatahubGameInfo.html +14 -16
- package/docs/api/interfaces/DetailedAssemblyResponse.html +6 -3
- package/docs/api/interfaces/DetailedSmartCharacterResponse.html +2 -2
- package/docs/api/interfaces/DynamicFieldNode.html +2 -2
- package/docs/api/interfaces/EphemeralInventory.html +2 -2
- package/docs/api/interfaces/EveFrontierSponsoredTransactionFeature.html +3 -3
- package/docs/api/interfaces/ExtractAsMoveObjectNode.html +4 -0
- package/docs/api/interfaces/GateModule.html +2 -5
- package/docs/api/interfaces/GetCharacterAndOwnedObjectsResponse.html +2 -0
- package/docs/api/interfaces/GetObjectAndCharacterOwnerResponse.html +2 -0
- package/docs/api/interfaces/GetObjectByAddressResponse.html +2 -2
- package/docs/api/interfaces/GetObjectOwnerAndOwnedObjectsResponse.html +2 -2
- package/docs/api/interfaces/GetObjectOwnerAndOwnedObjectsWithJsonResponse.html +2 -2
- package/docs/api/interfaces/GetObjectResponse.html +2 -2
- package/docs/api/interfaces/GetObjectWithJsonResponse.html +2 -2
- package/docs/api/interfaces/GetObjectsByTypeResponse.html +2 -2
- package/docs/api/interfaces/GetOwnedObjectsByPackageResponse.html +2 -2
- package/docs/api/interfaces/GetOwnedObjectsByTypeResponse.html +2 -2
- package/docs/api/interfaces/GetSingletonConfigObjectByTypeResponse.html +2 -0
- package/docs/api/interfaces/GetSingletonObjectByTypeResponse.html +2 -2
- package/docs/api/interfaces/GetWalletCharactersResponse.html +2 -0
- package/docs/api/interfaces/GraphQLResponse.html +2 -2
- package/docs/api/interfaces/InventoryItem.html +2 -2
- package/docs/api/interfaces/ManufacturingModule.html +1 -2
- package/docs/api/interfaces/MoveObjectContents.html +2 -2
- package/docs/api/interfaces/MoveObjectData.html +2 -2
- package/docs/api/interfaces/NetworkNodeModule.html +2 -2
- package/docs/api/interfaces/NotificationContextType.html +2 -2
- package/docs/api/interfaces/NotificationState.html +2 -2
- package/docs/api/interfaces/Notify.html +2 -2
- package/docs/api/interfaces/ObjectNodes.html +2 -0
- package/docs/api/interfaces/ObjectWithContentsNode.html +2 -2
- package/docs/api/interfaces/OwnedObjectAddressNode.html +2 -2
- package/docs/api/interfaces/OwnedObjectFullNode.html +2 -2
- package/docs/api/interfaces/OwnedObjectNode.html +2 -2
- package/docs/api/interfaces/OwnedObjectNodeWithJson.html +2 -2
- package/docs/api/interfaces/OwnerCapData.html +3 -3
- package/docs/api/interfaces/PageInfo.html +2 -2
- package/docs/api/interfaces/PreviousTransaction.html +2 -0
- package/docs/api/interfaces/RawCharacterData.html +3 -3
- package/docs/api/interfaces/RawSuiObjectData.html +10 -6
- package/docs/api/interfaces/RefineryModule.html +1 -2
- package/docs/api/interfaces/SmartAssemblyResponse.html +6 -2
- package/docs/api/interfaces/SmartObjectContextType.html +4 -6
- package/docs/api/interfaces/SponsoredTransactionInput.html +6 -7
- package/docs/api/interfaces/SponsoredTransactionOutput.html +5 -5
- package/docs/api/interfaces/StorageModule.html +2 -3
- package/docs/api/interfaces/SuiObjectResponse.html +2 -2
- package/docs/api/interfaces/TransformOptions.html +8 -4
- package/docs/api/interfaces/TurretModule.html +1 -2
- package/docs/api/interfaces/TypeRepr.html +2 -0
- package/docs/api/interfaces/TypeReprWithLayout.html +3 -0
- package/docs/api/interfaces/VaultContextType.html +3 -3
- package/docs/api/modules.html +1 -1
- package/docs/api/types/AssemblyType.html +1 -1
- package/docs/api/types/ContentsTypeAndJson.html +2 -0
- package/docs/api/types/ErrorType.html +2 -2
- package/docs/api/types/MoveObjectRefWithJson.html +2 -0
- package/docs/api/types/SendSponsoredTransactionFn.html +1 -1
- package/docs/api/types/SponsoredTransactionArgs.html +2 -2
- package/docs/api/types/SponsoredTransactionAssemblyType.html +2 -2
- package/docs/api/types/SponsoredTransactionMethod.html +2 -2
- package/docs/api/types/UseSponsoredTransactionArgs.html +2 -2
- package/docs/api/types/UseSponsoredTransactionError.html +2 -2
- package/docs/api/types/UseSponsoredTransactionMutationOptions.html +2 -2
- package/docs/api/variables/ASSEMBLY_TYPE_API_STRING.html +2 -2
- package/docs/api/variables/ERRORS.html +1 -1
- package/docs/api/variables/ERROR_MESSAGES.html +1 -1
- package/docs/api/variables/EVEFRONTIER_SPONSORED_TRANSACTION.html +2 -2
- package/docs/api/variables/EXCLUDED_TYPEIDS.html +1 -1
- package/docs/api/variables/GET_OBJECTS_BY_TYPE.html +2 -2
- package/docs/api/variables/GET_OBJECT_BY_ADDRESS.html +2 -2
- package/docs/api/variables/GET_OBJECT_OWNER_AND_OWNED_OBJECTS_BY_TYPE.html +2 -2
- package/docs/api/variables/GET_OBJECT_OWNER_AND_OWNED_OBJECTS_WITH_JSON.html +2 -2
- package/docs/api/variables/GET_OBJECT_WITH_DYNAMIC_FIELDS.html +2 -2
- package/docs/api/variables/GET_OBJECT_WITH_JSON.html +2 -2
- package/docs/api/variables/GET_OWNED_OBJECTS_BY_PACKAGE.html +2 -2
- package/docs/api/variables/GET_OWNED_OBJECTS_BY_TYPE.html +2 -2
- package/docs/api/variables/GET_SINGLETON_CONFIG_OBJECT_BY_TYPE.html +2 -0
- package/docs/api/variables/GET_SINGLETON_OBJECT_BY_TYPE.html +2 -2
- package/docs/api/variables/GET_WALLET_CHARACTERS.html +3 -0
- package/docs/api/variables/NotificationContext.html +1 -1
- package/docs/api/variables/ONE_M3.html +2 -2
- package/docs/api/variables/POLLING_INTERVAL.html +2 -2
- package/docs/api/variables/STORAGE_KEYS.html +2 -2
- package/docs/api/variables/SmartObjectContext.html +1 -1
- package/docs/api/variables/VaultContext.html +1 -1
- package/docs/api/variables/dAppKit.html +2 -2
- package/graphql/client.ts +119 -24
- package/graphql/index.ts +5 -39
- package/graphql/queries.ts +166 -1
- package/graphql/types.ts +192 -167
- package/hooks/index.ts +1 -0
- package/hooks/useSmartObject.ts +2 -12
- package/hooks/useSponsoredTransaction.ts +60 -9
- package/index.ts +7 -71
- package/package.json +1 -1
- package/providers/SmartObjectProvider.tsx +84 -57
- package/types/contexts.ts +1 -3
- package/types/sponsoredTransaction.ts +1 -0
- package/types/types.ts +52 -10
- package/types/worldApiReturnTypes.ts +13 -37
- package/utils/__tests__/burnRate.test.ts +121 -0
- package/utils/__tests__/character.test.ts +112 -0
- package/utils/__tests__/characterOwnedObjects.test.ts +222 -0
- package/utils/__tests__/config.test.ts +424 -0
- package/utils/__tests__/mapping.test.ts +2 -2
- package/utils/burnRate.ts +46 -0
- package/utils/character.ts +46 -0
- package/utils/characterOwnedObjects.ts +52 -0
- package/utils/config.ts +151 -0
- package/utils/constants.ts +39 -5
- package/utils/datahub.ts +3 -41
- package/utils/errors.ts +1 -1
- package/utils/index.ts +10 -1
- package/utils/mapping.ts +5 -2
- package/utils/transforms.ts +34 -31
- package/vite-env.d.ts +2 -2
- package/wallet/features.ts +7 -2
- package/docs/api/interfaces/GameTypeResponse.html +0 -13
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const TEST_EVE_WORLD_PACKAGE_ID =
|
|
4
|
+
"0x2ff3e06b96eb830bdcffbc6cae9b8fe43f005c3b94cef05d9ec23057df16f107";
|
|
5
|
+
|
|
6
|
+
vi.mock("../../graphql/client", () => ({
|
|
7
|
+
getSingletonConfigObjectByType: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
import { getSingletonConfigObjectByType } from "../../graphql/client";
|
|
11
|
+
import {
|
|
12
|
+
getEnergyConfig,
|
|
13
|
+
getEnergyUsageForType,
|
|
14
|
+
getFuelEfficiencyConfig,
|
|
15
|
+
getFuelEfficiencyForType,
|
|
16
|
+
resetConfigCachesForTesting,
|
|
17
|
+
} from "../config";
|
|
18
|
+
import { getEnergyConfigType, getFuelEfficiencyConfigType } from "../constants";
|
|
19
|
+
|
|
20
|
+
/** Builds the response shape that config reads: data.objects.nodes[0].asMoveObject.contents.extract...dynamicFields.nodes */
|
|
21
|
+
function mockConfigResponse(
|
|
22
|
+
nodes: Array<{ key: { json: string }; value: { json: string } }>,
|
|
23
|
+
) {
|
|
24
|
+
return {
|
|
25
|
+
data: {
|
|
26
|
+
objects: {
|
|
27
|
+
nodes: [
|
|
28
|
+
{
|
|
29
|
+
address: "0xconfig",
|
|
30
|
+
asMoveObject: {
|
|
31
|
+
contents: {
|
|
32
|
+
extract: {
|
|
33
|
+
extract: {
|
|
34
|
+
asAddress: {
|
|
35
|
+
addressAt: {
|
|
36
|
+
dynamicFields: {
|
|
37
|
+
pageInfo: { hasNextPage: false, endCursor: null },
|
|
38
|
+
nodes,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe("config utilities", () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
vi.clearAllMocks();
|
|
56
|
+
vi.stubEnv("VITE_EVE_WORLD_PACKAGE_ID", TEST_EVE_WORLD_PACKAGE_ID);
|
|
57
|
+
resetConfigCachesForTesting();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
vi.unstubAllEnvs();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Parsing (via getEnergyConfig with mocked response)
|
|
66
|
+
// ============================================================================
|
|
67
|
+
describe("parsing", () => {
|
|
68
|
+
it("returns empty object when nodes is undefined (e.g. missing path)", async () => {
|
|
69
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue({
|
|
70
|
+
data: {
|
|
71
|
+
objects: {
|
|
72
|
+
nodes: [
|
|
73
|
+
{
|
|
74
|
+
address: "0xcfg",
|
|
75
|
+
asMoveObject: {
|
|
76
|
+
contents: {
|
|
77
|
+
extract: {
|
|
78
|
+
extract: {
|
|
79
|
+
asAddress: {
|
|
80
|
+
addressAt: {
|
|
81
|
+
dynamicFields: { nodes: undefined },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
} as unknown as Awaited<
|
|
93
|
+
ReturnType<typeof getSingletonConfigObjectByType>
|
|
94
|
+
>);
|
|
95
|
+
|
|
96
|
+
const result = await getEnergyConfig();
|
|
97
|
+
|
|
98
|
+
expect(result).toEqual({});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("returns empty object when nodes is empty array", async () => {
|
|
102
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
103
|
+
mockConfigResponse([]),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const result = await getEnergyConfig();
|
|
107
|
+
|
|
108
|
+
expect(result).toEqual({});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("parses valid key/value nodes into typeId -> value map", async () => {
|
|
112
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
113
|
+
mockConfigResponse([
|
|
114
|
+
{ key: { json: "77917" }, value: { json: "500" } },
|
|
115
|
+
{ key: { json: "88067" }, value: { json: "100" } },
|
|
116
|
+
{ key: { json: "92279" }, value: { json: "10" } },
|
|
117
|
+
]),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const result = await getEnergyConfig();
|
|
121
|
+
|
|
122
|
+
expect(result).toEqual({
|
|
123
|
+
77917: 500,
|
|
124
|
+
88067: 100,
|
|
125
|
+
92279: 10,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("skips entries with invalid (non-numeric) key and continues parsing", async () => {
|
|
130
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
131
|
+
mockConfigResponse([
|
|
132
|
+
{ key: { json: "77917" }, value: { json: "500" } },
|
|
133
|
+
{ key: { json: "not-a-number" }, value: { json: "99" } },
|
|
134
|
+
{ key: { json: "88067" }, value: { json: "100" } },
|
|
135
|
+
]),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const result = await getEnergyConfig();
|
|
139
|
+
|
|
140
|
+
expect(result).toEqual({
|
|
141
|
+
77917: 500,
|
|
142
|
+
88067: 100,
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("uses 0 for entries with invalid (non-numeric) value", async () => {
|
|
147
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
148
|
+
mockConfigResponse([
|
|
149
|
+
{ key: { json: "77917" }, value: { json: "nope" } },
|
|
150
|
+
{ key: { json: "88067" }, value: { json: "100" } },
|
|
151
|
+
]),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const result = await getEnergyConfig();
|
|
155
|
+
|
|
156
|
+
expect(result).toEqual({
|
|
157
|
+
77917: 0,
|
|
158
|
+
88067: 100,
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("does not silently zero out known type IDs when data is present", async () => {
|
|
163
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
164
|
+
mockConfigResponse([
|
|
165
|
+
{ key: { json: "77917" }, value: { json: "500" } },
|
|
166
|
+
{ key: { json: "88071" }, value: { json: "300" } },
|
|
167
|
+
]),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const config = await getEnergyConfig();
|
|
171
|
+
const usage77917 = await getEnergyUsageForType(77917);
|
|
172
|
+
const usage88071 = await getEnergyUsageForType(88071);
|
|
173
|
+
const usageMissing = await getEnergyUsageForType(99999);
|
|
174
|
+
|
|
175
|
+
expect(config[77917]).toBe(500);
|
|
176
|
+
expect(config[88071]).toBe(300);
|
|
177
|
+
expect(usage77917).toBe(500);
|
|
178
|
+
expect(usage88071).toBe(300);
|
|
179
|
+
expect(usageMissing).toBe(0);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// Caching
|
|
185
|
+
// ============================================================================
|
|
186
|
+
describe("caching", () => {
|
|
187
|
+
it("calls GraphQL once and returns same reference on second getEnergyConfig()", async () => {
|
|
188
|
+
const nodes = [{ key: { json: "77917" }, value: { json: "500" } }];
|
|
189
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
190
|
+
mockConfigResponse(nodes),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const first = await getEnergyConfig();
|
|
194
|
+
const second = await getEnergyConfig();
|
|
195
|
+
|
|
196
|
+
expect(getSingletonConfigObjectByType).toHaveBeenCalledTimes(1);
|
|
197
|
+
expect(getSingletonConfigObjectByType).toHaveBeenCalledWith(
|
|
198
|
+
getEnergyConfigType(),
|
|
199
|
+
"assembly_energy",
|
|
200
|
+
);
|
|
201
|
+
expect(first).toBe(second);
|
|
202
|
+
expect(second).toEqual({ 77917: 500 });
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("calls GraphQL once for getFuelEfficiencyConfig and returns same reference on second call", async () => {
|
|
206
|
+
const nodes = [{ key: { json: "12345" }, value: { json: "75" } }];
|
|
207
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue({
|
|
208
|
+
data: {
|
|
209
|
+
objects: {
|
|
210
|
+
nodes: [
|
|
211
|
+
{
|
|
212
|
+
address: "0xfuel",
|
|
213
|
+
asMoveObject: {
|
|
214
|
+
contents: {
|
|
215
|
+
extract: {
|
|
216
|
+
extract: {
|
|
217
|
+
asAddress: {
|
|
218
|
+
addressAt: {
|
|
219
|
+
dynamicFields: {
|
|
220
|
+
pageInfo: { hasNextPage: false },
|
|
221
|
+
nodes,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
} as Awaited<ReturnType<typeof getSingletonConfigObjectByType>>);
|
|
234
|
+
|
|
235
|
+
const first = await getFuelEfficiencyConfig();
|
|
236
|
+
const second = await getFuelEfficiencyConfig();
|
|
237
|
+
|
|
238
|
+
expect(getSingletonConfigObjectByType).toHaveBeenCalledWith(
|
|
239
|
+
getFuelEfficiencyConfigType(),
|
|
240
|
+
"fuel_efficiency",
|
|
241
|
+
);
|
|
242
|
+
expect(first).toBe(second);
|
|
243
|
+
expect(second).toEqual({ 12345: 75 });
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// ============================================================================
|
|
248
|
+
// Concurrent-call deduplication
|
|
249
|
+
// ============================================================================
|
|
250
|
+
describe("concurrent-call deduplication", () => {
|
|
251
|
+
it("performs a single fetch when getEnergyConfig() is invoked concurrently", async () => {
|
|
252
|
+
type MockResponse = Awaited<
|
|
253
|
+
ReturnType<typeof getSingletonConfigObjectByType>
|
|
254
|
+
>;
|
|
255
|
+
let resolveFetch: (v: MockResponse) => void;
|
|
256
|
+
const fetchPromise = new Promise<MockResponse>((r) => {
|
|
257
|
+
resolveFetch = r;
|
|
258
|
+
});
|
|
259
|
+
vi.mocked(getSingletonConfigObjectByType).mockReturnValue(fetchPromise);
|
|
260
|
+
|
|
261
|
+
const concurrentCalls = [
|
|
262
|
+
getEnergyConfig(),
|
|
263
|
+
getEnergyConfig(),
|
|
264
|
+
getEnergyConfig(),
|
|
265
|
+
getEnergyConfig(),
|
|
266
|
+
getEnergyConfig(),
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
resolveFetch!(
|
|
270
|
+
mockConfigResponse([
|
|
271
|
+
{ key: { json: "77917" }, value: { json: "500" } },
|
|
272
|
+
]),
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const results = await Promise.all(concurrentCalls);
|
|
276
|
+
|
|
277
|
+
expect(getSingletonConfigObjectByType).toHaveBeenCalledTimes(1);
|
|
278
|
+
expect(results.every((r) => r === results[0])).toBe(true);
|
|
279
|
+
expect(results[0]).toEqual({ 77917: 500 });
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("performs a single fetch when getFuelEfficiencyConfig() is invoked concurrently", async () => {
|
|
283
|
+
type MockResponse = Awaited<
|
|
284
|
+
ReturnType<typeof getSingletonConfigObjectByType>
|
|
285
|
+
>;
|
|
286
|
+
let resolveFetch: (v: MockResponse) => void;
|
|
287
|
+
const fetchPromise = new Promise<MockResponse>((r) => {
|
|
288
|
+
resolveFetch = r;
|
|
289
|
+
});
|
|
290
|
+
vi.mocked(getSingletonConfigObjectByType).mockReturnValue(fetchPromise);
|
|
291
|
+
|
|
292
|
+
const concurrentCalls = [
|
|
293
|
+
getFuelEfficiencyConfig(),
|
|
294
|
+
getFuelEfficiencyConfig(),
|
|
295
|
+
getFuelEfficiencyConfig(),
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
resolveFetch!(
|
|
299
|
+
mockConfigResponse([
|
|
300
|
+
{ key: { json: "88071" }, value: { json: "200" } },
|
|
301
|
+
]),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const results = await Promise.all(concurrentCalls);
|
|
305
|
+
|
|
306
|
+
expect(getSingletonConfigObjectByType).toHaveBeenCalledTimes(1);
|
|
307
|
+
expect(results.every((r) => r === results[0])).toBe(true);
|
|
308
|
+
expect(results[0]).toEqual({ 88071: 200 });
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// ============================================================================
|
|
313
|
+
// Pagination / first-page behavior
|
|
314
|
+
// ============================================================================
|
|
315
|
+
describe("pagination behavior", () => {
|
|
316
|
+
it("parses first page of nodes and does not zero out returned entries", async () => {
|
|
317
|
+
// Simulates response where dynamicFields has one page of nodes (hasNextPage may be true on backend)
|
|
318
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
319
|
+
mockConfigResponse([
|
|
320
|
+
{ key: { json: "77917" }, value: { json: "500" } },
|
|
321
|
+
{ key: { json: "88067" }, value: { json: "100" } },
|
|
322
|
+
]),
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const result = await getEnergyConfig();
|
|
326
|
+
|
|
327
|
+
expect(result).toEqual({ 77917: 500, 88067: 100 });
|
|
328
|
+
expect(result[77917]).toBe(500);
|
|
329
|
+
expect(result[88067]).toBe(100);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("returns empty object when first object has no nodes (missing path) without throwing", async () => {
|
|
333
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue({
|
|
334
|
+
data: {
|
|
335
|
+
objects: {
|
|
336
|
+
nodes: [
|
|
337
|
+
{
|
|
338
|
+
address: "0xcfg",
|
|
339
|
+
asMoveObject: {
|
|
340
|
+
contents: {
|
|
341
|
+
extract: undefined,
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
} as unknown as Awaited<
|
|
349
|
+
ReturnType<typeof getSingletonConfigObjectByType>
|
|
350
|
+
>);
|
|
351
|
+
|
|
352
|
+
const result = await getEnergyConfig();
|
|
353
|
+
|
|
354
|
+
expect(result).toEqual({});
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// ============================================================================
|
|
359
|
+
// getEnergyUsageForType / getFuelEfficiencyForType
|
|
360
|
+
// ============================================================================
|
|
361
|
+
describe("getEnergyUsageForType / getFuelEfficiencyForType", () => {
|
|
362
|
+
it("getEnergyUsageForType returns 0 for typeId not in config", async () => {
|
|
363
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
364
|
+
mockConfigResponse([
|
|
365
|
+
{ key: { json: "77917" }, value: { json: "500" } },
|
|
366
|
+
]),
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
const missing = await getEnergyUsageForType(99999);
|
|
370
|
+
|
|
371
|
+
expect(missing).toBe(0);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("getFuelEfficiencyForType returns cached value for typeId in config", async () => {
|
|
375
|
+
vi.mocked(getSingletonConfigObjectByType)
|
|
376
|
+
.mockResolvedValueOnce(
|
|
377
|
+
mockConfigResponse([
|
|
378
|
+
{ key: { json: "77917" }, value: { json: "500" } },
|
|
379
|
+
]),
|
|
380
|
+
)
|
|
381
|
+
.mockResolvedValueOnce({
|
|
382
|
+
data: {
|
|
383
|
+
objects: {
|
|
384
|
+
nodes: [
|
|
385
|
+
{
|
|
386
|
+
address: "0xfuel",
|
|
387
|
+
asMoveObject: {
|
|
388
|
+
contents: {
|
|
389
|
+
extract: {
|
|
390
|
+
extract: {
|
|
391
|
+
asAddress: {
|
|
392
|
+
addressAt: {
|
|
393
|
+
dynamicFields: {
|
|
394
|
+
pageInfo: {
|
|
395
|
+
hasNextPage: false,
|
|
396
|
+
endCursor: null,
|
|
397
|
+
},
|
|
398
|
+
nodes: [
|
|
399
|
+
{
|
|
400
|
+
key: { json: "77917" },
|
|
401
|
+
value: { json: "75" },
|
|
402
|
+
},
|
|
403
|
+
],
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const energy = await getEnergyUsageForType(77917);
|
|
418
|
+
const fuelEff = await getFuelEfficiencyForType(77917);
|
|
419
|
+
|
|
420
|
+
expect(energy).toBe(500);
|
|
421
|
+
expect(fuelEff).toBe(75);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
});
|
|
@@ -95,9 +95,9 @@ describe("mapping utilities", () => {
|
|
|
95
95
|
).toBe(Assemblies.NetworkNode);
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
it("returns
|
|
98
|
+
it("returns Assembly as default for unknown type", () => {
|
|
99
99
|
expect(getAssemblyType("unknown::type::Unknown")).toBe(
|
|
100
|
-
Assemblies.
|
|
100
|
+
Assemblies.Assembly,
|
|
101
101
|
);
|
|
102
102
|
});
|
|
103
103
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const MS_PER_HOUR = 3600000;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Result of adjusting raw burn time for efficiency.
|
|
5
|
+
* - burnTimePerUnitMs: efficiency-adjusted milliseconds to burn one unit
|
|
6
|
+
* - unitsPerHour: how many units are burned per hour at this rate
|
|
7
|
+
*/
|
|
8
|
+
export interface AdjustedBurnRate {
|
|
9
|
+
burnTimePerUnitMs: number;
|
|
10
|
+
unitsPerHour: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Takes raw burn time (ms per unit at 100%) and efficiency (0–100 scale),
|
|
15
|
+
* returns efficiency-adjusted burn time per unit and units burned per hour.
|
|
16
|
+
* E.g. raw 3600000 ms, 90% efficient → burn time 3240000 ms, ~1.11 units/hour.
|
|
17
|
+
*
|
|
18
|
+
* @param rawBurnTimeMs - Milliseconds to burn one unit at 100% efficiency. If non-finite or negative, treated as 0 so both returned fields are finite and consistent.
|
|
19
|
+
* @param efficiencyPercent - Efficiency as percentage on a 0–100 scale (e.g. 90 for 90%). Values outside [0, 100] are treated as invalid; when null/undefined, not finite, ≤0, or >100, raw is used as-is for burn time.
|
|
20
|
+
* @returns { burnTimePerUnitMs, unitsPerHour }
|
|
21
|
+
* @category Utilities
|
|
22
|
+
*/
|
|
23
|
+
export function getAdjustedBurnRate(
|
|
24
|
+
rawBurnTimeMs: number,
|
|
25
|
+
efficiencyPercent: number | null | undefined,
|
|
26
|
+
): AdjustedBurnRate {
|
|
27
|
+
const raw =
|
|
28
|
+
Number.isFinite(rawBurnTimeMs) && rawBurnTimeMs >= 0 ? rawBurnTimeMs : 0;
|
|
29
|
+
|
|
30
|
+
const validEfficiency =
|
|
31
|
+
efficiencyPercent != null &&
|
|
32
|
+
Number.isFinite(efficiencyPercent) &&
|
|
33
|
+
efficiencyPercent > 0 &&
|
|
34
|
+
efficiencyPercent <= 100;
|
|
35
|
+
|
|
36
|
+
const burnTimePerUnitMs = validEfficiency
|
|
37
|
+
? raw * (efficiencyPercent / 100)
|
|
38
|
+
: raw;
|
|
39
|
+
|
|
40
|
+
const unitsPerHour =
|
|
41
|
+
burnTimePerUnitMs > 0 && Number.isFinite(burnTimePerUnitMs)
|
|
42
|
+
? MS_PER_HOUR / burnTimePerUnitMs
|
|
43
|
+
: 0;
|
|
44
|
+
|
|
45
|
+
return { burnTimePerUnitMs, unitsPerHour };
|
|
46
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { CharacterInfo, RawCharacterData } from "../graphql/types";
|
|
2
|
+
|
|
3
|
+
function parseOptionalInt(value: unknown): number {
|
|
4
|
+
if (value == null) return 0;
|
|
5
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
6
|
+
if (typeof value === "string") {
|
|
7
|
+
const n = parseInt(value, 10);
|
|
8
|
+
return Number.isNaN(n) ? 0 : n;
|
|
9
|
+
}
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Normalize raw character JSON (from wallet/assembly GQL responses) into CharacterInfo.
|
|
15
|
+
* Accepts varying shapes; returns null when input is not a usable object.
|
|
16
|
+
*
|
|
17
|
+
* @param json - Raw JSON (e.g. from contents.json or extract...contents.json)
|
|
18
|
+
* @returns CharacterInfo or null
|
|
19
|
+
* @category Character Helpers
|
|
20
|
+
*/
|
|
21
|
+
export function parseCharacterFromJson(json: unknown): CharacterInfo | null {
|
|
22
|
+
if (json == null || typeof json !== "object" || Array.isArray(json)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const obj = json as Record<string, unknown>;
|
|
26
|
+
const metadata =
|
|
27
|
+
obj.metadata != null &&
|
|
28
|
+
typeof obj.metadata === "object" &&
|
|
29
|
+
!Array.isArray(obj.metadata)
|
|
30
|
+
? (obj.metadata as Record<string, unknown>)
|
|
31
|
+
: undefined;
|
|
32
|
+
const key =
|
|
33
|
+
obj.key != null && typeof obj.key === "object" && !Array.isArray(obj.key)
|
|
34
|
+
? (obj.key as Record<string, unknown>)
|
|
35
|
+
: undefined;
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
id: typeof obj.id === "string" ? obj.id : "",
|
|
39
|
+
address:
|
|
40
|
+
typeof obj.character_address === "string" ? obj.character_address : "",
|
|
41
|
+
name: typeof metadata?.name === "string" ? metadata.name : "",
|
|
42
|
+
tribeId: parseOptionalInt(obj.tribe_id),
|
|
43
|
+
characterId: parseOptionalInt(key?.item_id),
|
|
44
|
+
_raw: json as RawCharacterData,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { getCharacterAndOwnedObjects } from "../graphql/client";
|
|
2
|
+
import type { GetCharacterAndOwnedObjectsResponse } from "../graphql/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extracts the JSON payloads from the first character's owned objects
|
|
6
|
+
* (from getCharacterAndOwnedObjects response data).
|
|
7
|
+
*
|
|
8
|
+
* @param data - Response data from getCharacterAndOwnedObjects
|
|
9
|
+
* @returns Array of contents.json for each owned object, or undefined if missing
|
|
10
|
+
*
|
|
11
|
+
* @category Character Helpers
|
|
12
|
+
*/
|
|
13
|
+
export function getCharacterOwnedObjectsJson(
|
|
14
|
+
data: GetCharacterAndOwnedObjectsResponse | undefined,
|
|
15
|
+
): Record<string, unknown>[] | undefined {
|
|
16
|
+
const objects =
|
|
17
|
+
data?.address?.objects?.nodes?.[0]?.contents?.extract?.asAddress?.objects;
|
|
18
|
+
if (!objects?.nodes?.length) return undefined;
|
|
19
|
+
return objects.nodes.map(
|
|
20
|
+
(node) =>
|
|
21
|
+
node.contents.extract.asAddress.asObject.asMoveObject.contents.json,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Fetches the character and owned objects for an address, then returns the JSON
|
|
27
|
+
* payloads of the first character's owned objects.
|
|
28
|
+
*
|
|
29
|
+
* @param address - Wallet/signer address to query
|
|
30
|
+
* @returns Promise resolving to an array of contents.json per owned object, or undefined if none
|
|
31
|
+
*
|
|
32
|
+
* @category Character Helpers
|
|
33
|
+
*/
|
|
34
|
+
export async function getCharacterOwnedObjects(
|
|
35
|
+
address: string,
|
|
36
|
+
): Promise<Record<string, unknown>[] | undefined> {
|
|
37
|
+
const response = await getCharacterAndOwnedObjects(address).then(
|
|
38
|
+
(response) => {
|
|
39
|
+
const data = response.data;
|
|
40
|
+
if (!data) {
|
|
41
|
+
console.warn(
|
|
42
|
+
"[Dapp] No data returned from getCharacterAndOwnedObjects",
|
|
43
|
+
);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const ownedObjectsJson = getCharacterOwnedObjectsJson(data);
|
|
47
|
+
return ownedObjectsJson;
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return response;
|
|
52
|
+
}
|