@uswap/toolboxes 4.3.6
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/dist/src/cardano/index.cjs +4 -0
- package/dist/src/cardano/index.cjs.map +11 -0
- package/dist/src/cardano/index.js +4 -0
- package/dist/src/cardano/index.js.map +11 -0
- package/dist/src/cosmos/index.cjs +4 -0
- package/dist/src/cosmos/index.cjs.map +20 -0
- package/dist/src/cosmos/index.js +4 -0
- package/dist/src/cosmos/index.js.map +20 -0
- package/dist/src/evm/index.cjs +4 -0
- package/dist/src/evm/index.cjs.map +20 -0
- package/dist/src/evm/index.js +4 -0
- package/dist/src/evm/index.js.map +20 -0
- package/dist/src/index.cjs +5 -0
- package/dist/src/index.cjs.map +67 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +67 -0
- package/dist/src/near/index.cjs +4 -0
- package/dist/src/near/index.cjs.map +16 -0
- package/dist/src/near/index.js +4 -0
- package/dist/src/near/index.js.map +16 -0
- package/dist/src/radix/index.cjs +4 -0
- package/dist/src/radix/index.cjs.map +10 -0
- package/dist/src/radix/index.js +4 -0
- package/dist/src/radix/index.js.map +10 -0
- package/dist/src/ripple/index.cjs +4 -0
- package/dist/src/ripple/index.cjs.map +10 -0
- package/dist/src/ripple/index.js +4 -0
- package/dist/src/ripple/index.js.map +10 -0
- package/dist/src/solana/index.cjs +4 -0
- package/dist/src/solana/index.cjs.map +11 -0
- package/dist/src/solana/index.js +4 -0
- package/dist/src/solana/index.js.map +11 -0
- package/dist/src/substrate/index.cjs +4 -0
- package/dist/src/substrate/index.cjs.map +13 -0
- package/dist/src/substrate/index.js +4 -0
- package/dist/src/substrate/index.js.map +13 -0
- package/dist/src/sui/index.cjs +4 -0
- package/dist/src/sui/index.cjs.map +11 -0
- package/dist/src/sui/index.js +4 -0
- package/dist/src/sui/index.js.map +11 -0
- package/dist/src/ton/index.cjs +4 -0
- package/dist/src/ton/index.cjs.map +11 -0
- package/dist/src/ton/index.js +4 -0
- package/dist/src/ton/index.js.map +11 -0
- package/dist/src/tron/index.cjs +4 -0
- package/dist/src/tron/index.cjs.map +13 -0
- package/dist/src/tron/index.js +4 -0
- package/dist/src/tron/index.js.map +13 -0
- package/dist/src/utxo/index.cjs +5 -0
- package/dist/src/utxo/index.cjs.map +21 -0
- package/dist/src/utxo/index.js +5 -0
- package/dist/src/utxo/index.js.map +21 -0
- package/dist/types/cardano/index.d.ts +3 -0
- package/dist/types/cardano/index.d.ts.map +1 -0
- package/dist/types/cardano/toolbox.d.ts +34 -0
- package/dist/types/cardano/toolbox.d.ts.map +1 -0
- package/dist/types/cardano/types.d.ts +11 -0
- package/dist/types/cardano/types.d.ts.map +1 -0
- package/dist/types/cosmos/index.d.ts +5 -0
- package/dist/types/cosmos/index.d.ts.map +1 -0
- package/dist/types/cosmos/thorchainUtils/addressFormat.d.ts +5 -0
- package/dist/types/cosmos/thorchainUtils/addressFormat.d.ts.map +1 -0
- package/dist/types/cosmos/thorchainUtils/index.d.ts +5 -0
- package/dist/types/cosmos/thorchainUtils/index.d.ts.map +1 -0
- package/dist/types/cosmos/thorchainUtils/messages.d.ts +208 -0
- package/dist/types/cosmos/thorchainUtils/messages.d.ts.map +1 -0
- package/dist/types/cosmos/thorchainUtils/registry.d.ts +4 -0
- package/dist/types/cosmos/thorchainUtils/registry.d.ts.map +1 -0
- package/dist/types/cosmos/thorchainUtils/types/MsgCompiled.d.ts +2 -0
- package/dist/types/cosmos/thorchainUtils/types/MsgCompiled.d.ts.map +1 -0
- package/dist/types/cosmos/thorchainUtils/types/client-types.d.ts +63 -0
- package/dist/types/cosmos/thorchainUtils/types/client-types.d.ts.map +1 -0
- package/dist/types/cosmos/thorchainUtils/types/index.d.ts +2 -0
- package/dist/types/cosmos/thorchainUtils/types/index.d.ts.map +1 -0
- package/dist/types/cosmos/toolbox/cosmos.d.ts +62 -0
- package/dist/types/cosmos/toolbox/cosmos.d.ts.map +1 -0
- package/dist/types/cosmos/toolbox/index.d.ts +15 -0
- package/dist/types/cosmos/toolbox/index.d.ts.map +1 -0
- package/dist/types/cosmos/toolbox/thorchain.d.ts +158 -0
- package/dist/types/cosmos/toolbox/thorchain.d.ts.map +1 -0
- package/dist/types/cosmos/types.d.ts +49 -0
- package/dist/types/cosmos/types.d.ts.map +1 -0
- package/dist/types/cosmos/util.d.ts +74 -0
- package/dist/types/cosmos/util.d.ts.map +1 -0
- package/dist/types/evm/api.d.ts +8 -0
- package/dist/types/evm/api.d.ts.map +1 -0
- package/dist/types/evm/contracts/eth/multicall.d.ts +36 -0
- package/dist/types/evm/contracts/eth/multicall.d.ts.map +1 -0
- package/dist/types/evm/contracts/op/gasOracle.d.ts +40 -0
- package/dist/types/evm/contracts/op/gasOracle.d.ts.map +1 -0
- package/dist/types/evm/helpers.d.ts +6 -0
- package/dist/types/evm/helpers.d.ts.map +1 -0
- package/dist/types/evm/index.d.ts +5 -0
- package/dist/types/evm/index.d.ts.map +1 -0
- package/dist/types/evm/toolbox/baseEVMToolbox.d.ts +83 -0
- package/dist/types/evm/toolbox/baseEVMToolbox.d.ts.map +1 -0
- package/dist/types/evm/toolbox/evm.d.ts +767 -0
- package/dist/types/evm/toolbox/evm.d.ts.map +1 -0
- package/dist/types/evm/toolbox/index.d.ts +7 -0
- package/dist/types/evm/toolbox/index.d.ts.map +1 -0
- package/dist/types/evm/toolbox/op.d.ts +76 -0
- package/dist/types/evm/toolbox/op.d.ts.map +1 -0
- package/dist/types/evm/types.d.ts +108 -0
- package/dist/types/evm/types.d.ts.map +1 -0
- package/dist/types/index.d.ts +75 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/near/helpers/core.d.ts +15 -0
- package/dist/types/near/helpers/core.d.ts.map +1 -0
- package/dist/types/near/helpers/gasEstimation.d.ts +41 -0
- package/dist/types/near/helpers/gasEstimation.d.ts.map +1 -0
- package/dist/types/near/helpers/nep141.d.ts +36 -0
- package/dist/types/near/helpers/nep141.d.ts.map +1 -0
- package/dist/types/near/index.d.ts +10 -0
- package/dist/types/near/index.d.ts.map +1 -0
- package/dist/types/near/toolbox.d.ts +32 -0
- package/dist/types/near/toolbox.d.ts.map +1 -0
- package/dist/types/near/types/contract.d.ts +38 -0
- package/dist/types/near/types/contract.d.ts.map +1 -0
- package/dist/types/near/types/nep141.d.ts +29 -0
- package/dist/types/near/types/nep141.d.ts.map +1 -0
- package/dist/types/near/types/toolbox.d.ts +51 -0
- package/dist/types/near/types/toolbox.d.ts.map +1 -0
- package/dist/types/near/types.d.ts +47 -0
- package/dist/types/near/types.d.ts.map +1 -0
- package/dist/types/radix/index.d.ts +14 -0
- package/dist/types/radix/index.d.ts.map +1 -0
- package/dist/types/ripple/index.d.ts +46 -0
- package/dist/types/ripple/index.d.ts.map +1 -0
- package/dist/types/solana/index.d.ts +23 -0
- package/dist/types/solana/index.d.ts.map +1 -0
- package/dist/types/solana/toolbox.d.ts +51 -0
- package/dist/types/solana/toolbox.d.ts.map +1 -0
- package/dist/types/substrate/balance.d.ts +17 -0
- package/dist/types/substrate/balance.d.ts.map +1 -0
- package/dist/types/substrate/index.d.ts +3 -0
- package/dist/types/substrate/index.d.ts.map +1 -0
- package/dist/types/substrate/substrate.d.ts +148 -0
- package/dist/types/substrate/substrate.d.ts.map +1 -0
- package/dist/types/substrate/types.d.ts +100 -0
- package/dist/types/substrate/types.d.ts.map +1 -0
- package/dist/types/sui/index.d.ts +3 -0
- package/dist/types/sui/index.d.ts.map +1 -0
- package/dist/types/sui/toolbox.d.ts +19 -0
- package/dist/types/sui/toolbox.d.ts.map +1 -0
- package/dist/types/sui/types.d.ts +16 -0
- package/dist/types/sui/types.d.ts.map +1 -0
- package/dist/types/ton/index.d.ts +3 -0
- package/dist/types/ton/index.d.ts.map +1 -0
- package/dist/types/ton/toolbox.d.ts +14 -0
- package/dist/types/ton/toolbox.d.ts.map +1 -0
- package/dist/types/ton/types.d.ts +22 -0
- package/dist/types/ton/types.d.ts.map +1 -0
- package/dist/types/tron/helpers/trc20.abi.d.ts +156 -0
- package/dist/types/tron/helpers/trc20.abi.d.ts.map +1 -0
- package/dist/types/tron/helpers/trongrid.d.ts +8 -0
- package/dist/types/tron/helpers/trongrid.d.ts.map +1 -0
- package/dist/types/tron/index.d.ts +6 -0
- package/dist/types/tron/index.d.ts.map +1 -0
- package/dist/types/tron/toolbox.d.ts +26 -0
- package/dist/types/tron/toolbox.d.ts.map +1 -0
- package/dist/types/tron/types.d.ts +103 -0
- package/dist/types/tron/types.d.ts.map +1 -0
- package/dist/types/types.d.ts +26 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/utils.d.ts +4 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/dist/types/utxo/helpers/api.d.ts +101 -0
- package/dist/types/utxo/helpers/api.d.ts.map +1 -0
- package/dist/types/utxo/helpers/bchaddrjs.d.ts +10 -0
- package/dist/types/utxo/helpers/bchaddrjs.d.ts.map +1 -0
- package/dist/types/utxo/helpers/coinselect.d.ts +17 -0
- package/dist/types/utxo/helpers/coinselect.d.ts.map +1 -0
- package/dist/types/utxo/helpers/index.d.ts +5 -0
- package/dist/types/utxo/helpers/index.d.ts.map +1 -0
- package/dist/types/utxo/helpers/txSize.d.ts +21 -0
- package/dist/types/utxo/helpers/txSize.d.ts.map +1 -0
- package/dist/types/utxo/index.d.ts +7 -0
- package/dist/types/utxo/index.d.ts.map +1 -0
- package/dist/types/utxo/toolbox/bitcoinCash.d.ts +93 -0
- package/dist/types/utxo/toolbox/bitcoinCash.d.ts.map +1 -0
- package/dist/types/utxo/toolbox/index.d.ts +28 -0
- package/dist/types/utxo/toolbox/index.d.ts.map +1 -0
- package/dist/types/utxo/toolbox/params.d.ts +32 -0
- package/dist/types/utxo/toolbox/params.d.ts.map +1 -0
- package/dist/types/utxo/toolbox/utxo.d.ts +103 -0
- package/dist/types/utxo/toolbox/utxo.d.ts.map +1 -0
- package/dist/types/utxo/toolbox/validators.d.ts +4 -0
- package/dist/types/utxo/toolbox/validators.d.ts.map +1 -0
- package/dist/types/utxo/toolbox/zcash.d.ts +72 -0
- package/dist/types/utxo/toolbox/zcash.d.ts.map +1 -0
- package/dist/types/utxo/types.d.ts +46 -0
- package/dist/types/utxo/types.d.ts.map +1 -0
- package/package.json +205 -0
- package/src/__tests__/address-validation-all-chains.test.ts +162 -0
- package/src/__tests__/addressValidator.test.ts +162 -0
- package/src/cardano/__tests__/toolbox.test.ts +48 -0
- package/src/cardano/index.ts +2 -0
- package/src/cardano/toolbox.ts +168 -0
- package/src/cardano/types.ts +10 -0
- package/src/cosmos/__tests__/toolbox.test.ts +91 -0
- package/src/cosmos/index.ts +4 -0
- package/src/cosmos/thorchainUtils/addressFormat.ts +22 -0
- package/src/cosmos/thorchainUtils/index.ts +4 -0
- package/src/cosmos/thorchainUtils/messages.ts +212 -0
- package/src/cosmos/thorchainUtils/registry.ts +43 -0
- package/src/cosmos/thorchainUtils/types/MsgCompiled.ts +2800 -0
- package/src/cosmos/thorchainUtils/types/client-types.ts +54 -0
- package/src/cosmos/thorchainUtils/types/index.ts +1 -0
- package/src/cosmos/toolbox/cosmos.ts +345 -0
- package/src/cosmos/toolbox/index.ts +35 -0
- package/src/cosmos/toolbox/thorchain.ts +249 -0
- package/src/cosmos/types.ts +48 -0
- package/src/cosmos/util.ts +214 -0
- package/src/evm/__tests__/address-validation.test.ts +84 -0
- package/src/evm/__tests__/ethereum.test.ts +137 -0
- package/src/evm/__tests__/signMessage.test.ts +60 -0
- package/src/evm/api.ts +10 -0
- package/src/evm/contracts/eth/multicall.ts +165 -0
- package/src/evm/contracts/op/gasOracle.ts +145 -0
- package/src/evm/helpers.ts +73 -0
- package/src/evm/index.ts +4 -0
- package/src/evm/toolbox/baseEVMToolbox.ts +695 -0
- package/src/evm/toolbox/evm.ts +67 -0
- package/src/evm/toolbox/index.ts +44 -0
- package/src/evm/toolbox/op.ts +156 -0
- package/src/evm/types.ts +146 -0
- package/src/index.ts +260 -0
- package/src/near/__tests__/core.test.ts +70 -0
- package/src/near/helpers/core.ts +85 -0
- package/src/near/helpers/gasEstimation.ts +96 -0
- package/src/near/helpers/nep141.ts +50 -0
- package/src/near/index.ts +21 -0
- package/src/near/toolbox.ts +421 -0
- package/src/near/types/contract.ts +32 -0
- package/src/near/types/nep141.ts +34 -0
- package/src/near/types/toolbox.ts +55 -0
- package/src/near/types.ts +44 -0
- package/src/radix/index.ts +132 -0
- package/src/ripple/index.ts +179 -0
- package/src/solana/index.ts +36 -0
- package/src/solana/toolbox.ts +415 -0
- package/src/substrate/balance.ts +88 -0
- package/src/substrate/index.ts +2 -0
- package/src/substrate/substrate.ts +281 -0
- package/src/substrate/types.ts +115 -0
- package/src/sui/__tests__/toolbox.test.ts +82 -0
- package/src/sui/index.ts +2 -0
- package/src/sui/toolbox.ts +165 -0
- package/src/sui/types.ts +11 -0
- package/src/ton/__tests__/toolbox.test.ts +63 -0
- package/src/ton/index.ts +2 -0
- package/src/ton/toolbox.ts +136 -0
- package/src/ton/types.ts +13 -0
- package/src/tron/__tests__/toolbox.test.ts +221 -0
- package/src/tron/helpers/trc20.abi.ts +107 -0
- package/src/tron/helpers/trongrid.ts +53 -0
- package/src/tron/index.ts +21 -0
- package/src/tron/toolbox.ts +585 -0
- package/src/tron/types.ts +83 -0
- package/src/types.ts +28 -0
- package/src/utils.ts +27 -0
- package/src/utxo/__tests__/zcash-integration.test.ts +97 -0
- package/src/utxo/helpers/api.ts +471 -0
- package/src/utxo/helpers/bchaddrjs.ts +166 -0
- package/src/utxo/helpers/coinselect.ts +92 -0
- package/src/utxo/helpers/index.ts +4 -0
- package/src/utxo/helpers/txSize.ts +137 -0
- package/src/utxo/index.ts +6 -0
- package/src/utxo/toolbox/bitcoinCash.ts +243 -0
- package/src/utxo/toolbox/index.ts +59 -0
- package/src/utxo/toolbox/params.ts +18 -0
- package/src/utxo/toolbox/utxo.ts +439 -0
- package/src/utxo/toolbox/validators.ts +36 -0
- package/src/utxo/toolbox/zcash.ts +245 -0
- package/src/utxo/types.ts +39 -0
package/src/utils.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AssetValue, type Chain, getChainConfig } from "@uswap/helpers";
|
|
2
|
+
import { SwapKitApi } from "@uswap/helpers/api";
|
|
3
|
+
|
|
4
|
+
const pid = typeof process !== "undefined" && process.pid ? process.pid.toString(36) : "";
|
|
5
|
+
|
|
6
|
+
let last = 0;
|
|
7
|
+
export function uniqid() {
|
|
8
|
+
function now() {
|
|
9
|
+
const time = Date.now();
|
|
10
|
+
const lastTime = last || time;
|
|
11
|
+
last = lastTime;
|
|
12
|
+
|
|
13
|
+
return time > last ? time : lastTime + 1;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return pid + now().toString(36);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getBalance<T extends Chain>(chain: T) {
|
|
20
|
+
return async function getBalance(address: string, scamFilter = true) {
|
|
21
|
+
const balances = await SwapKitApi.getChainBalance({ address, chain, scamFilter });
|
|
22
|
+
const { baseDecimal } = getChainConfig(chain);
|
|
23
|
+
return balances.map(({ identifier, value, decimal }) => {
|
|
24
|
+
return new AssetValue({ decimal: decimal || baseDecimal, identifier, value });
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { Chain, DerivationPath } from "@uswap/helpers";
|
|
3
|
+
import { getUtxoToolbox } from "../toolbox";
|
|
4
|
+
|
|
5
|
+
describe("UTXO Toolbox Zcash Integration", () => {
|
|
6
|
+
const testPhrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
|
|
7
|
+
|
|
8
|
+
it("should create Zcash toolbox through main UTXO toolbox factory", async () => {
|
|
9
|
+
const toolbox = await getUtxoToolbox(Chain.Zcash);
|
|
10
|
+
|
|
11
|
+
expect(toolbox).toBeDefined();
|
|
12
|
+
expect(typeof toolbox.validateAddress).toBe("function");
|
|
13
|
+
expect(typeof toolbox.getBalance).toBe("function");
|
|
14
|
+
expect(typeof toolbox.getFeeRates).toBe("function");
|
|
15
|
+
expect(typeof toolbox.broadcastTx).toBe("function");
|
|
16
|
+
expect(typeof toolbox.createTransaction).toBe("function");
|
|
17
|
+
expect(typeof toolbox.transfer).toBe("function");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should create Zcash toolbox with phrase", async () => {
|
|
21
|
+
const toolbox = await getUtxoToolbox(Chain.Zcash, { phrase: testPhrase });
|
|
22
|
+
|
|
23
|
+
expect(toolbox).toBeDefined();
|
|
24
|
+
expect(() => toolbox.getAddress()).not.toThrow();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should generate valid Zcash addresses", async () => {
|
|
28
|
+
const toolbox = await getUtxoToolbox(Chain.Zcash, { phrase: testPhrase });
|
|
29
|
+
|
|
30
|
+
const address = await toolbox.getAddress();
|
|
31
|
+
expect(address).toBeDefined();
|
|
32
|
+
expect(typeof address).toBe("string");
|
|
33
|
+
expect(address?.startsWith("t1")).toBe(true); // Zcash mainnet addresses start with t1
|
|
34
|
+
expect(toolbox.validateAddress(address || "")).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should validate Zcash addresses correctly", async () => {
|
|
38
|
+
const toolbox = await getUtxoToolbox(Chain.Zcash);
|
|
39
|
+
|
|
40
|
+
// Valid Zcash mainnet address format
|
|
41
|
+
expect(toolbox.validateAddress("t1XVXWCvpMgBvUaed4XDqWtgQgJSu1Ghz7F")).toBe(true);
|
|
42
|
+
|
|
43
|
+
// Invalid addresses
|
|
44
|
+
expect(toolbox.validateAddress("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2")).toBe(false); // Bitcoin address
|
|
45
|
+
expect(toolbox.validateAddress("zcash:qr5agtachyxvrwxu76vzszan5pnvuzy8dm")).toBe(false); // Wrong format
|
|
46
|
+
expect(toolbox.validateAddress("")).toBe(false); // Empty string
|
|
47
|
+
expect(toolbox.validateAddress("invalid")).toBe(false); // Invalid string
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should reject shielded addresses", async () => {
|
|
51
|
+
const toolbox = await getUtxoToolbox(Chain.Zcash);
|
|
52
|
+
|
|
53
|
+
// Test z-address (shielded) - should be rejected with warning
|
|
54
|
+
const originalWarn = console.warn;
|
|
55
|
+
let warnCalled = false;
|
|
56
|
+
let warnMessage = "";
|
|
57
|
+
|
|
58
|
+
console.warn = (message: string) => {
|
|
59
|
+
warnCalled = true;
|
|
60
|
+
warnMessage = message;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const isValid = toolbox.validateAddress("zs1z7rejlpsa98s2rrrfkwmaxu2xldqmfq5nj2m3hq6s7r8qjq8eqqqq9p4e7x");
|
|
64
|
+
|
|
65
|
+
expect(isValid).toBe(false);
|
|
66
|
+
expect(warnCalled).toBe(true);
|
|
67
|
+
expect(warnMessage).toBe(
|
|
68
|
+
"Shielded Zcash addresses (z-addresses) are not supported. Use transparent addresses (t1/t3) only.",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
console.warn = originalWarn;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should create keys for derivation path", async () => {
|
|
75
|
+
const toolbox = await getUtxoToolbox(Chain.Zcash, { phrase: testPhrase });
|
|
76
|
+
|
|
77
|
+
const keys = await toolbox.createKeysForPath({ derivationPath: DerivationPath.ZEC, phrase: testPhrase });
|
|
78
|
+
|
|
79
|
+
expect(keys).toBeDefined();
|
|
80
|
+
expect(keys.publicKey).toBeDefined();
|
|
81
|
+
expect(keys.privateKey).toBeDefined();
|
|
82
|
+
expect(typeof keys.toWIF).toBe("function");
|
|
83
|
+
|
|
84
|
+
const address = await toolbox.getAddress();
|
|
85
|
+
expect(address).toBeDefined();
|
|
86
|
+
expect(address?.startsWith("t1")).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should get WIF private key from mnemonic", async () => {
|
|
90
|
+
const toolbox = await getUtxoToolbox(Chain.Zcash, { phrase: testPhrase });
|
|
91
|
+
|
|
92
|
+
const wif = await toolbox.getPrivateKeyFromMnemonic({ derivationPath: DerivationPath.ZEC, phrase: testPhrase });
|
|
93
|
+
|
|
94
|
+
expect(typeof wif).toBe("string");
|
|
95
|
+
expect(wif.length).toBeGreaterThan(50); // WIF keys are typically 51-52 characters
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { networks as zcashNetworks } from "@bitgo/utxo-lib";
|
|
2
|
+
import { Chain, getRPCUrl, RequestClient, SKConfig, SwapKitError, type UTXOChain, warnOnce } from "@uswap/helpers";
|
|
3
|
+
import { networks } from "bitcoinjs-lib";
|
|
4
|
+
// @ts-expect-error
|
|
5
|
+
import coininfo from "coininfo";
|
|
6
|
+
import { uniqid } from "../../utils";
|
|
7
|
+
|
|
8
|
+
type BlockchairParams<T> = T & { chain: Chain; apiKey?: string };
|
|
9
|
+
type BlockchairFetchUnspentUtxoParams = BlockchairParams<{
|
|
10
|
+
offset?: number;
|
|
11
|
+
limit?: number;
|
|
12
|
+
address: string;
|
|
13
|
+
targetValue?: number;
|
|
14
|
+
accumulativeValue?: number;
|
|
15
|
+
}>;
|
|
16
|
+
|
|
17
|
+
async function broadcastUTXOTx({ chain, txHash }: { chain: Chain; txHash: string }) {
|
|
18
|
+
// Use Blockchair's push transaction endpoint (no API key needed)
|
|
19
|
+
const url = `${baseUrl(chain)}/push/transaction`;
|
|
20
|
+
const body = JSON.stringify({ data: txHash });
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const response = await RequestClient.post<{
|
|
24
|
+
data: { transaction_hash: string } | null;
|
|
25
|
+
context: { code: number; error?: string };
|
|
26
|
+
}>(url, { body, headers: { "Content-Type": "application/json" } });
|
|
27
|
+
|
|
28
|
+
if (response.context.code !== 200) {
|
|
29
|
+
throw new SwapKitError("toolbox_utxo_broadcast_failed", {
|
|
30
|
+
error: response.context.error || "Transaction broadcast failed",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return response.data?.transaction_hash || txHash;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
// Fallback to RPC if Blockchair fails
|
|
37
|
+
const rpcUrl = await getRPCUrl(chain);
|
|
38
|
+
if (rpcUrl) {
|
|
39
|
+
const rpcBody = JSON.stringify({ id: uniqid(), jsonrpc: "2.0", method: "sendrawtransaction", params: [txHash] });
|
|
40
|
+
|
|
41
|
+
const rpcResponse = await RequestClient.post<{
|
|
42
|
+
id: string;
|
|
43
|
+
result: string;
|
|
44
|
+
error: { message: string; code?: number } | null;
|
|
45
|
+
}>(rpcUrl, { body: rpcBody, headers: { "Content-Type": "application/json" } });
|
|
46
|
+
|
|
47
|
+
if (rpcResponse.error) {
|
|
48
|
+
throw new SwapKitError("toolbox_utxo_broadcast_failed", { error: rpcResponse.error?.message });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (rpcResponse.result.includes('"code":-26')) {
|
|
52
|
+
throw new SwapKitError("toolbox_utxo_invalid_transaction", { error: "Transaction amount was too low" });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return rpcResponse.result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function baseUrl(chain: Chain) {
|
|
63
|
+
return `https://api.blockchair.com/${mapChainToBlockchairChain(chain)}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getDefaultTxFeeByChain(chain: Chain) {
|
|
67
|
+
switch (chain) {
|
|
68
|
+
case Chain.Bitcoin:
|
|
69
|
+
return 5;
|
|
70
|
+
case Chain.Dogecoin:
|
|
71
|
+
return 10000;
|
|
72
|
+
case Chain.Litecoin:
|
|
73
|
+
return 1;
|
|
74
|
+
case Chain.Zcash:
|
|
75
|
+
return 1;
|
|
76
|
+
default:
|
|
77
|
+
return 2;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function mapChainToBlockchairChain(chain: Chain) {
|
|
82
|
+
switch (chain) {
|
|
83
|
+
case Chain.BitcoinCash:
|
|
84
|
+
return "bitcoin-cash";
|
|
85
|
+
case Chain.Litecoin:
|
|
86
|
+
return "litecoin";
|
|
87
|
+
case Chain.Dash:
|
|
88
|
+
return "dash";
|
|
89
|
+
case Chain.Dogecoin:
|
|
90
|
+
return "dogecoin";
|
|
91
|
+
case Chain.Zcash:
|
|
92
|
+
return "zcash";
|
|
93
|
+
case Chain.Polkadot:
|
|
94
|
+
return "polkadot";
|
|
95
|
+
default:
|
|
96
|
+
return "bitcoin";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function getSuggestedTxFee(chain: Chain) {
|
|
101
|
+
try {
|
|
102
|
+
//Use Bitgo API for fee estimation
|
|
103
|
+
//Refer: https://app.bitgo.com/docs/#operation/v2.tx.getfeeestimate
|
|
104
|
+
const { feePerKb } = await RequestClient.get<{
|
|
105
|
+
feePerKb: number;
|
|
106
|
+
cpfpFeePerKb: number;
|
|
107
|
+
numBlocks: number;
|
|
108
|
+
feeByBlockTarget: { 1: number; 3: number };
|
|
109
|
+
}>(`https://app.bitgo.com/api/v2/${chain.toLowerCase()}/tx/fee`);
|
|
110
|
+
const suggestedFee = feePerKb / 1000;
|
|
111
|
+
|
|
112
|
+
return Math.max(suggestedFee, getDefaultTxFeeByChain(chain));
|
|
113
|
+
} catch {
|
|
114
|
+
return getDefaultTxFeeByChain(chain);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function blockchairRequest<T>(url: string, apiKey?: string): Promise<T> {
|
|
119
|
+
const response = await RequestClient.get<BlockchairResponse<T>>(
|
|
120
|
+
`${url}${apiKey ? `${url.includes("?") ? "&" : "?"}key=${apiKey}` : ""}`,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (!response || response.context.code !== 200)
|
|
124
|
+
throw new SwapKitError("toolbox_utxo_api_error", { error: `Failed to query ${url}` });
|
|
125
|
+
|
|
126
|
+
return response.data as T;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function getAddressData({ address, chain, apiKey }: BlockchairParams<{ address?: string }>) {
|
|
130
|
+
if (!address) throw new SwapKitError("toolbox_utxo_invalid_params", { error: "Address is required" });
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const response = await blockchairRequest<BlockchairAddressResponse>(
|
|
134
|
+
`${baseUrl(chain)}/dashboards/address/${address}?transaction_details=true`,
|
|
135
|
+
apiKey,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return response[address];
|
|
139
|
+
} catch {
|
|
140
|
+
return { address: { balance: 0, transaction_count: 0 }, utxo: [] };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function getUnconfirmedBalance({ address, chain, apiKey }: BlockchairParams<{ address?: string }>) {
|
|
145
|
+
const response = await getAddressData({ address, apiKey, chain });
|
|
146
|
+
|
|
147
|
+
return response?.address.balance || 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function getRawTx({ chain, apiKey, txHash }: BlockchairParams<{ txHash?: string }>) {
|
|
151
|
+
if (!txHash) throw new SwapKitError("toolbox_utxo_invalid_params", { error: "TxHash is required" });
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const rawTxResponse = await blockchairRequest<BlockchairRawTransactionResponse>(
|
|
155
|
+
`${baseUrl(chain)}/raw/transaction/${txHash}`,
|
|
156
|
+
apiKey,
|
|
157
|
+
);
|
|
158
|
+
return rawTxResponse?.[txHash]?.raw_transaction || "";
|
|
159
|
+
} catch (error) {
|
|
160
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
161
|
+
console.error(`Failed to fetch raw transaction: ${errorMessage}`);
|
|
162
|
+
return "";
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function fetchUtxosBatch({ chain, address, apiKey, offset = 0, limit = 30 }: BlockchairFetchUnspentUtxoParams) {
|
|
167
|
+
// Only fetch the fields we need to reduce payload size
|
|
168
|
+
const fields = "is_spent,transaction_hash,index,value,script_hex,block_id,spending_signature_hex";
|
|
169
|
+
|
|
170
|
+
const response = await blockchairRequest<BlockchairOutputsResponse[]>(
|
|
171
|
+
// TODO - remove max value limit once we updated bitcoinjs-lib to support larger values
|
|
172
|
+
`${baseUrl(chain)}/outputs?q=recipient(${address}),is_spent(false),value(..2000000000000000)&s=value(desc)&fields=${fields}&limit=${limit}&offset=${offset}`,
|
|
173
|
+
apiKey,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const txs = response.map(
|
|
177
|
+
({ is_spent, script_hex, block_id, transaction_hash, index, value, spending_signature_hex }) => ({
|
|
178
|
+
hash: transaction_hash,
|
|
179
|
+
index,
|
|
180
|
+
is_confirmed: block_id !== -1,
|
|
181
|
+
is_spent,
|
|
182
|
+
script_hex,
|
|
183
|
+
txHex: spending_signature_hex,
|
|
184
|
+
value,
|
|
185
|
+
}),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
return txs;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getTxsValue(txs: Awaited<ReturnType<typeof fetchUtxosBatch>>) {
|
|
192
|
+
return txs.reduce((total, tx) => total + tx.value, 0);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function pickMostValuableTxs(
|
|
196
|
+
txs: Awaited<ReturnType<typeof fetchUtxosBatch>>,
|
|
197
|
+
targetValue?: number,
|
|
198
|
+
): Awaited<ReturnType<typeof fetchUtxosBatch>> {
|
|
199
|
+
const sortedTxs = [...txs].sort((a, b) => b.value - a.value);
|
|
200
|
+
|
|
201
|
+
if (targetValue) {
|
|
202
|
+
const result = [];
|
|
203
|
+
let accumulated = 0;
|
|
204
|
+
|
|
205
|
+
for (const utxo of sortedTxs) {
|
|
206
|
+
result.push(utxo);
|
|
207
|
+
accumulated += utxo.value;
|
|
208
|
+
if (accumulated >= targetValue) break;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return sortedTxs;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function getUnspentUtxos({
|
|
218
|
+
chain,
|
|
219
|
+
address,
|
|
220
|
+
apiKey,
|
|
221
|
+
targetValue,
|
|
222
|
+
accumulativeValue = 0,
|
|
223
|
+
offset = 0,
|
|
224
|
+
limit = 30,
|
|
225
|
+
}: BlockchairFetchUnspentUtxoParams): Promise<Awaited<ReturnType<typeof fetchUtxosBatch>>> {
|
|
226
|
+
if (!address) throw new SwapKitError("toolbox_utxo_invalid_params", { error: "Address is required" });
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const utxos = await fetchUtxosBatch({ address, apiKey, chain, limit, offset, targetValue });
|
|
230
|
+
const utxosCount = utxos.length;
|
|
231
|
+
const isComplete = utxosCount < limit;
|
|
232
|
+
|
|
233
|
+
const unspentUtxos = utxos.filter(({ is_spent }) => !is_spent);
|
|
234
|
+
|
|
235
|
+
const unspentUtxosValue = getTxsValue(unspentUtxos);
|
|
236
|
+
const totalCurrentValue = accumulativeValue + unspentUtxosValue;
|
|
237
|
+
|
|
238
|
+
const limitReached = targetValue && totalCurrentValue >= targetValue;
|
|
239
|
+
|
|
240
|
+
if (isComplete || limitReached) {
|
|
241
|
+
return pickMostValuableTxs(unspentUtxos, targetValue);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const nextBatch = await getUnspentUtxos({
|
|
245
|
+
accumulativeValue: totalCurrentValue,
|
|
246
|
+
address,
|
|
247
|
+
apiKey,
|
|
248
|
+
chain,
|
|
249
|
+
limit,
|
|
250
|
+
offset: offset + limit,
|
|
251
|
+
targetValue,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const allUtxos = [...unspentUtxos, ...nextBatch];
|
|
255
|
+
|
|
256
|
+
return pickMostValuableTxs(allUtxos, targetValue);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
259
|
+
console.error(`Failed to fetch unspent UTXOs: ${errorMessage}`);
|
|
260
|
+
return [];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function getUtxos({
|
|
265
|
+
address,
|
|
266
|
+
chain,
|
|
267
|
+
apiKey,
|
|
268
|
+
fetchTxHex = true,
|
|
269
|
+
targetValue,
|
|
270
|
+
}: BlockchairParams<{ address: string; fetchTxHex?: boolean; targetValue?: number }>) {
|
|
271
|
+
const utxos = await getUnspentUtxos({ address, apiKey, chain, targetValue });
|
|
272
|
+
|
|
273
|
+
const results = [];
|
|
274
|
+
|
|
275
|
+
for (const { hash, index, script_hex, value } of utxos) {
|
|
276
|
+
let txHex: string | undefined;
|
|
277
|
+
if (fetchTxHex) {
|
|
278
|
+
txHex = await getRawTx({ apiKey, chain, txHash: hash });
|
|
279
|
+
}
|
|
280
|
+
results.push({
|
|
281
|
+
address,
|
|
282
|
+
hash,
|
|
283
|
+
index,
|
|
284
|
+
txHex,
|
|
285
|
+
value,
|
|
286
|
+
witnessUtxo: { script: Buffer.from(script_hex, "hex"), value },
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
return results;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function getUtxoApi(chain: UTXOChain) {
|
|
293
|
+
const apiKey = SKConfig.get("apiKeys").blockchair || "";
|
|
294
|
+
|
|
295
|
+
warnOnce({
|
|
296
|
+
condition: !apiKey,
|
|
297
|
+
id: "no_blockchair_api_key_warning",
|
|
298
|
+
warning: "No Blockchair API key found. Functionality will be limited.",
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
broadcastTx: (txHash: string) => broadcastUTXOTx({ chain, txHash }),
|
|
303
|
+
getAddressData: (address: string) => getAddressData({ address, apiKey, chain }),
|
|
304
|
+
getBalance: (address: string) => getUnconfirmedBalance({ address, apiKey, chain }),
|
|
305
|
+
getRawTx: (txHash: string) => getRawTx({ apiKey, chain, txHash }),
|
|
306
|
+
getSuggestedTxFee: () => getSuggestedTxFee(chain),
|
|
307
|
+
getUtxos: (params: { address: string; fetchTxHex?: boolean; targetValue?: number }) =>
|
|
308
|
+
getUtxos({ ...params, apiKey, chain }),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* "Factory" to ensure typing for custom UTXO APIs
|
|
314
|
+
*/
|
|
315
|
+
export function createCustomUtxoApi(methods: ReturnType<typeof getUtxoApi>) {
|
|
316
|
+
return methods;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function getUtxoNetwork() {
|
|
320
|
+
return function getNetwork(chain: Chain) {
|
|
321
|
+
switch (chain) {
|
|
322
|
+
case Chain.Bitcoin:
|
|
323
|
+
return networks.bitcoin;
|
|
324
|
+
case Chain.BitcoinCash:
|
|
325
|
+
return coininfo.bitcoincash.main.toBitcoinJS();
|
|
326
|
+
case Chain.Dash:
|
|
327
|
+
return coininfo.dash.main.toBitcoinJS();
|
|
328
|
+
case Chain.Litecoin:
|
|
329
|
+
return coininfo.litecoin.main.toBitcoinJS();
|
|
330
|
+
|
|
331
|
+
case Chain.Dogecoin: {
|
|
332
|
+
const bip32 = { private: 0x04358394, public: 0x043587cf };
|
|
333
|
+
const test = coininfo.dogecoin.test;
|
|
334
|
+
test.versions.bip32 = bip32;
|
|
335
|
+
return coininfo.dogecoin.main.toBitcoinJS();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
case Chain.Zcash: {
|
|
339
|
+
return zcashNetworks.zcash;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
default:
|
|
343
|
+
throw new SwapKitError("toolbox_utxo_not_supported", { chain });
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
interface BlockchairVin {
|
|
349
|
+
txid: string;
|
|
350
|
+
vout: number;
|
|
351
|
+
scriptSig: { asm: string; hex: string };
|
|
352
|
+
sequence: number;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
interface BlockchairVout {
|
|
356
|
+
value: number;
|
|
357
|
+
n: number;
|
|
358
|
+
scriptPubKey: { asm: string; hex: string; address: string; type: string; addresses: string[]; reqSigs: number };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
interface BlockchairTransaction {
|
|
362
|
+
block_id: number;
|
|
363
|
+
hash: string;
|
|
364
|
+
time: string;
|
|
365
|
+
balance_change: number;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
interface BlockchairUtxo {
|
|
369
|
+
block_id: number;
|
|
370
|
+
transaction_hash: string;
|
|
371
|
+
index: number;
|
|
372
|
+
value: number;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
interface BlockchairAddressCoreData {
|
|
376
|
+
type: string;
|
|
377
|
+
script_hex: string;
|
|
378
|
+
balance: number;
|
|
379
|
+
balance_usd: number;
|
|
380
|
+
received: number;
|
|
381
|
+
received_usd: number;
|
|
382
|
+
spent: number;
|
|
383
|
+
spent_usd: number;
|
|
384
|
+
output_count: number;
|
|
385
|
+
unspent_output_count: number;
|
|
386
|
+
first_seen_receiving: string;
|
|
387
|
+
last_seen_receiving: string;
|
|
388
|
+
first_seen_spending: null | string;
|
|
389
|
+
last_seen_spending: null | string;
|
|
390
|
+
transaction_count: number;
|
|
391
|
+
scripthash_type: null | string;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
interface BlockchairInputOutputCommonData {
|
|
395
|
+
block_id: number;
|
|
396
|
+
transaction_id: number;
|
|
397
|
+
index: number;
|
|
398
|
+
transaction_hash: string;
|
|
399
|
+
date: string;
|
|
400
|
+
time: string;
|
|
401
|
+
value: number;
|
|
402
|
+
value_usd: number;
|
|
403
|
+
recipient: string;
|
|
404
|
+
type: string;
|
|
405
|
+
script_hex: string;
|
|
406
|
+
is_from_coinbase: boolean;
|
|
407
|
+
is_spendable: boolean | null;
|
|
408
|
+
is_spent: boolean;
|
|
409
|
+
lifespan: number | null;
|
|
410
|
+
cdd: number | null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
interface BlockchairSpendingBlockData {
|
|
414
|
+
spending_block_id: number | null;
|
|
415
|
+
spending_transaction_id: number | null;
|
|
416
|
+
spending_index: number | null;
|
|
417
|
+
spending_transaction_hash: string | null;
|
|
418
|
+
spending_date: string | null;
|
|
419
|
+
spending_time: string | null;
|
|
420
|
+
spending_value_usd: number | null;
|
|
421
|
+
spending_sequence: number | null;
|
|
422
|
+
spending_signature_hex: string | null;
|
|
423
|
+
spending_witness: string | null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
interface BlockchairAddressResponse {
|
|
427
|
+
[key: string]: { address: BlockchairAddressCoreData; transactions: BlockchairTransaction[]; utxo: BlockchairUtxo[] };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
interface BlockchairOutputsResponse extends BlockchairSpendingBlockData, BlockchairInputOutputCommonData {}
|
|
431
|
+
|
|
432
|
+
interface BlockchairRawTransactionResponse {
|
|
433
|
+
[key: string]: {
|
|
434
|
+
raw_transaction: string;
|
|
435
|
+
decoded_raw_transaction: {
|
|
436
|
+
txid: string;
|
|
437
|
+
hash: string;
|
|
438
|
+
version: number;
|
|
439
|
+
size: number;
|
|
440
|
+
vsize: number;
|
|
441
|
+
weight: number;
|
|
442
|
+
locktime: number;
|
|
443
|
+
vin: BlockchairVin[];
|
|
444
|
+
vout: BlockchairVout[];
|
|
445
|
+
};
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
interface BlockchairResponse<T> {
|
|
450
|
+
data: T;
|
|
451
|
+
context: {
|
|
452
|
+
code: number;
|
|
453
|
+
source: string;
|
|
454
|
+
results: number;
|
|
455
|
+
state: number;
|
|
456
|
+
market_price_usd: number;
|
|
457
|
+
cache: { live: boolean; duration: number; since: string; until: string; time: any };
|
|
458
|
+
api: {
|
|
459
|
+
version: string;
|
|
460
|
+
last_major_update: string;
|
|
461
|
+
next_major_update: null | string;
|
|
462
|
+
documentation: string;
|
|
463
|
+
notice: string;
|
|
464
|
+
};
|
|
465
|
+
servers: string;
|
|
466
|
+
time: number;
|
|
467
|
+
render_time: number;
|
|
468
|
+
full_time: number;
|
|
469
|
+
request_cost: number;
|
|
470
|
+
};
|
|
471
|
+
}
|