@suigar/mcp 0.1.1 → 0.2.0-beta.0
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 +18 -0
- package/LICENSE +201 -0
- package/README.md +79 -110
- package/dist/app/index.html +181 -0
- package/dist/bin.mjs +4 -8
- package/dist/bin.mjs.map +1 -0
- package/dist/client.d.mts +47 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +116 -0
- package/dist/client.mjs.map +1 -0
- package/dist/dry-run.mjs +163 -0
- package/dist/dry-run.mjs.map +1 -0
- package/dist/format.mjs +24 -0
- package/dist/format.mjs.map +1 -0
- package/dist/index.d.mts +6 -820
- package/dist/index.mjs +5 -2
- package/dist/schemas.d.mts +642 -0
- package/dist/schemas.d.mts.map +1 -0
- package/dist/schemas.mjs +113 -0
- package/dist/schemas.mjs.map +1 -0
- package/dist/server.d.mts +21 -69
- package/dist/server.d.mts.map +1 -0
- package/dist/server.mjs +192 -2
- package/dist/server.mjs.map +1 -0
- package/dist/tools.d.mts +17 -0
- package/dist/tools.d.mts.map +1 -0
- package/dist/tools.mjs +352 -0
- package/dist/tools.mjs.map +1 -0
- package/dist/types.d.mts +137 -0
- package/dist/types.d.mts.map +1 -0
- package/package.json +33 -25
- package/dist/bin.cjs +0 -11
- package/dist/bin.d.cts +0 -1
- package/dist/index.cjs +0 -46
- package/dist/index.d.cts +0 -820
- package/dist/server-BD-123-u.mjs +0 -47003
- package/dist/server-Cmo8UaHE.cjs +0 -47266
- package/dist/server.cjs +0 -4
- package/dist/server.d.cts +0 -73
package/dist/bin.mjs
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { startSuigarMcpServer } from "./server.mjs";
|
|
3
3
|
//#region src/bin.ts
|
|
4
|
-
|
|
5
|
-
await startSuigarMcpServer();
|
|
6
|
-
};
|
|
7
|
-
main().catch((error) => {
|
|
8
|
-
console.error(error);
|
|
9
|
-
process.exitCode = 1;
|
|
10
|
-
});
|
|
4
|
+
await startSuigarMcpServer();
|
|
11
5
|
//#endregion
|
|
12
6
|
export {};
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=bin.mjs.map
|
package/dist/bin.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.mjs","names":[],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n// Copyright (c) Suigar\n// SPDX-License-Identifier: Apache-2.0\nimport { startSuigarMcpServer } from './server.js';\n\nawait startSuigarMcpServer();\n"],"mappings":";;;AAKA,MAAM,qBAAqB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { BuildTransactionResult, BuilderMode, JsonValue, RawDryRunResult, ResolvedMcpConfig, SuigarMcpConfigInput, TransactionSummary } from "./types.mjs";
|
|
2
|
+
import { SuigarClient, SuigarNetwork } from "@suigar/sdk";
|
|
3
|
+
import { Game, PvPCoinflipAction } from "@suigar/sdk/games";
|
|
4
|
+
import { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
5
|
+
import { ClientWithExtensions } from "@mysten/sui/client";
|
|
6
|
+
import { Transaction } from "@mysten/sui/transactions";
|
|
7
|
+
|
|
8
|
+
//#region src/client.d.ts
|
|
9
|
+
declare const DEFAULT_NETWORK: SuigarNetwork;
|
|
10
|
+
declare const normalizeNetwork: (network?: string | undefined) => SuigarNetwork;
|
|
11
|
+
declare const getProviderUrl: (network: SuigarNetwork, providerUrl?: string) => string;
|
|
12
|
+
type SuigarClientBundle = {
|
|
13
|
+
client: ClientWithExtensions<{
|
|
14
|
+
suigar: SuigarClient;
|
|
15
|
+
}, SuiGrpcClient>;
|
|
16
|
+
config: ResolvedMcpConfig;
|
|
17
|
+
resolveSuiNSName(name: string): Promise<string | null>;
|
|
18
|
+
};
|
|
19
|
+
declare const createSuigarClient: (input?: SuigarMcpConfigInput) => SuigarClientBundle;
|
|
20
|
+
declare const resolveDefaultCoinType: (config: ResolvedMcpConfig, coinType?: string) => string;
|
|
21
|
+
declare const resolveOwnerAddress: (owner: string, bundle: SuigarClientBundle) => Promise<string>;
|
|
22
|
+
declare const dryRunTransaction: (transaction: Transaction, client: ReturnType<typeof createSuigarClient>["client"]) => Promise<RawDryRunResult>;
|
|
23
|
+
declare const summarizeTransaction: (transaction: Transaction, context?: {
|
|
24
|
+
game?: Game;
|
|
25
|
+
action?: PvPCoinflipAction;
|
|
26
|
+
coinType?: string;
|
|
27
|
+
stake?: bigint | number;
|
|
28
|
+
stakeDisplay?: string;
|
|
29
|
+
coinDecimals?: number;
|
|
30
|
+
gameInputs?: Record<string, JsonValue>;
|
|
31
|
+
}) => TransactionSummary;
|
|
32
|
+
declare const buildTransactionResult: ({
|
|
33
|
+
mode,
|
|
34
|
+
transaction,
|
|
35
|
+
config,
|
|
36
|
+
client,
|
|
37
|
+
context
|
|
38
|
+
}: {
|
|
39
|
+
mode: Exclude<BuilderMode, "read-only">;
|
|
40
|
+
transaction: Transaction;
|
|
41
|
+
config: ResolvedMcpConfig;
|
|
42
|
+
client: ReturnType<typeof createSuigarClient>["client"];
|
|
43
|
+
context: Parameters<typeof summarizeTransaction>[1];
|
|
44
|
+
}) => Promise<BuildTransactionResult>;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { DEFAULT_NETWORK, SuigarClientBundle, buildTransactionResult, createSuigarClient, dryRunTransaction, getProviderUrl, normalizeNetwork, resolveDefaultCoinType, resolveOwnerAddress, summarizeTransaction };
|
|
47
|
+
//# sourceMappingURL=client.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;;;;;;;cAuCa,eAAA,EAAiB,aAAyB;AAAA,cAE1C,gBAAA,GACZ,OAAA,0BACE,aAQF;AAAA,cAEY,cAAA,GAAkB,OAAA,EAAS,aAAa,EAAE,WAAA;AAAA,KAG3C,kBAAA;EACX,MAAA,EAAQ,oBAAA;IAEN,MAAA,EAAQ,YAAA;EAAA,GAET,aAAA;EAED,MAAA,EAAQ,iBAAA;EACR,gBAAA,CAAiB,IAAA,WAAe,OAAA;AAAA;AAAA,cAGpB,kBAAA,GACZ,KAAA,GAAO,oBAAA,KACL,kBA2BF;AAAA,cAEY,sBAAA,GACZ,MAAA,EAAQ,iBAAiB,EACzB,QAAA;AAAA,cAGY,mBAAA,GACZ,KAAA,UACA,MAAA,EAAQ,kBAAA,KACN,OAAO;AAAA,cA2BG,iBAAA,GACZ,WAAA,EAAa,WAAA,EACb,MAAA,EAAQ,UAAA,QAAkB,kBAAA,gBACxB,OAAA,CAAQ,eAAA;AAAA,cAUE,oBAAA,GACZ,WAAA,EAAa,WAAA,EACb,OAAA;EACC,IAAA,GAAO,IAAA;EACP,MAAA,GAAS,iBAAA;EACT,QAAA;EACA,KAAA;EACA,YAAA;EACA,YAAA;EACA,UAAA,GAAa,MAAA,SAAe,SAAA;AAAA,MAE3B,kBAAA;AAAA,cAwEU,sBAAA;EAAgC,IAAA;EAAA,WAAA;EAAA,MAAA;EAAA,MAAA;EAAA;AAAA;EAO5C,IAAA,EAAM,OAAA,CAAQ,WAAA;EACd,WAAA,EAAa,WAAA;EACb,MAAA,EAAQ,iBAAA;EACR,MAAA,EAAQ,UAAA,QAAkB,kBAAA;EAC1B,OAAA,EAAS,UAAA,QAAkB,oBAAA;AAAA,MACxB,OAAA,CAAQ,sBAAA"}
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { formatBaseUnitAmount } from "./format.mjs";
|
|
2
|
+
import { extractDryRunErrors, summarizeDryRun, toJsonValue } from "./dry-run.mjs";
|
|
3
|
+
import { suigar } from "@suigar/sdk";
|
|
4
|
+
import { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
5
|
+
import { isValidSuiAddress, isValidSuiNSName, normalizeStructTag, normalizeSuiAddress, normalizeSuiNSName } from "@mysten/sui/utils";
|
|
6
|
+
//#region src/client.ts
|
|
7
|
+
const DEFAULT_PROVIDER_URLS = {
|
|
8
|
+
mainnet: "https://fullnode.mainnet.sui.io:443",
|
|
9
|
+
testnet: "https://fullnode.testnet.sui.io:443"
|
|
10
|
+
};
|
|
11
|
+
const DEFAULT_NETWORK = "testnet";
|
|
12
|
+
const normalizeNetwork = (network = DEFAULT_NETWORK) => {
|
|
13
|
+
if (network === "mainnet" || network === "testnet") return network;
|
|
14
|
+
throw new RangeError(`Unsupported network: ${network}. Use "mainnet" or "testnet".`);
|
|
15
|
+
};
|
|
16
|
+
const getProviderUrl = (network, providerUrl) => providerUrl ?? DEFAULT_PROVIDER_URLS[network];
|
|
17
|
+
const createSuigarClient = (input = {}) => {
|
|
18
|
+
const network = normalizeNetwork(input.network);
|
|
19
|
+
const baseClient = new SuiGrpcClient({
|
|
20
|
+
baseUrl: getProviderUrl(network, input.providerUrl),
|
|
21
|
+
network
|
|
22
|
+
});
|
|
23
|
+
const client = baseClient.$extend(suigar({
|
|
24
|
+
config: input.config,
|
|
25
|
+
partner: input.partner
|
|
26
|
+
}));
|
|
27
|
+
return {
|
|
28
|
+
client,
|
|
29
|
+
config: {
|
|
30
|
+
network,
|
|
31
|
+
providerUrl: getProviderUrl(network, input.providerUrl),
|
|
32
|
+
sdk: client.suigar.getConfig()
|
|
33
|
+
},
|
|
34
|
+
resolveSuiNSName: async (name) => (await baseClient.nameService.lookupName({ name })).response.record?.targetAddress ?? null
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
const resolveDefaultCoinType = (config, coinType) => normalizeStructTag(coinType ?? config.sdk.coins.sui.coinType);
|
|
38
|
+
const resolveOwnerAddress = async (owner, bundle) => {
|
|
39
|
+
try {
|
|
40
|
+
const normalizedAddress = normalizeSuiAddress(owner);
|
|
41
|
+
if (isValidSuiAddress(normalizedAddress)) return normalizedAddress;
|
|
42
|
+
} catch {}
|
|
43
|
+
if (!isValidSuiNSName(owner)) throw new TypeError("owner must be a valid Sui address or SuiNS name such as name.sui or sub.name.sui.");
|
|
44
|
+
const normalizedName = normalizeSuiNSName(owner, "dot");
|
|
45
|
+
const resolvedAddress = await bundle.resolveSuiNSName(normalizedName);
|
|
46
|
+
if (!resolvedAddress) throw new Error(`SuiNS name ${normalizedName} did not resolve to an address.`);
|
|
47
|
+
return normalizeSuiAddress(resolvedAddress);
|
|
48
|
+
};
|
|
49
|
+
const dryRunTransaction = async (transaction, client) => client.core.simulateTransaction({
|
|
50
|
+
transaction,
|
|
51
|
+
include: {
|
|
52
|
+
effects: true,
|
|
53
|
+
events: true,
|
|
54
|
+
balanceChanges: true
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
const summarizeTransaction = (transaction, context = {}) => {
|
|
58
|
+
const data = transaction.getData();
|
|
59
|
+
const commands = (data.commands ?? []).map((command) => {
|
|
60
|
+
const kind = String(command.$kind ?? Object.keys(command)[0] ?? "Unknown");
|
|
61
|
+
const moveCall = command.MoveCall;
|
|
62
|
+
const target = moveCall?.package && moveCall?.module && moveCall?.function ? `${moveCall.package}::${moveCall.module}::${moveCall.function}` : void 0;
|
|
63
|
+
return {
|
|
64
|
+
kind,
|
|
65
|
+
...target ? { target } : {},
|
|
66
|
+
...moveCall?.typeArguments ? { typeArguments: moveCall.typeArguments } : {}
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
const objectInputs = (data.inputs ?? []).flatMap((input) => input.$kind === "UnresolvedObject" && input.UnresolvedObject?.objectId ? [input.UnresolvedObject.objectId] : []);
|
|
70
|
+
return {
|
|
71
|
+
sender: data.sender ?? null,
|
|
72
|
+
gasBudget: data.gasData?.budget == null ? null : String(data.gasData.budget),
|
|
73
|
+
gasBudgetDisplay: data.gasData?.budget == null ? null : formatBaseUnitAmount(data.gasData.budget, context.coinDecimals),
|
|
74
|
+
gasPrice: data.gasData?.price == null ? null : String(data.gasData.price),
|
|
75
|
+
commandCount: commands.length,
|
|
76
|
+
commands,
|
|
77
|
+
inputs: data.inputs?.length ?? 0,
|
|
78
|
+
objectInputs,
|
|
79
|
+
...context.game ? { game: context.game } : {},
|
|
80
|
+
...context.action ? { action: context.action } : {},
|
|
81
|
+
...context.coinType ? { coinType: normalizeStructTag(context.coinType) } : {},
|
|
82
|
+
...context.stake == null ? {} : { stake: String(context.stake) },
|
|
83
|
+
...context.stakeDisplay ? { stakeDisplay: context.stakeDisplay } : {},
|
|
84
|
+
...context.coinDecimals == null ? {} : { coinDecimals: context.coinDecimals },
|
|
85
|
+
...context.gameInputs ? { gameInputs: context.gameInputs } : {}
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
const buildTransactionResult = async ({ mode, transaction, config, client, context }) => {
|
|
89
|
+
const summary = summarizeTransaction(transaction, context);
|
|
90
|
+
if (mode === "dry-run") {
|
|
91
|
+
const rawDryRun = await dryRunTransaction(transaction, client);
|
|
92
|
+
const dryRun = toJsonValue(rawDryRun);
|
|
93
|
+
const dryRunSummary = summarizeDryRun(rawDryRun, client, context);
|
|
94
|
+
const errors = extractDryRunErrors(rawDryRun);
|
|
95
|
+
return {
|
|
96
|
+
mode,
|
|
97
|
+
network: config.network,
|
|
98
|
+
config,
|
|
99
|
+
summary,
|
|
100
|
+
dryRun,
|
|
101
|
+
dryRunSummary,
|
|
102
|
+
...errors.length > 0 ? { errors } : {}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
mode,
|
|
107
|
+
network: config.network,
|
|
108
|
+
config,
|
|
109
|
+
summary,
|
|
110
|
+
transactionBytesBase64: await client.suigar.serializeTransactionToBase64(transaction)
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
//#endregion
|
|
114
|
+
export { DEFAULT_NETWORK, buildTransactionResult, createSuigarClient, dryRunTransaction, getProviderUrl, normalizeNetwork, resolveDefaultCoinType, resolveOwnerAddress, summarizeTransaction };
|
|
115
|
+
|
|
116
|
+
//# sourceMappingURL=client.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["// Copyright (c) Suigar\n// SPDX-License-Identifier: Apache-2.0\n\nimport { ClientWithExtensions } from '@mysten/sui/client';\nimport { SuiGrpcClient } from '@mysten/sui/grpc';\nimport type { Transaction } from '@mysten/sui/transactions';\nimport {\n\tisValidSuiAddress,\n\tisValidSuiNSName,\n\tnormalizeStructTag,\n\tnormalizeSuiAddress,\n\tnormalizeSuiNSName,\n} from '@mysten/sui/utils';\nimport { suigar } from '@suigar/sdk';\nimport type { SuigarClient, SuigarNetwork } from '@suigar/sdk';\nimport type { Game, PvPCoinflipAction } from '@suigar/sdk/games';\nimport {\n\textractDryRunErrors,\n\tsummarizeDryRun,\n\ttoJsonValue,\n} from './dry-run.js';\nimport { formatBaseUnitAmount } from './format.js';\nimport type {\n\tBuilderMode,\n\tBuildTransactionResult,\n\tDryRunResult,\n\tJsonValue,\n\tRawDryRunResult,\n\tResolvedMcpConfig,\n\tSuigarConfigOverrides,\n\tSuigarMcpConfigInput,\n\tTransactionSummary,\n} from './types.js';\n\nconst DEFAULT_PROVIDER_URLS = {\n\tmainnet: 'https://fullnode.mainnet.sui.io:443',\n\ttestnet: 'https://fullnode.testnet.sui.io:443',\n} as const satisfies Record<SuigarNetwork, string>;\n\nexport const DEFAULT_NETWORK: SuigarNetwork = 'testnet';\n\nexport const normalizeNetwork = (\n\tnetwork: string | undefined = DEFAULT_NETWORK,\n): SuigarNetwork => {\n\tif (network === 'mainnet' || network === 'testnet') {\n\t\treturn network;\n\t}\n\n\tthrow new RangeError(\n\t\t`Unsupported network: ${network}. Use \"mainnet\" or \"testnet\".`,\n\t);\n};\n\nexport const getProviderUrl = (network: SuigarNetwork, providerUrl?: string) =>\n\tproviderUrl ?? DEFAULT_PROVIDER_URLS[network];\n\nexport type SuigarClientBundle = {\n\tclient: ClientWithExtensions<\n\t\t{\n\t\t\tsuigar: SuigarClient;\n\t\t},\n\t\tSuiGrpcClient\n\t>;\n\tconfig: ResolvedMcpConfig;\n\tresolveSuiNSName(name: string): Promise<string | null>;\n};\n\nexport const createSuigarClient = (\n\tinput: SuigarMcpConfigInput = {},\n): SuigarClientBundle => {\n\tconst network = normalizeNetwork(input.network);\n\tconst baseClient = new SuiGrpcClient({\n\t\tbaseUrl: getProviderUrl(network, input.providerUrl),\n\t\tnetwork,\n\t});\n\tconst client = baseClient.$extend(\n\t\tsuigar({\n\t\t\tconfig: input.config as SuigarConfigOverrides | undefined,\n\t\t\tpartner: input.partner,\n\t\t}),\n\t);\n\n\treturn {\n\t\tclient,\n\t\tconfig: {\n\t\t\tnetwork,\n\t\t\tproviderUrl: getProviderUrl(network, input.providerUrl),\n\t\t\tsdk: client.suigar.getConfig(),\n\t\t} satisfies ResolvedMcpConfig,\n\t\tresolveSuiNSName: async (name) =>\n\t\t\t(\n\t\t\t\tawait baseClient.nameService.lookupName({\n\t\t\t\t\tname,\n\t\t\t\t})\n\t\t\t).response.record?.targetAddress ?? null,\n\t};\n};\n\nexport const resolveDefaultCoinType = (\n\tconfig: ResolvedMcpConfig,\n\tcoinType?: string,\n) => normalizeStructTag(coinType ?? config.sdk.coins.sui.coinType);\n\nexport const resolveOwnerAddress = async (\n\towner: string,\n\tbundle: SuigarClientBundle,\n): Promise<string> => {\n\ttry {\n\t\tconst normalizedAddress = normalizeSuiAddress(owner);\n\t\tif (isValidSuiAddress(normalizedAddress)) {\n\t\t\treturn normalizedAddress;\n\t\t}\n\t} catch {\n\t\t// Fall through to SuiNS validation.\n\t}\n\n\tif (!isValidSuiNSName(owner)) {\n\t\tthrow new TypeError(\n\t\t\t'owner must be a valid Sui address or SuiNS name such as name.sui or sub.name.sui.',\n\t\t);\n\t}\n\n\tconst normalizedName = normalizeSuiNSName(owner, 'dot');\n\tconst resolvedAddress = await bundle.resolveSuiNSName(normalizedName);\n\tif (!resolvedAddress) {\n\t\tthrow new Error(\n\t\t\t`SuiNS name ${normalizedName} did not resolve to an address.`,\n\t\t);\n\t}\n\n\treturn normalizeSuiAddress(resolvedAddress);\n};\n\nexport const dryRunTransaction = async (\n\ttransaction: Transaction,\n\tclient: ReturnType<typeof createSuigarClient>['client'],\n): Promise<RawDryRunResult> =>\n\tclient.core.simulateTransaction({\n\t\ttransaction,\n\t\tinclude: {\n\t\t\teffects: true,\n\t\t\tevents: true,\n\t\t\tbalanceChanges: true,\n\t\t},\n\t});\n\nexport const summarizeTransaction = (\n\ttransaction: Transaction,\n\tcontext: {\n\t\tgame?: Game;\n\t\taction?: PvPCoinflipAction;\n\t\tcoinType?: string;\n\t\tstake?: bigint | number;\n\t\tstakeDisplay?: string;\n\t\tcoinDecimals?: number;\n\t\tgameInputs?: Record<string, JsonValue>;\n\t} = {},\n): TransactionSummary => {\n\tconst data = transaction.getData() as {\n\t\tsender?: string | null;\n\t\tgasData?: {\n\t\t\tbudget?: string | number | bigint | null;\n\t\t\tprice?: string | number | bigint | null;\n\t\t};\n\t\tcommands?: Array<Record<string, unknown> & { $kind?: string }>;\n\t\tinputs?: Array<{\n\t\t\t$kind?: string;\n\t\t\tUnresolvedObject?: { objectId?: string } | null;\n\t\t}>;\n\t};\n\n\tconst commands = (data.commands ?? []).map((command) => {\n\t\tconst kind = String(command.$kind ?? Object.keys(command)[0] ?? 'Unknown');\n\t\tconst moveCall = (\n\t\t\tcommand as {\n\t\t\t\tMoveCall?: {\n\t\t\t\t\tpackage?: string;\n\t\t\t\t\tmodule?: string;\n\t\t\t\t\tfunction?: string;\n\t\t\t\t\ttypeArguments?: string[];\n\t\t\t\t};\n\t\t\t}\n\t\t).MoveCall;\n\t\tconst target =\n\t\t\tmoveCall?.package && moveCall?.module && moveCall?.function\n\t\t\t\t? `${moveCall.package}::${moveCall.module}::${moveCall.function}`\n\t\t\t\t: undefined;\n\t\treturn {\n\t\t\tkind,\n\t\t\t...(target ? { target } : {}),\n\t\t\t...(moveCall?.typeArguments\n\t\t\t\t? { typeArguments: moveCall.typeArguments }\n\t\t\t\t: {}),\n\t\t};\n\t});\n\n\tconst objectInputs = (data.inputs ?? []).flatMap((input) =>\n\t\tinput.$kind === 'UnresolvedObject' && input.UnresolvedObject?.objectId\n\t\t\t? [input.UnresolvedObject.objectId]\n\t\t\t: [],\n\t);\n\n\treturn {\n\t\tsender: data.sender ?? null,\n\t\tgasBudget:\n\t\t\tdata.gasData?.budget == null ? null : String(data.gasData.budget),\n\t\tgasBudgetDisplay:\n\t\t\tdata.gasData?.budget == null\n\t\t\t\t? null\n\t\t\t\t: formatBaseUnitAmount(data.gasData.budget, context.coinDecimals),\n\t\tgasPrice: data.gasData?.price == null ? null : String(data.gasData.price),\n\t\tcommandCount: commands.length,\n\t\tcommands,\n\t\tinputs: data.inputs?.length ?? 0,\n\t\tobjectInputs,\n\t\t...(context.game ? { game: context.game } : {}),\n\t\t...(context.action ? { action: context.action } : {}),\n\t\t...(context.coinType\n\t\t\t? { coinType: normalizeStructTag(context.coinType) }\n\t\t\t: {}),\n\t\t...(context.stake == null ? {} : { stake: String(context.stake) }),\n\t\t...(context.stakeDisplay ? { stakeDisplay: context.stakeDisplay } : {}),\n\t\t...(context.coinDecimals == null\n\t\t\t? {}\n\t\t\t: { coinDecimals: context.coinDecimals }),\n\t\t...(context.gameInputs ? { gameInputs: context.gameInputs } : {}),\n\t};\n};\n\nexport const buildTransactionResult = async ({\n\tmode,\n\ttransaction,\n\tconfig,\n\tclient,\n\tcontext,\n}: {\n\tmode: Exclude<BuilderMode, 'read-only'>;\n\ttransaction: Transaction;\n\tconfig: ResolvedMcpConfig;\n\tclient: ReturnType<typeof createSuigarClient>['client'];\n\tcontext: Parameters<typeof summarizeTransaction>[1];\n}): Promise<BuildTransactionResult> => {\n\tconst summary = summarizeTransaction(transaction, context);\n\tif (mode === 'dry-run') {\n\t\tconst rawDryRun = await dryRunTransaction(transaction, client);\n\t\tconst dryRun = toJsonValue(rawDryRun) as DryRunResult;\n\t\tconst dryRunSummary = summarizeDryRun(rawDryRun, client, context);\n\t\tconst errors = extractDryRunErrors(rawDryRun);\n\t\treturn {\n\t\t\tmode,\n\t\t\tnetwork: config.network,\n\t\t\tconfig,\n\t\t\tsummary,\n\t\t\tdryRun,\n\t\t\tdryRunSummary,\n\t\t\t...(errors.length > 0 ? { errors } : {}),\n\t\t};\n\t}\n\n\treturn {\n\t\tmode,\n\t\tnetwork: config.network,\n\t\tconfig,\n\t\tsummary,\n\t\ttransactionBytesBase64:\n\t\t\tawait client.suigar.serializeTransactionToBase64(transaction),\n\t};\n};\n"],"mappings":"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"}
|
package/dist/dry-run.mjs
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { formatAmount } from "./format.mjs";
|
|
2
|
+
import { GAMES } from "@suigar/sdk/games";
|
|
3
|
+
import { parseGameDetails, parseGameEvent } from "@suigar/sdk/utils";
|
|
4
|
+
//#region src/dry-run.ts
|
|
5
|
+
const amountFieldNames = /* @__PURE__ */ new Set([
|
|
6
|
+
"stake_amount",
|
|
7
|
+
"outcome_amount",
|
|
8
|
+
"payout_amount",
|
|
9
|
+
"amount"
|
|
10
|
+
]);
|
|
11
|
+
const isRecord = (value) => value !== null && typeof value === "object";
|
|
12
|
+
const getDryRunTransaction = (dryRun) => {
|
|
13
|
+
if (!isRecord(dryRun)) return;
|
|
14
|
+
return dryRun.FailedTransaction ?? dryRun.Transaction;
|
|
15
|
+
};
|
|
16
|
+
const toJsonValue = (value) => {
|
|
17
|
+
if (value == null || typeof value === "string" || typeof value === "boolean") return value ?? null;
|
|
18
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : String(value);
|
|
19
|
+
if (typeof value === "bigint") return value.toString();
|
|
20
|
+
if (value instanceof Uint8Array) return Array.from(value);
|
|
21
|
+
if (Array.isArray(value)) return value.map((item) => toJsonValue(item)).filter((item) => item !== void 0);
|
|
22
|
+
if (isRecord(value)) {
|
|
23
|
+
const entries = Object.entries(value).map(([key, item]) => [key, toJsonValue(item)]).filter((entry) => entry[1] !== void 0);
|
|
24
|
+
return Object.fromEntries(entries);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const collectStrings = (value, path) => {
|
|
28
|
+
if (!isRecord(value)) return [];
|
|
29
|
+
return path.flatMap((key) => {
|
|
30
|
+
const next = value[key];
|
|
31
|
+
if (typeof next === "string" && next.trim()) return [next.trim()];
|
|
32
|
+
if (Array.isArray(next)) return next.filter((item) => typeof item === "string");
|
|
33
|
+
if (isRecord(next)) return collectStrings(next, path);
|
|
34
|
+
return [];
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
const extractDryRunErrors = (dryRun) => {
|
|
38
|
+
const source = getDryRunTransaction(dryRun) ?? dryRun;
|
|
39
|
+
const effects = isRecord(source) ? source.effects : void 0;
|
|
40
|
+
const errors = [
|
|
41
|
+
source,
|
|
42
|
+
effects,
|
|
43
|
+
isRecord(effects) ? effects.status : void 0
|
|
44
|
+
].filter(isRecord).flatMap((item) => collectStrings(item, [
|
|
45
|
+
"error",
|
|
46
|
+
"cleverError",
|
|
47
|
+
"message"
|
|
48
|
+
]));
|
|
49
|
+
return [...new Set(errors)];
|
|
50
|
+
};
|
|
51
|
+
const stringField = (record, key) => {
|
|
52
|
+
const value = record[key];
|
|
53
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "bigint" ? String(value) : void 0;
|
|
54
|
+
};
|
|
55
|
+
const gasUsedSummary = (effects, decimals) => {
|
|
56
|
+
const gasUsed = isRecord(effects) ? effects.gasUsed : void 0;
|
|
57
|
+
if (!isRecord(gasUsed)) return {
|
|
58
|
+
computation: null,
|
|
59
|
+
storage: null,
|
|
60
|
+
rebate: null,
|
|
61
|
+
nonRefundableStorageFee: null,
|
|
62
|
+
net: null
|
|
63
|
+
};
|
|
64
|
+
const computation = stringField(gasUsed, "computationCost");
|
|
65
|
+
const storage = stringField(gasUsed, "storageCost");
|
|
66
|
+
const rebate = stringField(gasUsed, "storageRebate");
|
|
67
|
+
const nonRefundableStorageFee = stringField(gasUsed, "nonRefundableStorageFee");
|
|
68
|
+
const net = computation && storage && rebate ? String(-(BigInt(computation) + BigInt(storage) - BigInt(rebate))) : void 0;
|
|
69
|
+
return {
|
|
70
|
+
computation: formatAmount(computation, decimals),
|
|
71
|
+
storage: formatAmount(storage, decimals),
|
|
72
|
+
rebate: formatAmount(rebate, decimals),
|
|
73
|
+
nonRefundableStorageFee: formatAmount(nonRefundableStorageFee, decimals),
|
|
74
|
+
net: formatAmount(net, decimals)
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
const eventFields = (fields, decimals) => {
|
|
78
|
+
const entries = Object.entries(fields).flatMap(([key, value]) => {
|
|
79
|
+
const jsonValue = toJsonValue(value);
|
|
80
|
+
if (jsonValue === void 0) return [];
|
|
81
|
+
const displayValue = amountFieldNames.has(key) ? formatAmount(value, decimals) : null;
|
|
82
|
+
return displayValue ? [[key, jsonValue], [`${key}_display`, displayValue.display]] : [[key, jsonValue]];
|
|
83
|
+
});
|
|
84
|
+
return Object.fromEntries(entries);
|
|
85
|
+
};
|
|
86
|
+
const parseDryRunEvent = (event, eventType) => {
|
|
87
|
+
const module = typeof event.module === "string" ? event.module : eventType.includes("::core::") ? "core" : eventType.includes("::pvp_coinflip::") ? "pvp_coinflip" : "";
|
|
88
|
+
if (!module) return null;
|
|
89
|
+
try {
|
|
90
|
+
const parsedEvent = parseGameEvent({
|
|
91
|
+
...event,
|
|
92
|
+
eventType,
|
|
93
|
+
module
|
|
94
|
+
});
|
|
95
|
+
if (parsedEvent) return parsedEvent;
|
|
96
|
+
} catch {}
|
|
97
|
+
const gameId = /::BetResultEvent<[^>]+::([^:<>,]+)::Game>/u.exec(eventType)?.[1]?.replaceAll("_", "-");
|
|
98
|
+
return gameId && GAMES.includes(gameId) ? {
|
|
99
|
+
gameId,
|
|
100
|
+
eventName: "BetResultEvent"
|
|
101
|
+
} : null;
|
|
102
|
+
};
|
|
103
|
+
const summarizeDryRunEvent = (event, client, decimals) => {
|
|
104
|
+
if (!isRecord(event)) return null;
|
|
105
|
+
const eventType = typeof event.eventType === "string" ? event.eventType : typeof event.type === "string" ? event.type : "unknown";
|
|
106
|
+
const baseSummary = { type: eventType };
|
|
107
|
+
let parsedEvent = null;
|
|
108
|
+
try {
|
|
109
|
+
parsedEvent = parseDryRunEvent(event, eventType);
|
|
110
|
+
if (parsedEvent && event.bcs instanceof Uint8Array) {
|
|
111
|
+
const decoded = client.suigar.bcs.BetResultEvent.parse(event.bcs);
|
|
112
|
+
const details = parseGameDetails(parsedEvent.gameId, decoded.game_details);
|
|
113
|
+
return {
|
|
114
|
+
...baseSummary,
|
|
115
|
+
game: parsedEvent.gameId,
|
|
116
|
+
eventName: parsedEvent.eventName,
|
|
117
|
+
fields: eventFields({
|
|
118
|
+
...decoded,
|
|
119
|
+
game_details: details,
|
|
120
|
+
...details
|
|
121
|
+
}, decimals)
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
} catch {}
|
|
125
|
+
const json = isRecord(event.json) ? event.json : isRecord(event.parsedJson) ? event.parsedJson : null;
|
|
126
|
+
return json ? {
|
|
127
|
+
...baseSummary,
|
|
128
|
+
...parsedEvent ? {
|
|
129
|
+
game: parsedEvent.gameId,
|
|
130
|
+
eventName: parsedEvent.eventName
|
|
131
|
+
} : {},
|
|
132
|
+
fields: eventFields(json, decimals)
|
|
133
|
+
} : null;
|
|
134
|
+
};
|
|
135
|
+
const summarizeDryRun = (dryRun, client, context = {}) => {
|
|
136
|
+
const transaction = getDryRunTransaction(dryRun);
|
|
137
|
+
const transactionRecord = isRecord(transaction) ? transaction : {};
|
|
138
|
+
const effects = transactionRecord.effects;
|
|
139
|
+
const status = isRecord(effects) ? effects.status : void 0;
|
|
140
|
+
const success = isRecord(status) ? status.success === true : false;
|
|
141
|
+
const statusError = isRecord(status) ? status.error : void 0;
|
|
142
|
+
const error = typeof statusError === "string" ? statusError : isRecord(statusError) && typeof statusError.message === "string" ? statusError.message : null;
|
|
143
|
+
const balanceChanges = Array.isArray(transactionRecord.balanceChanges) ? transactionRecord.balanceChanges.filter(isRecord).map((change) => ({
|
|
144
|
+
address: String(change.address ?? ""),
|
|
145
|
+
coinType: String(change.coinType ?? ""),
|
|
146
|
+
amount: formatAmount(change.amount, context.coinDecimals) ?? {
|
|
147
|
+
raw: String(change.amount ?? ""),
|
|
148
|
+
display: ""
|
|
149
|
+
}
|
|
150
|
+
})) : [];
|
|
151
|
+
const events = Array.isArray(transactionRecord.events) ? transactionRecord.events.map((event) => summarizeDryRunEvent(event, client, context.coinDecimals)).filter((event) => event !== null) : [];
|
|
152
|
+
return {
|
|
153
|
+
success,
|
|
154
|
+
error,
|
|
155
|
+
gasUsed: gasUsedSummary(effects, context.coinDecimals),
|
|
156
|
+
balanceChanges,
|
|
157
|
+
events
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
//#endregion
|
|
161
|
+
export { extractDryRunErrors, summarizeDryRun, toJsonValue };
|
|
162
|
+
|
|
163
|
+
//# sourceMappingURL=dry-run.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dry-run.mjs","names":[],"sources":["../src/dry-run.ts"],"sourcesContent":["// Copyright (c) Suigar\n// SPDX-License-Identifier: Apache-2.0\n\nimport { GAMES, type Game } from '@suigar/sdk/games';\nimport { parseGameDetails, parseGameEvent } from '@suigar/sdk/utils';\nimport { formatAmount } from './format.js';\nimport type {\n\tDryRunEventSummary,\n\tDryRunSummary,\n\tJsonValue,\n\tRawDryRunResult,\n} from './types.js';\n\ntype DryRunSummaryClient = {\n\tsuigar: {\n\t\tbcs: {\n\t\t\tBetResultEvent: {\n\t\t\t\tparse(value: Uint8Array): Record<string, unknown> & {\n\t\t\t\t\tgame_details: {\n\t\t\t\t\t\tcontents: Array<{\n\t\t\t\t\t\t\tkey: string;\n\t\t\t\t\t\t\tvalue: number[];\n\t\t\t\t\t\t}>;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t};\n\t};\n};\n\nconst amountFieldNames = new Set([\n\t'stake_amount',\n\t'outcome_amount',\n\t'payout_amount',\n\t'amount',\n]);\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n\tvalue !== null && typeof value === 'object';\n\nconst getDryRunTransaction = (dryRun: RawDryRunResult) => {\n\tif (!isRecord(dryRun)) {\n\t\treturn undefined;\n\t}\n\treturn dryRun.FailedTransaction ?? dryRun.Transaction;\n};\n\nexport const toJsonValue = (value: unknown): JsonValue | undefined => {\n\tif (\n\t\tvalue == null ||\n\t\ttypeof value === 'string' ||\n\t\ttypeof value === 'boolean'\n\t) {\n\t\treturn value ?? null;\n\t}\n\tif (typeof value === 'number') {\n\t\treturn Number.isFinite(value) ? value : String(value);\n\t}\n\tif (typeof value === 'bigint') {\n\t\treturn value.toString();\n\t}\n\tif (value instanceof Uint8Array) {\n\t\treturn Array.from(value);\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn value\n\t\t\t.map((item) => toJsonValue(item))\n\t\t\t.filter((item): item is JsonValue => item !== undefined);\n\t}\n\tif (isRecord(value)) {\n\t\tconst entries = Object.entries(value)\n\t\t\t.map(([key, item]) => [key, toJsonValue(item)] as const)\n\t\t\t.filter(\n\t\t\t\t(entry): entry is readonly [string, JsonValue] =>\n\t\t\t\t\tentry[1] !== undefined,\n\t\t\t);\n\t\treturn Object.fromEntries(entries);\n\t}\n\treturn undefined;\n};\n\nconst collectStrings = (value: unknown, path: string[]): string[] => {\n\tif (!isRecord(value)) {\n\t\treturn [];\n\t}\n\n\treturn path.flatMap((key) => {\n\t\tconst next = value[key];\n\t\tif (typeof next === 'string' && next.trim()) {\n\t\t\treturn [next.trim()];\n\t\t}\n\t\tif (Array.isArray(next)) {\n\t\t\treturn next.filter((item): item is string => typeof item === 'string');\n\t\t}\n\t\tif (isRecord(next)) {\n\t\t\treturn collectStrings(next, path);\n\t\t}\n\t\treturn [];\n\t});\n};\n\nexport const extractDryRunErrors = (dryRun: RawDryRunResult): string[] => {\n\tconst source: unknown = getDryRunTransaction(dryRun) ?? dryRun;\n\tconst effects = isRecord(source) ? source.effects : undefined;\n\tconst status = isRecord(effects) ? effects.status : undefined;\n\tconst errorSources = [source, effects, status].filter(isRecord);\n\n\tconst errors = errorSources.flatMap((item) =>\n\t\tcollectStrings(item, ['error', 'cleverError', 'message']),\n\t);\n\treturn [...new Set(errors)];\n};\n\nconst stringField = (record: Record<string, unknown>, key: string) => {\n\tconst value = record[key];\n\treturn typeof value === 'string' ||\n\t\ttypeof value === 'number' ||\n\t\ttypeof value === 'bigint'\n\t\t? String(value)\n\t\t: undefined;\n};\n\nconst gasUsedSummary = (\n\teffects: unknown,\n\tdecimals?: number,\n): DryRunSummary['gasUsed'] => {\n\tconst gasUsed = isRecord(effects) ? effects.gasUsed : undefined;\n\tif (!isRecord(gasUsed)) {\n\t\treturn {\n\t\t\tcomputation: null,\n\t\t\tstorage: null,\n\t\t\trebate: null,\n\t\t\tnonRefundableStorageFee: null,\n\t\t\tnet: null,\n\t\t};\n\t}\n\n\tconst computation = stringField(gasUsed, 'computationCost');\n\tconst storage = stringField(gasUsed, 'storageCost');\n\tconst rebate = stringField(gasUsed, 'storageRebate');\n\tconst nonRefundableStorageFee = stringField(\n\t\tgasUsed,\n\t\t'nonRefundableStorageFee',\n\t);\n\tconst net =\n\t\tcomputation && storage && rebate\n\t\t\t? String(-(BigInt(computation) + BigInt(storage) - BigInt(rebate)))\n\t\t\t: undefined;\n\n\treturn {\n\t\tcomputation: formatAmount(computation, decimals),\n\t\tstorage: formatAmount(storage, decimals),\n\t\trebate: formatAmount(rebate, decimals),\n\t\tnonRefundableStorageFee: formatAmount(nonRefundableStorageFee, decimals),\n\t\tnet: formatAmount(net, decimals),\n\t};\n};\n\nconst eventFields = (\n\tfields: Record<string, unknown>,\n\tdecimals?: number,\n): Record<string, JsonValue> => {\n\tconst entries = Object.entries(fields).flatMap(([key, value]) => {\n\t\tconst jsonValue = toJsonValue(value);\n\t\tif (jsonValue === undefined) {\n\t\t\treturn [];\n\t\t}\n\t\tconst displayValue = amountFieldNames.has(key)\n\t\t\t? formatAmount(value, decimals)\n\t\t\t: null;\n\t\treturn displayValue\n\t\t\t? [\n\t\t\t\t\t[key, jsonValue] as const,\n\t\t\t\t\t[`${key}_display`, displayValue.display] as const,\n\t\t\t\t]\n\t\t\t: [[key, jsonValue] as const];\n\t});\n\treturn Object.fromEntries(entries);\n};\n\nconst parseDryRunEvent = (\n\tevent: Record<string, unknown>,\n\teventType: string,\n): ReturnType<typeof parseGameEvent> => {\n\tconst module =\n\t\ttypeof event.module === 'string'\n\t\t\t? event.module\n\t\t\t: eventType.includes('::core::')\n\t\t\t\t? 'core'\n\t\t\t\t: eventType.includes('::pvp_coinflip::')\n\t\t\t\t\t? 'pvp_coinflip'\n\t\t\t\t\t: '';\n\n\tif (!module) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst parsedEvent = parseGameEvent({\n\t\t\t...event,\n\t\t\teventType,\n\t\t\tmodule,\n\t\t} as never);\n\t\tif (parsedEvent) {\n\t\t\treturn parsedEvent;\n\t\t}\n\t} catch {\n\t\t// Fall back to string matching below for JSON-only simulated events.\n\t}\n\n\tconst standardBetResult = /::BetResultEvent<[^>]+::([^:<>,]+)::Game>/u.exec(\n\t\teventType,\n\t);\n\tconst gameId = standardBetResult?.[1]?.replaceAll('_', '-');\n\treturn gameId && GAMES.includes(gameId as Game)\n\t\t? {\n\t\t\t\tgameId: gameId as Game,\n\t\t\t\teventName: 'BetResultEvent',\n\t\t\t}\n\t\t: null;\n};\n\nconst summarizeDryRunEvent = (\n\tevent: unknown,\n\tclient: DryRunSummaryClient,\n\tdecimals?: number,\n): DryRunEventSummary | null => {\n\tif (!isRecord(event)) {\n\t\treturn null;\n\t}\n\n\tconst eventType =\n\t\ttypeof event.eventType === 'string'\n\t\t\t? event.eventType\n\t\t\t: typeof event.type === 'string'\n\t\t\t\t? event.type\n\t\t\t\t: 'unknown';\n\tconst baseSummary = {\n\t\ttype: eventType,\n\t};\n\tlet parsedEvent: ReturnType<typeof parseGameEvent> = null;\n\n\ttry {\n\t\tparsedEvent = parseDryRunEvent(event, eventType);\n\t\tif (parsedEvent && event.bcs instanceof Uint8Array) {\n\t\t\tconst decoded = client.suigar.bcs.BetResultEvent.parse(event.bcs);\n\t\t\tconst details = parseGameDetails(\n\t\t\t\tparsedEvent.gameId,\n\t\t\t\tdecoded.game_details,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\t...baseSummary,\n\t\t\t\tgame: parsedEvent.gameId,\n\t\t\t\teventName: parsedEvent.eventName,\n\t\t\t\tfields: eventFields(\n\t\t\t\t\t{\n\t\t\t\t\t\t...decoded,\n\t\t\t\t\t\tgame_details: details,\n\t\t\t\t\t\t...details,\n\t\t\t\t\t},\n\t\t\t\t\tdecimals,\n\t\t\t\t),\n\t\t\t};\n\t\t}\n\t} catch {\n\t\t// Fall back to API-provided JSON below.\n\t}\n\n\tconst json = isRecord(event.json)\n\t\t? event.json\n\t\t: isRecord(event.parsedJson)\n\t\t\t? event.parsedJson\n\t\t\t: null;\n\treturn json\n\t\t? {\n\t\t\t\t...baseSummary,\n\t\t\t\t...(parsedEvent\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tgame: parsedEvent.gameId,\n\t\t\t\t\t\t\teventName: parsedEvent.eventName,\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t\tfields: eventFields(json, decimals),\n\t\t\t}\n\t\t: null;\n};\n\nexport const summarizeDryRun = (\n\tdryRun: RawDryRunResult,\n\tclient: DryRunSummaryClient,\n\tcontext: { coinDecimals?: number } = {},\n): DryRunSummary => {\n\tconst transaction = getDryRunTransaction(dryRun);\n\tconst transactionRecord: Record<string, unknown> = isRecord(transaction)\n\t\t? transaction\n\t\t: {};\n\tconst effects = transactionRecord.effects;\n\tconst status = isRecord(effects) ? effects.status : undefined;\n\tconst success = isRecord(status) ? status.success === true : false;\n\tconst statusError = isRecord(status) ? status.error : undefined;\n\tconst error =\n\t\ttypeof statusError === 'string'\n\t\t\t? statusError\n\t\t\t: isRecord(statusError) && typeof statusError.message === 'string'\n\t\t\t\t? statusError.message\n\t\t\t\t: null;\n\tconst balanceChanges = Array.isArray(transactionRecord.balanceChanges)\n\t\t? transactionRecord.balanceChanges.filter(isRecord).map((change) => ({\n\t\t\t\taddress: String(change.address ?? ''),\n\t\t\t\tcoinType: String(change.coinType ?? ''),\n\t\t\t\tamount:\n\t\t\t\t\tformatAmount(change.amount, context.coinDecimals) ??\n\t\t\t\t\t({ raw: String(change.amount ?? ''), display: '' } as const),\n\t\t\t}))\n\t\t: [];\n\tconst events = Array.isArray(transactionRecord.events)\n\t\t? transactionRecord.events\n\t\t\t\t.map((event) =>\n\t\t\t\t\tsummarizeDryRunEvent(event, client, context.coinDecimals),\n\t\t\t\t)\n\t\t\t\t.filter((event): event is DryRunEventSummary => event !== null)\n\t\t: [];\n\n\treturn {\n\t\tsuccess,\n\t\terror,\n\t\tgasUsed: gasUsedSummary(effects, context.coinDecimals),\n\t\tbalanceChanges,\n\t\tevents,\n\t};\n};\n"],"mappings":"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"}
|
package/dist/format.mjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/format.ts
|
|
2
|
+
const formatBaseUnitAmount = (value, decimals = 9) => {
|
|
3
|
+
const raw = String(value);
|
|
4
|
+
const negative = raw.startsWith("-");
|
|
5
|
+
const digits = negative ? raw.slice(1) : raw;
|
|
6
|
+
if (!/^\d+$/u.test(digits)) return raw;
|
|
7
|
+
if (decimals === 0) return `${negative ? "-" : ""}${digits}`;
|
|
8
|
+
const padded = digits.length <= decimals ? digits.padStart(decimals + 1, "0") : digits;
|
|
9
|
+
const whole = padded.slice(0, -decimals) || "0";
|
|
10
|
+
const fraction = padded.slice(-decimals).replace(/0+$/u, "");
|
|
11
|
+
return `${negative ? "-" : ""}${whole}${fraction ? `.${fraction}` : ""}`;
|
|
12
|
+
};
|
|
13
|
+
const formatAmount = (value, decimals) => {
|
|
14
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint") return null;
|
|
15
|
+
const raw = String(value);
|
|
16
|
+
return {
|
|
17
|
+
raw,
|
|
18
|
+
display: formatBaseUnitAmount(raw, decimals)
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { formatAmount, formatBaseUnitAmount };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=format.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.mjs","names":[],"sources":["../src/format.ts"],"sourcesContent":["// Copyright (c) Suigar\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { FormattedAmount } from './types.js';\n\nexport const formatBaseUnitAmount = (\n\tvalue: string | number | bigint,\n\tdecimals = 9,\n): string => {\n\tconst raw = String(value);\n\tconst negative = raw.startsWith('-');\n\tconst digits = negative ? raw.slice(1) : raw;\n\tif (!/^\\d+$/u.test(digits)) {\n\t\treturn raw;\n\t}\n\tif (decimals === 0) {\n\t\treturn `${negative ? '-' : ''}${digits}`;\n\t}\n\n\tconst padded =\n\t\tdigits.length <= decimals ? digits.padStart(decimals + 1, '0') : digits;\n\tconst whole = padded.slice(0, -decimals) || '0';\n\tconst fraction = padded.slice(-decimals).replace(/0+$/u, '');\n\treturn `${negative ? '-' : ''}${whole}${fraction ? `.${fraction}` : ''}`;\n};\n\nexport const formatAmount = (\n\tvalue: unknown,\n\tdecimals?: number,\n): FormattedAmount | null => {\n\tif (\n\t\ttypeof value !== 'string' &&\n\t\ttypeof value !== 'number' &&\n\t\ttypeof value !== 'bigint'\n\t) {\n\t\treturn null;\n\t}\n\tconst raw = String(value);\n\treturn {\n\t\traw,\n\t\tdisplay: formatBaseUnitAmount(raw, decimals),\n\t};\n};\n"],"mappings":";AAKA,MAAa,wBACZ,OACA,WAAW,MACC;CACZ,MAAM,MAAM,OAAO,KAAK;CACxB,MAAM,WAAW,IAAI,WAAW,GAAG;CACnC,MAAM,SAAS,WAAW,IAAI,MAAM,CAAC,IAAI;CACzC,IAAI,CAAC,SAAS,KAAK,MAAM,GACxB,OAAO;CAER,IAAI,aAAa,GAChB,OAAO,GAAG,WAAW,MAAM,KAAK;CAGjC,MAAM,SACL,OAAO,UAAU,WAAW,OAAO,SAAS,WAAW,GAAG,GAAG,IAAI;CAClE,MAAM,QAAQ,OAAO,MAAM,GAAG,CAAC,QAAQ,KAAK;CAC5C,MAAM,WAAW,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,QAAQ,EAAE;CAC3D,OAAO,GAAG,WAAW,MAAM,KAAK,QAAQ,WAAW,IAAI,aAAa;AACrE;AAEA,MAAa,gBACZ,OACA,aAC4B;CAC5B,IACC,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,UAEjB,OAAO;CAER,MAAM,MAAM,OAAO,KAAK;CACxB,OAAO;EACN;EACA,SAAS,qBAAqB,KAAK,QAAQ;CAC5C;AACD"}
|