@chainflip/bitcoin 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +286 -4
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +258 -0
- package/package.json +4 -4
- package/dist/index.mjs +0 -6
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,288 @@
|
|
|
1
|
-
|
|
2
|
-
var _addresscjs = require('./address.cjs'); _createStarExport(_addresscjs);
|
|
3
|
-
var _depositcjs = require('./deposit.cjs');
|
|
1
|
+
'use strict';
|
|
4
2
|
|
|
3
|
+
var assertion = require('@chainflip/utils/assertion');
|
|
4
|
+
var bytes = require('@chainflip/utils/bytes');
|
|
5
|
+
var bitcoin = require('bitcoinjs-lib');
|
|
6
|
+
var base58 = require('@chainflip/utils/base58');
|
|
7
|
+
var chainflip = require('@chainflip/utils/chainflip');
|
|
8
|
+
var consts = require('@chainflip/utils/consts');
|
|
9
|
+
var ss58 = require('@chainflip/utils/ss58');
|
|
10
|
+
var BigNumber = require('bignumber.js');
|
|
11
|
+
var url = require('@chainflip/utils/url');
|
|
12
|
+
var zod = require('zod');
|
|
13
|
+
var scaleTs = require('scale-ts');
|
|
5
14
|
|
|
6
|
-
|
|
15
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
|
+
|
|
17
|
+
function _interopNamespace(e) {
|
|
18
|
+
if (e && e.__esModule) return e;
|
|
19
|
+
var n = Object.create(null);
|
|
20
|
+
if (e) {
|
|
21
|
+
Object.keys(e).forEach(function (k) {
|
|
22
|
+
if (k !== 'default') {
|
|
23
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
24
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function () { return e[k]; }
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
n.default = e;
|
|
32
|
+
return Object.freeze(n);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var bitcoin__namespace = /*#__PURE__*/_interopNamespace(bitcoin);
|
|
36
|
+
var base58__namespace = /*#__PURE__*/_interopNamespace(base58);
|
|
37
|
+
var ss58__namespace = /*#__PURE__*/_interopNamespace(ss58);
|
|
38
|
+
var BigNumber__default = /*#__PURE__*/_interopDefault(BigNumber);
|
|
39
|
+
|
|
40
|
+
// src/address.ts
|
|
41
|
+
|
|
42
|
+
// src/consts.ts
|
|
43
|
+
var networkMap = {
|
|
44
|
+
mainnet: "mainnet",
|
|
45
|
+
perseverance: "testnet",
|
|
46
|
+
sisyphos: "testnet",
|
|
47
|
+
testnet: "testnet",
|
|
48
|
+
backspin: "regtest",
|
|
49
|
+
regtest: "regtest"
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/address.ts
|
|
53
|
+
var p2pkhAddressVersion = {
|
|
54
|
+
mainnet: 0,
|
|
55
|
+
testnet: 111,
|
|
56
|
+
regtest: 111
|
|
57
|
+
};
|
|
58
|
+
var p2shAddressVersion = {
|
|
59
|
+
mainnet: 5,
|
|
60
|
+
testnet: 196,
|
|
61
|
+
regtest: 196
|
|
62
|
+
};
|
|
63
|
+
var networkHrp = {
|
|
64
|
+
mainnet: "bc",
|
|
65
|
+
testnet: "tb",
|
|
66
|
+
regtest: "bcrt"
|
|
67
|
+
};
|
|
68
|
+
var segwitVersions = {
|
|
69
|
+
P2WPKH: 0,
|
|
70
|
+
P2WSH: 0,
|
|
71
|
+
Taproot: 1
|
|
72
|
+
};
|
|
73
|
+
var byteLikeToUint8Array = (data) => typeof data === "string" ? bytes.hexToBytes(data) : new Uint8Array(data);
|
|
74
|
+
var encodeAddress = (data, kind, cfOrBtcnetwork) => {
|
|
75
|
+
const btcNetwork = networkMap[cfOrBtcnetwork];
|
|
76
|
+
assertion.assert(btcNetwork, `Invalid network: ${cfOrBtcnetwork}`);
|
|
77
|
+
assertion.assert(data.length % 2 === 0, "bytes must have an even number of characters");
|
|
78
|
+
assertion.assert(
|
|
79
|
+
typeof data !== "string" || /^(0x)?[0-9a-f]*$/.test(data),
|
|
80
|
+
"bytes are not a valid hex string"
|
|
81
|
+
);
|
|
82
|
+
const bytes = byteLikeToUint8Array(data);
|
|
83
|
+
switch (kind) {
|
|
84
|
+
case "P2PKH":
|
|
85
|
+
case "P2SH": {
|
|
86
|
+
const version = (kind === "P2SH" ? p2shAddressVersion : p2pkhAddressVersion)[btcNetwork];
|
|
87
|
+
return bitcoin__namespace.address.toBase58Check(bytes, version);
|
|
88
|
+
}
|
|
89
|
+
case "P2WPKH":
|
|
90
|
+
case "P2WSH":
|
|
91
|
+
case "Taproot":
|
|
92
|
+
return bitcoin__namespace.address.toBech32(bytes, segwitVersions[kind], networkHrp[btcNetwork]);
|
|
93
|
+
default:
|
|
94
|
+
throw new Error(`Invalid address type: ${kind}`);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var decodeAddress = (address2, cfOrBtcNetwork) => {
|
|
98
|
+
const network = networkMap[cfOrBtcNetwork];
|
|
99
|
+
if (/^[13mn2]/.test(address2)) {
|
|
100
|
+
const { hash, version } = bitcoin__namespace.address.fromBase58Check(address2);
|
|
101
|
+
if (version === p2pkhAddressVersion[network]) {
|
|
102
|
+
return { type: "P2PKH", data: hash, version };
|
|
103
|
+
}
|
|
104
|
+
if (version === p2shAddressVersion[network]) {
|
|
105
|
+
return { type: "P2SH", data: hash, version };
|
|
106
|
+
}
|
|
107
|
+
throw new TypeError(`Invalid version: ${version}`);
|
|
108
|
+
}
|
|
109
|
+
if (/^(bc|tb|bcrt)1/.test(address2)) {
|
|
110
|
+
const { data, prefix, version } = bitcoin__namespace.address.fromBech32(address2);
|
|
111
|
+
assertion.assert(prefix === networkHrp[network], `Invalid prefix: ${prefix}`);
|
|
112
|
+
let type;
|
|
113
|
+
if (version === 0 && data.length === 20) {
|
|
114
|
+
type = "P2WPKH";
|
|
115
|
+
} else if (version === 0) {
|
|
116
|
+
type = "P2WSH";
|
|
117
|
+
} else if (version === 1) {
|
|
118
|
+
type = "Taproot";
|
|
119
|
+
} else {
|
|
120
|
+
throw new TypeError(`Invalid version: ${version}`);
|
|
121
|
+
}
|
|
122
|
+
return { hrp: prefix, data, type, version };
|
|
123
|
+
}
|
|
124
|
+
throw new TypeError(`Invalid address "${address2}" for network "${network}"`);
|
|
125
|
+
};
|
|
126
|
+
var isValidAddressForNetwork = (address2, cfOrBtcNetwork) => {
|
|
127
|
+
try {
|
|
128
|
+
decodeAddress(address2, cfOrBtcNetwork);
|
|
129
|
+
return true;
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var hexString = zod.z.string().regex(/^([0-9a-f]{2})+$/, { message: "expected hex string" });
|
|
135
|
+
var vout = zod.z.object({
|
|
136
|
+
value: zod.z.number().transform((n) => BigInt(new BigNumber__default.default(n).shiftedBy(8).toFixed(0))),
|
|
137
|
+
n: zod.z.number()
|
|
138
|
+
});
|
|
139
|
+
var nulldataVout = zod.z.object({
|
|
140
|
+
scriptPubKey: zod.z.object({
|
|
141
|
+
type: zod.z.literal("nulldata"),
|
|
142
|
+
// remove OP_RETURN and PUSH_BYTES_XX
|
|
143
|
+
hex: hexString.transform((x) => bytes.hexToBytes(`0x${x.slice(4)}`))
|
|
144
|
+
})
|
|
145
|
+
}).and(vout);
|
|
146
|
+
var addressVout = zod.z.object({
|
|
147
|
+
scriptPubKey: zod.z.object({
|
|
148
|
+
type: zod.z.enum([
|
|
149
|
+
"witness_v1_taproot",
|
|
150
|
+
"witness_v0_scripthash",
|
|
151
|
+
"witness_v0_keyhash",
|
|
152
|
+
"pubkeyhash",
|
|
153
|
+
"scripthash"
|
|
154
|
+
]),
|
|
155
|
+
address: zod.z.string()
|
|
156
|
+
})
|
|
157
|
+
}).and(vout);
|
|
158
|
+
var txSchema = zod.z.object({
|
|
159
|
+
vout: zod.z.tuple([addressVout, nulldataVout, addressVout]),
|
|
160
|
+
blockhash: hexString.nullish()
|
|
161
|
+
});
|
|
162
|
+
var blockSchema = zod.z.object({
|
|
163
|
+
height: zod.z.number()
|
|
164
|
+
});
|
|
165
|
+
var responseSchemas = {
|
|
166
|
+
getrawtransaction: txSchema,
|
|
167
|
+
getblock: blockSchema
|
|
168
|
+
};
|
|
169
|
+
var rpcResponse = zod.z.union([
|
|
170
|
+
zod.z.object({ result: zod.z.null(), error: zod.z.object({ code: zod.z.number(), message: zod.z.string() }) }),
|
|
171
|
+
zod.z.object({ result: zod.z.unknown(), error: zod.z.null() })
|
|
172
|
+
]);
|
|
173
|
+
var makeRequest = async (rpcUrl, method, params) => {
|
|
174
|
+
const { url: url$1, headers } = url.parseUrlWithBasicAuth(rpcUrl);
|
|
175
|
+
const res = await fetch(url$1, {
|
|
176
|
+
method: "POST",
|
|
177
|
+
headers: {
|
|
178
|
+
...headers,
|
|
179
|
+
"Content-Type": "application/json"
|
|
180
|
+
},
|
|
181
|
+
body: JSON.stringify({
|
|
182
|
+
jsonrpc: "2.0",
|
|
183
|
+
id: 1,
|
|
184
|
+
method,
|
|
185
|
+
params
|
|
186
|
+
})
|
|
187
|
+
});
|
|
188
|
+
const json = await res.json();
|
|
189
|
+
const result = rpcResponse.parse(json);
|
|
190
|
+
if (result.error) {
|
|
191
|
+
if (result.error.code === -5) return null;
|
|
192
|
+
throw new Error(`RPC error [${result.error.code}]: ${result.error.message}`);
|
|
193
|
+
}
|
|
194
|
+
const parseResult = responseSchemas[method].safeParse(result.result);
|
|
195
|
+
if (!parseResult.success) {
|
|
196
|
+
if (method === "getrawtransaction") return null;
|
|
197
|
+
throw parseResult.error;
|
|
198
|
+
}
|
|
199
|
+
return parseResult.data;
|
|
200
|
+
};
|
|
201
|
+
var addressByteLengths = {
|
|
202
|
+
Bitcoin: void 0,
|
|
203
|
+
Arbitrum: 20,
|
|
204
|
+
Ethereum: 20,
|
|
205
|
+
Solana: 32,
|
|
206
|
+
Polkadot: 32
|
|
207
|
+
};
|
|
208
|
+
var createSwapDataCodec = (asset) => scaleTs.Struct({
|
|
209
|
+
version: scaleTs.u8,
|
|
210
|
+
destinationAsset: scaleTs.u8,
|
|
211
|
+
destinationAddress: scaleTs.Bytes(addressByteLengths[chainflip.assetConstants[asset].chain]),
|
|
212
|
+
parameters: scaleTs.Struct({
|
|
213
|
+
retryDuration: scaleTs.u16,
|
|
214
|
+
minOutputAmount: scaleTs.u128,
|
|
215
|
+
numberOfChunks: scaleTs.u16,
|
|
216
|
+
chunkInterval: scaleTs.u16,
|
|
217
|
+
boostFee: scaleTs.u8,
|
|
218
|
+
brokerFee: scaleTs.u8,
|
|
219
|
+
affiliates: scaleTs.Vector(scaleTs.Struct({ accountIndex: scaleTs.u8, commissionBps: scaleTs.u8 }))
|
|
220
|
+
})
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// src/deposit.ts
|
|
224
|
+
var encodeChainAddress = (data, asset) => {
|
|
225
|
+
switch (chainflip.assetConstants[asset].chain) {
|
|
226
|
+
case "Solana":
|
|
227
|
+
return base58__namespace.encode(data);
|
|
228
|
+
case "Polkadot":
|
|
229
|
+
return ss58__namespace.encode({ data, ss58Format: consts.POLKADOT_SS58_PREFIX });
|
|
230
|
+
case "Ethereum":
|
|
231
|
+
case "Arbitrum":
|
|
232
|
+
return bytes.bytesToHex(data);
|
|
233
|
+
case "Bitcoin":
|
|
234
|
+
return new TextDecoder().decode(data);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
var contractIdToInternalAsset = Object.fromEntries(
|
|
238
|
+
Object.entries(chainflip.assetContractId).map(([asset, id]) => [id, asset])
|
|
239
|
+
);
|
|
240
|
+
var parseVaultSwapData = (data) => {
|
|
241
|
+
const version = data[0];
|
|
242
|
+
assertion.assert(version === 0, "unsupported version");
|
|
243
|
+
const contractId = data[1];
|
|
244
|
+
const outputAsset = contractIdToInternalAsset[contractId];
|
|
245
|
+
assertion.assert(outputAsset, "unknown asset contract id");
|
|
246
|
+
const { destinationAddress, parameters } = createSwapDataCodec(outputAsset).dec(data);
|
|
247
|
+
return {
|
|
248
|
+
...parameters,
|
|
249
|
+
outputAsset,
|
|
250
|
+
destinationAddress: encodeChainAddress(destinationAddress, outputAsset)
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
var getX128PriceFromAmounts = (depositAmount, minOutputAmount) => BigInt(
|
|
254
|
+
new BigNumber.BigNumber(minOutputAmount.toString()).div(depositAmount.toString()).multipliedBy(new BigNumber.BigNumber(2).pow(128)).toFixed(0, BigNumber.BigNumber.ROUND_FLOOR)
|
|
255
|
+
);
|
|
256
|
+
var findVaultSwapData = async (url, txId) => {
|
|
257
|
+
const tx = await makeRequest(url, "getrawtransaction", [txId, true]);
|
|
258
|
+
if (!tx) return null;
|
|
259
|
+
const data = parseVaultSwapData(tx.vout[1].scriptPubKey.hex);
|
|
260
|
+
const amount = tx.vout[0].value;
|
|
261
|
+
const block = tx.blockhash ? await makeRequest(url, "getblock", [tx.blockhash, true]).catch(() => null) : null;
|
|
262
|
+
return {
|
|
263
|
+
inputAsset: "Btc",
|
|
264
|
+
amount,
|
|
265
|
+
depositAddress: tx.vout[0].scriptPubKey.address,
|
|
266
|
+
refundParams: {
|
|
267
|
+
refundAddress: tx.vout[2].scriptPubKey.address,
|
|
268
|
+
retryDuration: data.retryDuration,
|
|
269
|
+
minPrice: getX128PriceFromAmounts(amount, data.minOutputAmount)
|
|
270
|
+
},
|
|
271
|
+
destinationAddress: data.destinationAddress,
|
|
272
|
+
outputAsset: data.outputAsset,
|
|
273
|
+
brokerFee: { account: null, commissionBps: data.brokerFee },
|
|
274
|
+
affiliateFees: data.affiliates,
|
|
275
|
+
ccmDepositMetadata: null,
|
|
276
|
+
maxBoostFee: data.boostFee,
|
|
277
|
+
dcaParams: data.numberOfChunks > 0 && data.chunkInterval > 0 ? {
|
|
278
|
+
chunkInterval: data.chunkInterval,
|
|
279
|
+
numberOfChunks: data.numberOfChunks
|
|
280
|
+
} : null,
|
|
281
|
+
depositChainBlockHeight: block && block.height
|
|
282
|
+
};
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
exports.decodeAddress = decodeAddress;
|
|
286
|
+
exports.encodeAddress = encodeAddress;
|
|
287
|
+
exports.findVaultSwapData = findVaultSwapData;
|
|
288
|
+
exports.isValidAddressForNetwork = isValidAddressForNetwork;
|
package/dist/index.d.cts
CHANGED
|
@@ -28,7 +28,7 @@ declare const encodeAddress: (data: Bytelike, kind: Base58AddressType | SegwitAd
|
|
|
28
28
|
declare const decodeAddress: (address: string, cfOrBtcNetwork: BitcoinNetwork | ChainflipNetwork$1) => DecodedBase58Address | DecodedSegwitAddress;
|
|
29
29
|
declare const isValidAddressForNetwork: (address: string, cfOrBtcNetwork: BitcoinNetwork | ChainflipNetwork$1) => boolean;
|
|
30
30
|
|
|
31
|
-
type BitcoinVaultSwapData = VaultSwapData & {
|
|
31
|
+
type BitcoinVaultSwapData = VaultSwapData<null> & {
|
|
32
32
|
depositAddress: string;
|
|
33
33
|
};
|
|
34
34
|
declare const findVaultSwapData: (url: string, txId: string) => Promise<BitcoinVaultSwapData | null>;
|
package/dist/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ declare const encodeAddress: (data: Bytelike, kind: Base58AddressType | SegwitAd
|
|
|
28
28
|
declare const decodeAddress: (address: string, cfOrBtcNetwork: BitcoinNetwork | ChainflipNetwork$1) => DecodedBase58Address | DecodedSegwitAddress;
|
|
29
29
|
declare const isValidAddressForNetwork: (address: string, cfOrBtcNetwork: BitcoinNetwork | ChainflipNetwork$1) => boolean;
|
|
30
30
|
|
|
31
|
-
type BitcoinVaultSwapData = VaultSwapData & {
|
|
31
|
+
type BitcoinVaultSwapData = VaultSwapData<null> & {
|
|
32
32
|
depositAddress: string;
|
|
33
33
|
};
|
|
34
34
|
declare const findVaultSwapData: (url: string, txId: string) => Promise<BitcoinVaultSwapData | null>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { assert } from '@chainflip/utils/assertion';
|
|
2
|
+
import { hexToBytes, bytesToHex } from '@chainflip/utils/bytes';
|
|
3
|
+
import * as bitcoin from 'bitcoinjs-lib';
|
|
4
|
+
import * as base58 from '@chainflip/utils/base58';
|
|
5
|
+
import { assetContractId, assetConstants } from '@chainflip/utils/chainflip';
|
|
6
|
+
import { POLKADOT_SS58_PREFIX } from '@chainflip/utils/consts';
|
|
7
|
+
import * as ss58 from '@chainflip/utils/ss58';
|
|
8
|
+
import BigNumber, { BigNumber as BigNumber$1 } from 'bignumber.js';
|
|
9
|
+
import { parseUrlWithBasicAuth } from '@chainflip/utils/url';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { Struct, Bytes, u8, Vector, u16, u128 } from 'scale-ts';
|
|
12
|
+
|
|
13
|
+
// src/address.ts
|
|
14
|
+
|
|
15
|
+
// src/consts.ts
|
|
16
|
+
var networkMap = {
|
|
17
|
+
mainnet: "mainnet",
|
|
18
|
+
perseverance: "testnet",
|
|
19
|
+
sisyphos: "testnet",
|
|
20
|
+
testnet: "testnet",
|
|
21
|
+
backspin: "regtest",
|
|
22
|
+
regtest: "regtest"
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/address.ts
|
|
26
|
+
var p2pkhAddressVersion = {
|
|
27
|
+
mainnet: 0,
|
|
28
|
+
testnet: 111,
|
|
29
|
+
regtest: 111
|
|
30
|
+
};
|
|
31
|
+
var p2shAddressVersion = {
|
|
32
|
+
mainnet: 5,
|
|
33
|
+
testnet: 196,
|
|
34
|
+
regtest: 196
|
|
35
|
+
};
|
|
36
|
+
var networkHrp = {
|
|
37
|
+
mainnet: "bc",
|
|
38
|
+
testnet: "tb",
|
|
39
|
+
regtest: "bcrt"
|
|
40
|
+
};
|
|
41
|
+
var segwitVersions = {
|
|
42
|
+
P2WPKH: 0,
|
|
43
|
+
P2WSH: 0,
|
|
44
|
+
Taproot: 1
|
|
45
|
+
};
|
|
46
|
+
var byteLikeToUint8Array = (data) => typeof data === "string" ? hexToBytes(data) : new Uint8Array(data);
|
|
47
|
+
var encodeAddress = (data, kind, cfOrBtcnetwork) => {
|
|
48
|
+
const btcNetwork = networkMap[cfOrBtcnetwork];
|
|
49
|
+
assert(btcNetwork, `Invalid network: ${cfOrBtcnetwork}`);
|
|
50
|
+
assert(data.length % 2 === 0, "bytes must have an even number of characters");
|
|
51
|
+
assert(
|
|
52
|
+
typeof data !== "string" || /^(0x)?[0-9a-f]*$/.test(data),
|
|
53
|
+
"bytes are not a valid hex string"
|
|
54
|
+
);
|
|
55
|
+
const bytes = byteLikeToUint8Array(data);
|
|
56
|
+
switch (kind) {
|
|
57
|
+
case "P2PKH":
|
|
58
|
+
case "P2SH": {
|
|
59
|
+
const version = (kind === "P2SH" ? p2shAddressVersion : p2pkhAddressVersion)[btcNetwork];
|
|
60
|
+
return bitcoin.address.toBase58Check(bytes, version);
|
|
61
|
+
}
|
|
62
|
+
case "P2WPKH":
|
|
63
|
+
case "P2WSH":
|
|
64
|
+
case "Taproot":
|
|
65
|
+
return bitcoin.address.toBech32(bytes, segwitVersions[kind], networkHrp[btcNetwork]);
|
|
66
|
+
default:
|
|
67
|
+
throw new Error(`Invalid address type: ${kind}`);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var decodeAddress = (address2, cfOrBtcNetwork) => {
|
|
71
|
+
const network = networkMap[cfOrBtcNetwork];
|
|
72
|
+
if (/^[13mn2]/.test(address2)) {
|
|
73
|
+
const { hash, version } = bitcoin.address.fromBase58Check(address2);
|
|
74
|
+
if (version === p2pkhAddressVersion[network]) {
|
|
75
|
+
return { type: "P2PKH", data: hash, version };
|
|
76
|
+
}
|
|
77
|
+
if (version === p2shAddressVersion[network]) {
|
|
78
|
+
return { type: "P2SH", data: hash, version };
|
|
79
|
+
}
|
|
80
|
+
throw new TypeError(`Invalid version: ${version}`);
|
|
81
|
+
}
|
|
82
|
+
if (/^(bc|tb|bcrt)1/.test(address2)) {
|
|
83
|
+
const { data, prefix, version } = bitcoin.address.fromBech32(address2);
|
|
84
|
+
assert(prefix === networkHrp[network], `Invalid prefix: ${prefix}`);
|
|
85
|
+
let type;
|
|
86
|
+
if (version === 0 && data.length === 20) {
|
|
87
|
+
type = "P2WPKH";
|
|
88
|
+
} else if (version === 0) {
|
|
89
|
+
type = "P2WSH";
|
|
90
|
+
} else if (version === 1) {
|
|
91
|
+
type = "Taproot";
|
|
92
|
+
} else {
|
|
93
|
+
throw new TypeError(`Invalid version: ${version}`);
|
|
94
|
+
}
|
|
95
|
+
return { hrp: prefix, data, type, version };
|
|
96
|
+
}
|
|
97
|
+
throw new TypeError(`Invalid address "${address2}" for network "${network}"`);
|
|
98
|
+
};
|
|
99
|
+
var isValidAddressForNetwork = (address2, cfOrBtcNetwork) => {
|
|
100
|
+
try {
|
|
101
|
+
decodeAddress(address2, cfOrBtcNetwork);
|
|
102
|
+
return true;
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
var hexString = z.string().regex(/^([0-9a-f]{2})+$/, { message: "expected hex string" });
|
|
108
|
+
var vout = z.object({
|
|
109
|
+
value: z.number().transform((n) => BigInt(new BigNumber(n).shiftedBy(8).toFixed(0))),
|
|
110
|
+
n: z.number()
|
|
111
|
+
});
|
|
112
|
+
var nulldataVout = z.object({
|
|
113
|
+
scriptPubKey: z.object({
|
|
114
|
+
type: z.literal("nulldata"),
|
|
115
|
+
// remove OP_RETURN and PUSH_BYTES_XX
|
|
116
|
+
hex: hexString.transform((x) => hexToBytes(`0x${x.slice(4)}`))
|
|
117
|
+
})
|
|
118
|
+
}).and(vout);
|
|
119
|
+
var addressVout = z.object({
|
|
120
|
+
scriptPubKey: z.object({
|
|
121
|
+
type: z.enum([
|
|
122
|
+
"witness_v1_taproot",
|
|
123
|
+
"witness_v0_scripthash",
|
|
124
|
+
"witness_v0_keyhash",
|
|
125
|
+
"pubkeyhash",
|
|
126
|
+
"scripthash"
|
|
127
|
+
]),
|
|
128
|
+
address: z.string()
|
|
129
|
+
})
|
|
130
|
+
}).and(vout);
|
|
131
|
+
var txSchema = z.object({
|
|
132
|
+
vout: z.tuple([addressVout, nulldataVout, addressVout]),
|
|
133
|
+
blockhash: hexString.nullish()
|
|
134
|
+
});
|
|
135
|
+
var blockSchema = z.object({
|
|
136
|
+
height: z.number()
|
|
137
|
+
});
|
|
138
|
+
var responseSchemas = {
|
|
139
|
+
getrawtransaction: txSchema,
|
|
140
|
+
getblock: blockSchema
|
|
141
|
+
};
|
|
142
|
+
var rpcResponse = z.union([
|
|
143
|
+
z.object({ result: z.null(), error: z.object({ code: z.number(), message: z.string() }) }),
|
|
144
|
+
z.object({ result: z.unknown(), error: z.null() })
|
|
145
|
+
]);
|
|
146
|
+
var makeRequest = async (rpcUrl, method, params) => {
|
|
147
|
+
const { url, headers } = parseUrlWithBasicAuth(rpcUrl);
|
|
148
|
+
const res = await fetch(url, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
...headers,
|
|
152
|
+
"Content-Type": "application/json"
|
|
153
|
+
},
|
|
154
|
+
body: JSON.stringify({
|
|
155
|
+
jsonrpc: "2.0",
|
|
156
|
+
id: 1,
|
|
157
|
+
method,
|
|
158
|
+
params
|
|
159
|
+
})
|
|
160
|
+
});
|
|
161
|
+
const json = await res.json();
|
|
162
|
+
const result = rpcResponse.parse(json);
|
|
163
|
+
if (result.error) {
|
|
164
|
+
if (result.error.code === -5) return null;
|
|
165
|
+
throw new Error(`RPC error [${result.error.code}]: ${result.error.message}`);
|
|
166
|
+
}
|
|
167
|
+
const parseResult = responseSchemas[method].safeParse(result.result);
|
|
168
|
+
if (!parseResult.success) {
|
|
169
|
+
if (method === "getrawtransaction") return null;
|
|
170
|
+
throw parseResult.error;
|
|
171
|
+
}
|
|
172
|
+
return parseResult.data;
|
|
173
|
+
};
|
|
174
|
+
var addressByteLengths = {
|
|
175
|
+
Bitcoin: void 0,
|
|
176
|
+
Arbitrum: 20,
|
|
177
|
+
Ethereum: 20,
|
|
178
|
+
Solana: 32,
|
|
179
|
+
Polkadot: 32
|
|
180
|
+
};
|
|
181
|
+
var createSwapDataCodec = (asset) => Struct({
|
|
182
|
+
version: u8,
|
|
183
|
+
destinationAsset: u8,
|
|
184
|
+
destinationAddress: Bytes(addressByteLengths[assetConstants[asset].chain]),
|
|
185
|
+
parameters: Struct({
|
|
186
|
+
retryDuration: u16,
|
|
187
|
+
minOutputAmount: u128,
|
|
188
|
+
numberOfChunks: u16,
|
|
189
|
+
chunkInterval: u16,
|
|
190
|
+
boostFee: u8,
|
|
191
|
+
brokerFee: u8,
|
|
192
|
+
affiliates: Vector(Struct({ accountIndex: u8, commissionBps: u8 }))
|
|
193
|
+
})
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// src/deposit.ts
|
|
197
|
+
var encodeChainAddress = (data, asset) => {
|
|
198
|
+
switch (assetConstants[asset].chain) {
|
|
199
|
+
case "Solana":
|
|
200
|
+
return base58.encode(data);
|
|
201
|
+
case "Polkadot":
|
|
202
|
+
return ss58.encode({ data, ss58Format: POLKADOT_SS58_PREFIX });
|
|
203
|
+
case "Ethereum":
|
|
204
|
+
case "Arbitrum":
|
|
205
|
+
return bytesToHex(data);
|
|
206
|
+
case "Bitcoin":
|
|
207
|
+
return new TextDecoder().decode(data);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
var contractIdToInternalAsset = Object.fromEntries(
|
|
211
|
+
Object.entries(assetContractId).map(([asset, id]) => [id, asset])
|
|
212
|
+
);
|
|
213
|
+
var parseVaultSwapData = (data) => {
|
|
214
|
+
const version = data[0];
|
|
215
|
+
assert(version === 0, "unsupported version");
|
|
216
|
+
const contractId = data[1];
|
|
217
|
+
const outputAsset = contractIdToInternalAsset[contractId];
|
|
218
|
+
assert(outputAsset, "unknown asset contract id");
|
|
219
|
+
const { destinationAddress, parameters } = createSwapDataCodec(outputAsset).dec(data);
|
|
220
|
+
return {
|
|
221
|
+
...parameters,
|
|
222
|
+
outputAsset,
|
|
223
|
+
destinationAddress: encodeChainAddress(destinationAddress, outputAsset)
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
var getX128PriceFromAmounts = (depositAmount, minOutputAmount) => BigInt(
|
|
227
|
+
new BigNumber$1(minOutputAmount.toString()).div(depositAmount.toString()).multipliedBy(new BigNumber$1(2).pow(128)).toFixed(0, BigNumber$1.ROUND_FLOOR)
|
|
228
|
+
);
|
|
229
|
+
var findVaultSwapData = async (url, txId) => {
|
|
230
|
+
const tx = await makeRequest(url, "getrawtransaction", [txId, true]);
|
|
231
|
+
if (!tx) return null;
|
|
232
|
+
const data = parseVaultSwapData(tx.vout[1].scriptPubKey.hex);
|
|
233
|
+
const amount = tx.vout[0].value;
|
|
234
|
+
const block = tx.blockhash ? await makeRequest(url, "getblock", [tx.blockhash, true]).catch(() => null) : null;
|
|
235
|
+
return {
|
|
236
|
+
inputAsset: "Btc",
|
|
237
|
+
amount,
|
|
238
|
+
depositAddress: tx.vout[0].scriptPubKey.address,
|
|
239
|
+
refundParams: {
|
|
240
|
+
refundAddress: tx.vout[2].scriptPubKey.address,
|
|
241
|
+
retryDuration: data.retryDuration,
|
|
242
|
+
minPrice: getX128PriceFromAmounts(amount, data.minOutputAmount)
|
|
243
|
+
},
|
|
244
|
+
destinationAddress: data.destinationAddress,
|
|
245
|
+
outputAsset: data.outputAsset,
|
|
246
|
+
brokerFee: { account: null, commissionBps: data.brokerFee },
|
|
247
|
+
affiliateFees: data.affiliates,
|
|
248
|
+
ccmDepositMetadata: null,
|
|
249
|
+
maxBoostFee: data.boostFee,
|
|
250
|
+
dcaParams: data.numberOfChunks > 0 && data.chunkInterval > 0 ? {
|
|
251
|
+
chunkInterval: data.chunkInterval,
|
|
252
|
+
numberOfChunks: data.numberOfChunks
|
|
253
|
+
} : null,
|
|
254
|
+
depositChainBlockHeight: block && block.height
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
export { decodeAddress, encodeAddress, findVaultSwapData, isValidAddressForNetwork };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chainflip/bitcoin",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": "https://github.com/chainflip-io/chainflip-product-toolkit.git",
|
|
6
6
|
"publishConfig": {
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"README.md"
|
|
13
13
|
],
|
|
14
14
|
"main": "dist/index.cjs",
|
|
15
|
-
"module": "dist/index.
|
|
15
|
+
"module": "dist/index.js",
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@chainflip/utils": "0.7.
|
|
17
|
+
"@chainflip/utils": "0.7.2",
|
|
18
18
|
"bignumber.js": "^9.1.2",
|
|
19
19
|
"bitcoinjs-lib": "^7.0.0-rc.0",
|
|
20
20
|
"scale-ts": "^1.6.1",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"prettier:write": "pnpm prettier:base --write",
|
|
28
28
|
"prepublish": "pnpm build && pnpm test run",
|
|
29
29
|
"clean": "rm -rf dist",
|
|
30
|
-
"tsup:build": "tsup --
|
|
30
|
+
"tsup:build": "tsup ./src/index.ts --format esm,cjs --dts --treeshake",
|
|
31
31
|
"build": "pnpm clean && pnpm tsup:build",
|
|
32
32
|
"test:ci": "CI=1 pnpm t run",
|
|
33
33
|
"test": "vitest"
|