@fundtokens/builders 0.1.0-rc5

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/utils.js ADDED
@@ -0,0 +1,201 @@
1
+ import {
2
+ swapEndianness,
3
+ hash256,
4
+ bigIntToBinUint64LEClamped,
5
+ hexToBin,
6
+ binToHex,
7
+ cashAddressToLockingBytecode,
8
+ binToBigIntUint64LE,
9
+ lockingBytecodeToCashAddress,
10
+ getDustThreshold,
11
+ } from '@bitauth/libauth';
12
+ import { BitcoinCategory } from './constants';
13
+
14
+ export const withDust = output => {
15
+ const o = {
16
+ lockingBytecode: cashAddressToLockingBytecode(output.to).bytecode,
17
+ valueSatoshis: 0n,
18
+ };
19
+ if(output.token) {
20
+ o.token = {
21
+ ...output.token,
22
+ category: hexToBin(output.token?.category),
23
+ };
24
+ }
25
+ return {
26
+ ...output,
27
+ amount: getDustThreshold(o),
28
+ }
29
+ };
30
+
31
+ const categoryAscending = (a, b) => {
32
+ return a.category.localeCompare(b.category);
33
+ };
34
+
35
+ // base - 48bytes
36
+ // per asset - 40bytes
37
+ //
38
+ // two = 128bytes
39
+ // three = 168bytes
40
+ // four = 208bytes
41
+ export function getFundHex(fund) {
42
+ const {
43
+ category,
44
+ amount,
45
+ satoshis,
46
+ assets,
47
+ } = fund;
48
+ const hex = [];
49
+ hex.push(swapEndianness(category)); // 32 bytes
50
+ hex.push(binToHex(bigIntToBinUint64LEClamped(amount))); // 8 bytes
51
+ hex.push(binToHex(bigIntToBinUint64LEClamped(satoshis))); // 8 bytes
52
+ assets.sort(categoryAscending).map(asset => {
53
+ hex.push(swapEndianness(asset.category)); // 32 bytes
54
+ hex.push(binToHex(bigIntToBinUint64LEClamped(asset.amount))); // 8 bytes
55
+ });
56
+ return hex.join('');
57
+ }
58
+
59
+ export function getFundBin(fund) {
60
+ return hexToBin(getFundHex(fund));
61
+ }
62
+
63
+ export function decodeFund(hex) {
64
+ if(typeof hex !== 'string' && typeof hex !== 'number') {
65
+ throw new Error('provide the fund hex as a string or number');
66
+ }
67
+ hex = typeof hex === 'number' ? hex.toString(16) : hex;
68
+
69
+ const fund = {
70
+ category: hex.slice(0, 64),
71
+ amount: binToBigIntUint64LE(hexToBin(hex.slice(64, 80))),
72
+ satoshis: binToBigIntUint64LE(hexToBin(hex.slice(80, 96))),
73
+ assets: [],
74
+ };
75
+
76
+ let assetsHex = hex.slice(96);
77
+
78
+ while(assetsHex.length > 0) {
79
+ fund.assets.push({
80
+ category: assetsHex.slice(0, 64),
81
+ amount: binToBigIntUint64LE(hexToBin(assetsHex.slice(64, 80))),
82
+ });
83
+ assetsHex = assetsHex.slice(80);
84
+ }
85
+
86
+ return fund;
87
+ }
88
+
89
+ export const hashFund = fund => binToHex(hash256(getFundBin(fund)));
90
+
91
+ export function decodeFee(hex) {
92
+ const category = swapEndianness(hex.slice(0, 64));
93
+ const amount = binToBigIntUint64LE(hexToBin(hex.slice(64, 80)));
94
+ if(hex.length > 80) {
95
+ const lockingBytecode = hex.slice(80);
96
+ const destination = lockingBytecodeToCashAddress({ bytecode: hexToBin(lockingBytecode), tokenSupport: true });
97
+ return { category, amount, destination: typeof destination === 'string' ? destination : destination.address };
98
+ }
99
+ return { category, amount };
100
+ }
101
+
102
+ export function encodeFee({ category, amount, destination }) {
103
+ if(!amount) {
104
+ throw new Error('Unable to encode fee, amount is required');
105
+ }
106
+ let encoded = swapEndianness(category ?? '0'.repeat(32 * 2)) + binToHex(bigIntToBinUint64LEClamped(amount));
107
+ if(destination) {
108
+ encoded += binToHex(cashAddressToLockingBytecode(destination).bytecode);
109
+ }
110
+ return encoded;
111
+ }
112
+
113
+ export async function getBestFee({ feeVaultContract, feeContract, payBy, fee }) {
114
+ if(!feeContract) {
115
+ throw new Error('Expected fee contract');
116
+ }
117
+
118
+ const defaultDestination = feeVaultContract.tokenAddress;
119
+
120
+ const {
121
+ nft,
122
+ value: defaultValue,
123
+ } = fee;
124
+ const feeUtxos = (await feeContract.getUtxos())
125
+ .filter(u => {
126
+ if(!u.token) {
127
+ return true;
128
+ } else {
129
+ return u.token.category === nft;
130
+ }
131
+ })
132
+ .map(u => {
133
+ if(!u.token) {
134
+ return {
135
+ isBitcoin: true,
136
+ amount: defaultValue,
137
+ destination: defaultDestination,
138
+ utxo: u,
139
+ };
140
+ }
141
+
142
+ const encodedFee = decodeFee(u.token.nft.commitment);
143
+
144
+ return {
145
+ isBitcoin: encodedFee.category === BitcoinCategory,
146
+ category: encodedFee.category,
147
+ amount: encodedFee.amount,
148
+ destination: encodedFee.destination ?? defaultDestination,
149
+ utxo: u,
150
+ };
151
+ })
152
+ .filter(b => {
153
+ const payByBitcoin = !payBy || payBy === '' || payBy === BitcoinCategory;
154
+ if(payByBitcoin) {
155
+ return b.isBitcoin;
156
+ } else {
157
+ return b.category === payBy;
158
+ }
159
+ })
160
+ .sort((a, b) => {
161
+ return a.amount > b.amount;
162
+ });
163
+
164
+ if(!feeUtxos || !feeUtxos.length) {
165
+ throw new Error('No acceptable fee UTXOs found');
166
+ }
167
+
168
+ const bestFee = feeUtxos[0];
169
+
170
+ const result = {
171
+ isBitcoin: bestFee.isBitcoin,
172
+ category: bestFee.category,
173
+ amount: bestFee.amount,
174
+ destination: bestFee.destination,
175
+ utxo: bestFee.utxo,
176
+ outputs: [withDust({
177
+ ...bestFee.utxo,
178
+ to: feeContract.tokenAddress,
179
+ })],
180
+ };
181
+
182
+ if(result.isBitcoin) {
183
+ result.outputs.push({
184
+ to: result.destination,
185
+ amount: result.amount,
186
+ });
187
+ } else {
188
+ result.outputs.push(withDust({
189
+ to: result.destination,
190
+ token: {
191
+ category: result.category,
192
+ amount: result.amount,
193
+ }
194
+ }));
195
+ }
196
+
197
+ return result;
198
+ }
199
+
200
+ //
201
+ export const getRandomInt = max => Math.floor(Math.random() * max);