@swapkit/toolboxes 0.0.0-nightly-20250304130539
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/chunk-fazw0jvt.js +3 -0
- package/dist/chunk-fazw0jvt.js.map +9 -0
- package/dist/chunk-tvrdndbw.js +4 -0
- package/dist/chunk-tvrdndbw.js.map +9 -0
- package/dist/cosmos/index.cjs +3 -0
- package/dist/cosmos/index.cjs.map +19 -0
- package/dist/cosmos/index.js +3 -0
- package/dist/cosmos/index.js.map +19 -0
- package/dist/evm/index.cjs +3 -0
- package/dist/evm/index.cjs.map +18 -0
- package/dist/evm/index.js +3 -0
- package/dist/evm/index.js.map +18 -0
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +9 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +9 -0
- package/dist/radix/index.cjs +3 -0
- package/dist/radix/index.cjs.map +10 -0
- package/dist/radix/index.js +3 -0
- package/dist/radix/index.js.map +10 -0
- package/dist/solana/index.cjs +3 -0
- package/dist/solana/index.cjs.map +10 -0
- package/dist/solana/index.js +3 -0
- package/dist/solana/index.js.map +10 -0
- package/dist/substrate/index.cjs +3 -0
- package/dist/substrate/index.cjs.map +12 -0
- package/dist/substrate/index.js +3 -0
- package/dist/substrate/index.js.map +12 -0
- package/dist/utxo/index.cjs +3 -0
- package/dist/utxo/index.cjs.map +17 -0
- package/dist/utxo/index.js +3 -0
- package/dist/utxo/index.js.map +17 -0
- package/package.json +102 -0
- package/src/cosmos/index.ts +8 -0
- package/src/cosmos/thorchainUtils/addressFormat.ts +23 -0
- package/src/cosmos/thorchainUtils/index.ts +4 -0
- package/src/cosmos/thorchainUtils/messages.ts +244 -0
- package/src/cosmos/thorchainUtils/registry.ts +47 -0
- package/src/cosmos/thorchainUtils/types/client-types.ts +79 -0
- package/src/cosmos/thorchainUtils/types/index.ts +1 -0
- package/src/cosmos/thorchainUtils/types/proto/MsgCompiled.js +2806 -0
- package/src/cosmos/thorchainUtils/types/proto/MsgCompiled.ts +2802 -0
- package/src/cosmos/thorchainUtils/util.ts +46 -0
- package/src/cosmos/toolbox/BaseCosmosToolbox.ts +257 -0
- package/src/cosmos/toolbox/gaia.ts +39 -0
- package/src/cosmos/toolbox/getToolboxByChain.ts +29 -0
- package/src/cosmos/toolbox/kujira.ts +61 -0
- package/src/cosmos/toolbox/thorchain.ts +305 -0
- package/src/cosmos/types.ts +42 -0
- package/src/cosmos/util.ts +230 -0
- package/src/evm/__tests__/ethereum.test.ts +147 -0
- package/src/evm/api.ts +157 -0
- package/src/evm/contracts/eth/multicall.ts +165 -0
- package/src/evm/contracts/op/gasOracle.ts +151 -0
- package/src/evm/helpers.ts +213 -0
- package/src/evm/index.ts +15 -0
- package/src/evm/toolbox/baseEVMToolbox.ts +670 -0
- package/src/evm/toolbox/evm.ts +89 -0
- package/src/evm/toolbox/getToolboxByChain.ts +37 -0
- package/src/evm/toolbox/op.ts +152 -0
- package/src/evm/types.ts +110 -0
- package/src/index.ts +0 -0
- package/src/radix/index.ts +151 -0
- package/src/radix/toolbox.ts +693 -0
- package/src/solana/index.ts +49 -0
- package/src/solana/toolbox.ts +271 -0
- package/src/substrate/index.ts +3 -0
- package/src/substrate/toolbox/baseSubstrateToolbox.ts +288 -0
- package/src/substrate/toolbox/index.ts +40 -0
- package/src/substrate/types/index.ts +2 -0
- package/src/substrate/types/network.ts +42 -0
- package/src/substrate/types/wallet.ts +78 -0
- package/src/utxo/helpers/api.ts +431 -0
- package/src/utxo/helpers/bchaddrjs.ts +177 -0
- package/src/utxo/helpers/coinselect.ts +96 -0
- package/src/utxo/helpers/index.ts +5 -0
- package/src/utxo/helpers/txSize.ts +103 -0
- package/src/utxo/helpers/utils.ts +48 -0
- package/src/utxo/index.ts +7 -0
- package/src/utxo/toolbox/bitcoinCash.ts +268 -0
- package/src/utxo/toolbox/index.ts +41 -0
- package/src/utxo/toolbox/utxo.ts +372 -0
- package/src/utxo/types.ts +51 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Chain, type UTXOChain } from "@swapkit/helpers";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
TX_OVERHEAD,
|
|
5
|
+
UTXOScriptType,
|
|
6
|
+
calculateTxSize,
|
|
7
|
+
getInputSize,
|
|
8
|
+
getOutputSize,
|
|
9
|
+
getScriptTypeForAddress,
|
|
10
|
+
} from "../helpers";
|
|
11
|
+
import type { TargetOutput, UTXOCalculateTxSizeParams, UTXOType } from "../types";
|
|
12
|
+
|
|
13
|
+
export const getDustThreshold = (chain: UTXOChain) => {
|
|
14
|
+
switch (chain) {
|
|
15
|
+
case Chain.Bitcoin:
|
|
16
|
+
case Chain.BitcoinCash:
|
|
17
|
+
return 550;
|
|
18
|
+
case Chain.Dash:
|
|
19
|
+
case Chain.Litecoin:
|
|
20
|
+
return 5500;
|
|
21
|
+
case Chain.Dogecoin:
|
|
22
|
+
return 100000;
|
|
23
|
+
default:
|
|
24
|
+
throw new Error("Invalid Chain");
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const accumulative = ({
|
|
29
|
+
inputs,
|
|
30
|
+
outputs,
|
|
31
|
+
feeRate: initialFeeRate = 1,
|
|
32
|
+
chain = Chain.Bitcoin,
|
|
33
|
+
}: UTXOCalculateTxSizeParams & { outputs: TargetOutput[]; chain: UTXOChain }) => {
|
|
34
|
+
const feeRate = Math.ceil(initialFeeRate);
|
|
35
|
+
|
|
36
|
+
const newTxType =
|
|
37
|
+
inputs[0] && "address" in inputs[0] && inputs[0].address
|
|
38
|
+
? getScriptTypeForAddress(inputs[0].address)
|
|
39
|
+
: UTXOScriptType.P2PKH;
|
|
40
|
+
// skip input if adding it would cost more than input is worth
|
|
41
|
+
const filteredInputs = inputs.filter((input) => getInputSize(input) * feeRate <= input.value);
|
|
42
|
+
|
|
43
|
+
const txSizeWithoutInputs =
|
|
44
|
+
TX_OVERHEAD + outputs.reduce((total, output) => total + getOutputSize(output, newTxType), 0);
|
|
45
|
+
|
|
46
|
+
const amountToSend = outputs.reduce((total, output) => total + output.value, 0);
|
|
47
|
+
|
|
48
|
+
let fees = txSizeWithoutInputs * feeRate;
|
|
49
|
+
let inputsValue = 0;
|
|
50
|
+
const inputsToUse: typeof inputs = [];
|
|
51
|
+
|
|
52
|
+
for (const input of filteredInputs) {
|
|
53
|
+
const inputSize = getInputSize(input);
|
|
54
|
+
const inputFee = feeRate * inputSize;
|
|
55
|
+
|
|
56
|
+
fees += inputFee;
|
|
57
|
+
inputsValue += input.value;
|
|
58
|
+
|
|
59
|
+
inputsToUse.push(input);
|
|
60
|
+
|
|
61
|
+
const totalCost = fees + amountToSend;
|
|
62
|
+
|
|
63
|
+
// we need more inputs
|
|
64
|
+
if (inputsValue < totalCost) continue;
|
|
65
|
+
|
|
66
|
+
const remainder = inputsValue - totalCost;
|
|
67
|
+
|
|
68
|
+
const feeForExtraOutput = feeRate * getOutputSize({ address: "", value: 0 }, newTxType);
|
|
69
|
+
|
|
70
|
+
// potential change address
|
|
71
|
+
if (remainder > feeForExtraOutput) {
|
|
72
|
+
const feeAfterExtraOutput = feeForExtraOutput + fees;
|
|
73
|
+
const remainderAfterExtraOutput = inputsValue - (amountToSend + feeAfterExtraOutput);
|
|
74
|
+
|
|
75
|
+
// is it worth a change output aka can we send it in the future?
|
|
76
|
+
if (
|
|
77
|
+
remainderAfterExtraOutput >
|
|
78
|
+
Math.max(getInputSize({} as UTXOType) * feeRate, getDustThreshold(chain))
|
|
79
|
+
) {
|
|
80
|
+
return {
|
|
81
|
+
inputs: inputsToUse,
|
|
82
|
+
outputs: outputs.concat({ value: remainderAfterExtraOutput, address: "" }),
|
|
83
|
+
fee: feeAfterExtraOutput,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
inputs: inputsToUse,
|
|
89
|
+
outputs,
|
|
90
|
+
fee: fees,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// We don't have enough inputs, let's calculate transaction fee accrude to the last input
|
|
95
|
+
return { fee: feeRate * calculateTxSize({ inputs, outputs, feeRate }) };
|
|
96
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TargetOutput,
|
|
3
|
+
UTXOCalculateTxSizeParams,
|
|
4
|
+
UTXOInputWithScriptType,
|
|
5
|
+
UTXOType,
|
|
6
|
+
} from "../types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Minimum transaction fee
|
|
10
|
+
* 1000 satoshi/kB (similar to current `minrelaytxfee`)
|
|
11
|
+
* @see https://github.com/bitcoin/bitcoin/blob/db88db47278d2e7208c50d16ab10cb355067d071/src/validation.h#L56
|
|
12
|
+
*/
|
|
13
|
+
export const MIN_TX_FEE = 1000;
|
|
14
|
+
export const TX_OVERHEAD = 4 + 1 + 1 + 4; //10
|
|
15
|
+
export const OP_RETURN_OVERHEAD = 1 + 8 + 1; //10
|
|
16
|
+
const TX_INPUT_BASE = 32 + 4 + 1 + 4; // 41
|
|
17
|
+
const TX_INPUT_PUBKEYHASH = 107;
|
|
18
|
+
|
|
19
|
+
export async function compileMemo(memo: string) {
|
|
20
|
+
const { script, opcodes } = await import("bitcoinjs-lib");
|
|
21
|
+
const data = Buffer.from(memo, "utf8"); // converts MEMO to buffer
|
|
22
|
+
return script.compile([opcodes.OP_RETURN as number, data]); // Compile OP_RETURN script
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export enum UTXOScriptType {
|
|
26
|
+
P2PKH = "P2PKH", // legacy
|
|
27
|
+
// P2SH = 'P2SH', // multisig
|
|
28
|
+
P2WPKH = "P2WPKH", // bech32 - native segwit
|
|
29
|
+
// P2TR = "P2TR", // taproot
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const InputSizes: Record<UTXOScriptType, number> = {
|
|
33
|
+
[UTXOScriptType.P2PKH]: 148,
|
|
34
|
+
// [UTXOScriptType.P2SH]: 91,
|
|
35
|
+
[UTXOScriptType.P2WPKH]: 68,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const OutputSizes: Record<UTXOScriptType, number> = {
|
|
39
|
+
[UTXOScriptType.P2PKH]: 34,
|
|
40
|
+
// [UTXOScriptType.P2SH]: 91,
|
|
41
|
+
[UTXOScriptType.P2WPKH]: 31,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const getScriptTypeForAddress = (address: string) => {
|
|
45
|
+
if (address.startsWith("bc1") || address.startsWith("ltc1")) {
|
|
46
|
+
return UTXOScriptType.P2WPKH;
|
|
47
|
+
}
|
|
48
|
+
// if (address.startsWith('3') || address.startsWith('M')) {
|
|
49
|
+
// return UTXOScriptType.P2SH;
|
|
50
|
+
// }
|
|
51
|
+
if (
|
|
52
|
+
address.startsWith("1") ||
|
|
53
|
+
address.startsWith("3") ||
|
|
54
|
+
address.startsWith("L") ||
|
|
55
|
+
address.startsWith("M") ||
|
|
56
|
+
address.startsWith("X") ||
|
|
57
|
+
address.startsWith("D") ||
|
|
58
|
+
address.startsWith("bitcoincash:q") ||
|
|
59
|
+
address.startsWith("q")
|
|
60
|
+
) {
|
|
61
|
+
return UTXOScriptType.P2PKH;
|
|
62
|
+
}
|
|
63
|
+
throw new Error("Invalid address");
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const calculateTxSize = ({ inputs, outputs, feeRate }: UTXOCalculateTxSizeParams) => {
|
|
67
|
+
const newTxType =
|
|
68
|
+
inputs[0] && "address" in inputs[0] && inputs[0].address
|
|
69
|
+
? getScriptTypeForAddress(inputs[0].address)
|
|
70
|
+
: UTXOScriptType.P2PKH;
|
|
71
|
+
const inputSize = inputs
|
|
72
|
+
.filter(
|
|
73
|
+
(utxo) =>
|
|
74
|
+
utxo.value >=
|
|
75
|
+
InputSizes["type" in utxo ? utxo.type : UTXOScriptType.P2PKH] * Math.ceil(feeRate),
|
|
76
|
+
)
|
|
77
|
+
.reduce((total, utxo) => total + getInputSize(utxo), 0);
|
|
78
|
+
|
|
79
|
+
const outputSize =
|
|
80
|
+
outputs?.reduce((total, output) => total + getOutputSize(output), 0) || OutputSizes[newTxType];
|
|
81
|
+
|
|
82
|
+
return TX_OVERHEAD + inputSize + outputSize;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const getInputSize = (input: UTXOInputWithScriptType | UTXOType) => {
|
|
86
|
+
if ("type" in input) {
|
|
87
|
+
return InputSizes[input.type];
|
|
88
|
+
}
|
|
89
|
+
if ("address" in input && input.address) {
|
|
90
|
+
return InputSizes[getScriptTypeForAddress(input.address as string)];
|
|
91
|
+
}
|
|
92
|
+
return TX_INPUT_BASE + TX_INPUT_PUBKEYHASH;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const getOutputSize = (output: TargetOutput, scriptType?: UTXOScriptType) => {
|
|
96
|
+
if (output?.script) {
|
|
97
|
+
return OP_RETURN_OVERHEAD + output.script.length + (output.script.length >= 74 ? 2 : 1);
|
|
98
|
+
}
|
|
99
|
+
if (scriptType) {
|
|
100
|
+
return OutputSizes[scriptType];
|
|
101
|
+
}
|
|
102
|
+
return OutputSizes[UTXOScriptType.P2PKH];
|
|
103
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Chain, FeeOption } from "@swapkit/helpers";
|
|
2
|
+
|
|
3
|
+
const pid = typeof process !== "undefined" && process.pid ? process.pid.toString(36) : "";
|
|
4
|
+
|
|
5
|
+
export async function getUtxoNetwork() {
|
|
6
|
+
// @ts-ignore TODO: check why wallets doesn't see modules included in toolbox
|
|
7
|
+
const { coininfo } = await import("coininfo");
|
|
8
|
+
const { networks } = await import("bitcoinjs-lib");
|
|
9
|
+
|
|
10
|
+
return function getNetwork(chain: Chain) {
|
|
11
|
+
switch (chain) {
|
|
12
|
+
case Chain.Bitcoin:
|
|
13
|
+
return networks.bitcoin;
|
|
14
|
+
case Chain.BitcoinCash:
|
|
15
|
+
return coininfo.bitcoincash.main.toBitcoinJS();
|
|
16
|
+
case Chain.Dash:
|
|
17
|
+
return coininfo.dash.main.toBitcoinJS();
|
|
18
|
+
case Chain.Litecoin:
|
|
19
|
+
return coininfo.litecoin.main.toBitcoinJS();
|
|
20
|
+
|
|
21
|
+
case Chain.Dogecoin: {
|
|
22
|
+
const bip32 = { private: 0x04358394, public: 0x043587cf };
|
|
23
|
+
const test = coininfo.dogecoin.test;
|
|
24
|
+
test.versions.bip32 = bip32;
|
|
25
|
+
return coininfo.dogecoin.main.toBitcoinJS();
|
|
26
|
+
}
|
|
27
|
+
default:
|
|
28
|
+
throw new Error("Invalid chain");
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const standardFeeRates = (rate: number) => ({
|
|
34
|
+
[FeeOption.Average]: rate,
|
|
35
|
+
[FeeOption.Fast]: rate * 1.5,
|
|
36
|
+
[FeeOption.Fastest]: rate * 2.0,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
let last = 0;
|
|
40
|
+
const now = () => {
|
|
41
|
+
const time = Date.now();
|
|
42
|
+
const lastTime = last || time;
|
|
43
|
+
last = lastTime;
|
|
44
|
+
|
|
45
|
+
return time > last ? time : lastTime + 1;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const uniqid = () => pid + now().toString(36);
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { Chain, DerivationPath, FeeOption, type UTXOChain } from "@swapkit/helpers";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
accumulative,
|
|
5
|
+
Network as bchNetwork,
|
|
6
|
+
compileMemo,
|
|
7
|
+
detectAddressNetwork,
|
|
8
|
+
getUtxoApi,
|
|
9
|
+
getUtxoNetwork,
|
|
10
|
+
isValidAddress,
|
|
11
|
+
toCashAddress,
|
|
12
|
+
toLegacyAddress,
|
|
13
|
+
} from "../helpers";
|
|
14
|
+
import type {
|
|
15
|
+
TargetOutput,
|
|
16
|
+
TransactionType,
|
|
17
|
+
UTXOBuildTxParams,
|
|
18
|
+
UTXOType,
|
|
19
|
+
UTXOWalletTransferParams,
|
|
20
|
+
} from "../types";
|
|
21
|
+
import { createUTXOToolbox } from "./utxo";
|
|
22
|
+
|
|
23
|
+
const chain = Chain.BitcoinCash as UTXOChain;
|
|
24
|
+
|
|
25
|
+
export function stripPrefix(address: string) {
|
|
26
|
+
return address.replace(/(bchtest:|bitcoincash:)/, "");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function validateAddress(address: string) {
|
|
30
|
+
const strippedAddress = stripPrefix(address);
|
|
31
|
+
return (
|
|
32
|
+
isValidAddress(strippedAddress) && detectAddressNetwork(strippedAddress) === bchNetwork.Mainnet
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function stripToCashAddress(address: string) {
|
|
37
|
+
return stripPrefix(toCashAddress(address));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function createBCHToolbox() {
|
|
41
|
+
const { getBalance, getFeeRates, broadcastTx, ...toolbox } = (
|
|
42
|
+
await createUTXOToolbox(Chain.BitcoinCash)
|
|
43
|
+
)();
|
|
44
|
+
|
|
45
|
+
function handleGetBalance(address: string, _scamFilter = true) {
|
|
46
|
+
return getBalance(stripPrefix(toCashAddress(address)));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return function createBCHToolbox() {
|
|
50
|
+
return {
|
|
51
|
+
...toolbox,
|
|
52
|
+
broadcastTx,
|
|
53
|
+
buildBCHTx,
|
|
54
|
+
buildTx,
|
|
55
|
+
createKeysForPath,
|
|
56
|
+
getAddressFromKeys,
|
|
57
|
+
getBalance: handleGetBalance,
|
|
58
|
+
getFeeRates,
|
|
59
|
+
stripPrefix,
|
|
60
|
+
stripToCashAddress,
|
|
61
|
+
validateAddress,
|
|
62
|
+
transfer: transfer({ getFeeRates, broadcastTx }),
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function buildBCHTx({ assetValue, recipient, memo, feeRate, sender }: UTXOBuildTxParams) {
|
|
68
|
+
const {
|
|
69
|
+
Transaction,
|
|
70
|
+
TransactionBuilder,
|
|
71
|
+
address: bchAddress,
|
|
72
|
+
// @ts-ignore TODO: check why wallets doesn't see modules included in toolbox
|
|
73
|
+
} = await import("@psf/bitcoincashjs-lib");
|
|
74
|
+
if (!validateAddress(recipient)) throw new Error("Invalid address");
|
|
75
|
+
const utxos = await getUtxoApi(chain).scanUTXOs({
|
|
76
|
+
address: stripToCashAddress(sender),
|
|
77
|
+
fetchTxHex: true,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const compiledMemo = memo ? await compileMemo(memo) : null;
|
|
81
|
+
|
|
82
|
+
const targetOutputs: TargetOutput[] = [];
|
|
83
|
+
// output to recipient
|
|
84
|
+
targetOutputs.push({ address: recipient, value: assetValue.getBaseValue("number") });
|
|
85
|
+
const { inputs, outputs } = accumulative({
|
|
86
|
+
inputs: utxos,
|
|
87
|
+
outputs: targetOutputs,
|
|
88
|
+
feeRate,
|
|
89
|
+
chain,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// .inputs and .outputs will be undefined if no solution was found
|
|
93
|
+
if (!(inputs && outputs)) throw new Error("Balance insufficient for transaction");
|
|
94
|
+
const getNetwork = await getUtxoNetwork();
|
|
95
|
+
const builder = new TransactionBuilder(getNetwork(chain));
|
|
96
|
+
|
|
97
|
+
await Promise.all(
|
|
98
|
+
inputs.map(async (utxo: UTXOType) => {
|
|
99
|
+
const txHex = await getUtxoApi(chain).getRawTx(utxo.hash);
|
|
100
|
+
|
|
101
|
+
builder.addInput(Transaction.fromBuffer(Buffer.from(txHex, "hex")), utxo.index);
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
for (const output of outputs) {
|
|
106
|
+
const address =
|
|
107
|
+
"address" in output && output.address ? output.address : toLegacyAddress(sender);
|
|
108
|
+
const getNetwork = await getUtxoNetwork();
|
|
109
|
+
const outputScript = bchAddress.toOutputScript(toLegacyAddress(address), getNetwork(chain));
|
|
110
|
+
|
|
111
|
+
builder.addOutput(outputScript, output.value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// add output for memo
|
|
115
|
+
if (compiledMemo) {
|
|
116
|
+
builder.addOutput(compiledMemo, 0); // Add OP_RETURN {script, value}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { builder, utxos: inputs };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function transfer({
|
|
123
|
+
broadcastTx,
|
|
124
|
+
getFeeRates,
|
|
125
|
+
}: {
|
|
126
|
+
broadcastTx: (txHash: string) => Promise<string>;
|
|
127
|
+
getFeeRates: () => Promise<Record<FeeOption, number>>;
|
|
128
|
+
}) {
|
|
129
|
+
return async function transfer({
|
|
130
|
+
signTransaction,
|
|
131
|
+
from,
|
|
132
|
+
recipient,
|
|
133
|
+
assetValue,
|
|
134
|
+
...rest
|
|
135
|
+
}: UTXOWalletTransferParams<
|
|
136
|
+
{ builder: TransactionBuilderType; utxos: UTXOType[] },
|
|
137
|
+
TransactionType
|
|
138
|
+
>) {
|
|
139
|
+
if (!from) throw new Error("From address must be provided");
|
|
140
|
+
if (!recipient) throw new Error("Recipient address must be provided");
|
|
141
|
+
if (!signTransaction) throw new Error("signTransaction must be provided");
|
|
142
|
+
|
|
143
|
+
const feeRate = rest.feeRate || (await getFeeRates())[FeeOption.Fast];
|
|
144
|
+
|
|
145
|
+
// try out if psbt tx is faster/better/nicer
|
|
146
|
+
const { builder, utxos } = await buildBCHTx({
|
|
147
|
+
...rest,
|
|
148
|
+
assetValue,
|
|
149
|
+
feeRate,
|
|
150
|
+
recipient,
|
|
151
|
+
sender: from,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const tx = await signTransaction({ builder, utxos });
|
|
155
|
+
const txHex = tx.toHex();
|
|
156
|
+
|
|
157
|
+
return broadcastTx(txHex);
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: TODO: refactor
|
|
162
|
+
async function buildTx({ assetValue, recipient, memo, feeRate, sender }: UTXOBuildTxParams) {
|
|
163
|
+
const { Psbt } = await import("bitcoinjs-lib");
|
|
164
|
+
const recipientCashAddress = toCashAddress(recipient);
|
|
165
|
+
if (!validateAddress(recipientCashAddress)) throw new Error("Invalid address");
|
|
166
|
+
|
|
167
|
+
const utxos = await getUtxoApi(chain).scanUTXOs({
|
|
168
|
+
address: stripToCashAddress(sender),
|
|
169
|
+
fetchTxHex: true,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const feeRateWhole = Number(feeRate.toFixed(0));
|
|
173
|
+
const compiledMemo = memo ? await compileMemo(memo) : null;
|
|
174
|
+
|
|
175
|
+
const targetOutputs = [] as TargetOutput[];
|
|
176
|
+
|
|
177
|
+
// output to recipient
|
|
178
|
+
targetOutputs.push({
|
|
179
|
+
address: toLegacyAddress(recipient),
|
|
180
|
+
value: assetValue.getBaseValue("number"),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
//2. add output memo to targets (optional)
|
|
184
|
+
if (compiledMemo) {
|
|
185
|
+
targetOutputs.push({ script: compiledMemo, value: 0 });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const { inputs, outputs } = accumulative({
|
|
189
|
+
inputs: utxos,
|
|
190
|
+
outputs: targetOutputs,
|
|
191
|
+
feeRate: feeRateWhole,
|
|
192
|
+
chain,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// .inputs and .outputs will be undefined if no solution was found
|
|
196
|
+
if (!(inputs && outputs)) throw new Error("Balance insufficient for transaction");
|
|
197
|
+
const getNetwork = await getUtxoNetwork();
|
|
198
|
+
const psbt = new Psbt({ network: getNetwork(chain) }); // Network-specific
|
|
199
|
+
|
|
200
|
+
for (const { hash, index, witnessUtxo } of inputs) {
|
|
201
|
+
psbt.addInput({ hash, index, witnessUtxo });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Outputs
|
|
205
|
+
for (const output of outputs) {
|
|
206
|
+
const address =
|
|
207
|
+
"address" in output && output.address ? output.address : toLegacyAddress(sender);
|
|
208
|
+
const params = output.script
|
|
209
|
+
? compiledMemo
|
|
210
|
+
? { script: compiledMemo, value: 0 }
|
|
211
|
+
: undefined
|
|
212
|
+
: { address, value: output.value };
|
|
213
|
+
|
|
214
|
+
if (params) {
|
|
215
|
+
psbt.addOutput(params);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return { psbt, utxos, inputs: inputs as UTXOType[] };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function createKeysForPath({
|
|
223
|
+
phrase,
|
|
224
|
+
derivationPath = `${DerivationPath.BCH}/0`,
|
|
225
|
+
wif,
|
|
226
|
+
}: { wif?: string; phrase?: string; derivationPath?: string }) {
|
|
227
|
+
const { ECPairFactory } = await import("ecpair");
|
|
228
|
+
const secp256k1 = await import("@bitcoinerlab/secp256k1");
|
|
229
|
+
const { mnemonicToSeedSync } = await import("@scure/bip39");
|
|
230
|
+
// @ts-ignore TODO: check why wallets doesn't see modules included in toolbox
|
|
231
|
+
const { HDNode } = await import("@psf/bitcoincashjs-lib");
|
|
232
|
+
const getNetwork = await getUtxoNetwork();
|
|
233
|
+
|
|
234
|
+
const network = getNetwork(chain);
|
|
235
|
+
|
|
236
|
+
if (wif) {
|
|
237
|
+
return ECPairFactory(secp256k1).fromWIF(wif, network);
|
|
238
|
+
}
|
|
239
|
+
if (!phrase) throw new Error("No phrase provided");
|
|
240
|
+
|
|
241
|
+
const masterHDNode = HDNode.fromSeedBuffer(Buffer.from(mnemonicToSeedSync(phrase)), network);
|
|
242
|
+
const keyPair = masterHDNode.derivePath(derivationPath).keyPair;
|
|
243
|
+
// TODO: Figure out same pattern as in BTC
|
|
244
|
+
// const testWif = keyPair.toWIF();
|
|
245
|
+
// const k = ECPairFactory(secp256k1).fromWIF(testWif, network);
|
|
246
|
+
// const a = payments.p2pkh({ pubkey: k.publicKey, network });
|
|
247
|
+
|
|
248
|
+
return keyPair;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function getAddressFromKeys(keys: { getAddress: (index?: number) => string }) {
|
|
252
|
+
const address = keys.getAddress(0);
|
|
253
|
+
return stripToCashAddress(address);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
type TransactionBuilderType = {
|
|
257
|
+
inputs: any[];
|
|
258
|
+
sign(
|
|
259
|
+
vin: number,
|
|
260
|
+
keyPair: { getAddress: (index?: number) => string },
|
|
261
|
+
redeemScript?: Buffer,
|
|
262
|
+
hashType?: number,
|
|
263
|
+
witnessValue?: number,
|
|
264
|
+
witnessScript?: Buffer,
|
|
265
|
+
signatureAlgorithm?: string,
|
|
266
|
+
): void;
|
|
267
|
+
build(): TransactionType;
|
|
268
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Chain, type UTXOChain } from "@swapkit/helpers";
|
|
2
|
+
|
|
3
|
+
import { createBCHToolbox } from "./bitcoinCash";
|
|
4
|
+
import { createUTXOToolbox } from "./utxo";
|
|
5
|
+
|
|
6
|
+
type BCHToolbox = Awaited<ReturnType<typeof createBCHToolbox>>;
|
|
7
|
+
type CommonUTXOToolbox = Awaited<ReturnType<typeof createUTXOToolbox>>;
|
|
8
|
+
|
|
9
|
+
type UTXOToolboxes = {
|
|
10
|
+
[Chain.BitcoinCash]: BCHToolbox;
|
|
11
|
+
[Chain.Bitcoin]: CommonUTXOToolbox;
|
|
12
|
+
[Chain.Dogecoin]: CommonUTXOToolbox;
|
|
13
|
+
[Chain.Litecoin]: CommonUTXOToolbox;
|
|
14
|
+
[Chain.Dash]: CommonUTXOToolbox;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type UTXOWallets = {
|
|
18
|
+
[key in keyof UTXOToolboxes]: ReturnType<UTXOToolboxes[key]>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export async function getToolboxByChain<T extends UTXOChain>(chain: T): Promise<UTXOToolboxes[T]> {
|
|
22
|
+
switch (chain) {
|
|
23
|
+
case Chain.BitcoinCash: {
|
|
24
|
+
const toolbox = await createBCHToolbox();
|
|
25
|
+
return toolbox as UTXOToolboxes[T];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
case Chain.Bitcoin:
|
|
29
|
+
case Chain.Dogecoin:
|
|
30
|
+
case Chain.Litecoin:
|
|
31
|
+
case Chain.Dash: {
|
|
32
|
+
const toolbox = await createUTXOToolbox(chain);
|
|
33
|
+
return toolbox as UTXOToolboxes[T];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
default:
|
|
37
|
+
throw new Error(`Chain ${chain} is not supported`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { stripToCashAddress, stripPrefix, validateAddress } from "./bitcoinCash";
|