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