|
1 | | -import { Hash, EventRecord } from "@polkadot/types/interfaces"; |
2 | | -import { ApiTypes, AugmentedEvent } from "@polkadot/api/types"; |
3 | | -import type { AnyTuple } from "@polkadot/types/types"; |
4 | | -import { ApiPromise } from "@polkadot/api"; |
5 | | -import { KeyringPair } from "@polkadot/keyring/types"; |
6 | | -import { Bitcoin, BitcoinAmount, InterBtcAmount, MonetaryAmount } from "@interlay/monetary-js"; |
| 1 | +import { MonetaryAmount } from "@interlay/monetary-js"; |
7 | 2 | import { InterbtcPrimitivesVaultId } from "@polkadot/types/lookup"; |
8 | | -import { ISubmittableResult } from "@polkadot/types/types"; |
9 | 3 |
|
10 | | -import { newAccountId } from "../utils"; |
11 | | -import { BitcoinCoreClient } from "./bitcoin-core-client"; |
12 | | -import { stripHexPrefix } from "../utils/encoding"; |
13 | | -import { Issue, IssueStatus, Redeem, RedeemStatus, WrappedCurrency } from "../types"; |
14 | | -import { waitForBlockFinalization } from "./bitcoin"; |
15 | | -import { atomicToBaseAmount, currencyIdToMonetaryCurrency, newMonetaryAmount } from "./currency"; |
16 | | -import { InterBtcApi } from "../interbtc-api"; |
17 | | -import { sleep, SLEEP_TIME_MS } from "../utils"; |
18 | | - |
19 | | -export interface IssueResult { |
20 | | - request: Issue; |
21 | | - initialWrappedTokenBalance: MonetaryAmount<WrappedCurrency>; |
22 | | - finalWrappedTokenBalance: MonetaryAmount<WrappedCurrency>; |
23 | | -} |
24 | | - |
25 | | -export enum ExecuteRedeem { |
26 | | - False, |
27 | | - Manually, |
28 | | - Auto, |
29 | | -} |
30 | | - |
31 | | -/** |
32 | | - * @param events The EventRecord array returned after sending a transaction |
33 | | - * @param methodToCheck The name of the event method whose existence to check |
34 | | - * @returns The id associated with the transaction. If the EventRecord array does not |
35 | | - * contain required events, the function throws an error. |
36 | | - */ |
37 | | -export function getRequestIdsFromEvents( |
38 | | - events: EventRecord[], |
39 | | - eventToFind: AugmentedEvent<ApiTypes, AnyTuple>, |
40 | | - api: ApiPromise |
41 | | -): Hash[] { |
42 | | - const ids = new Array<Hash>(); |
43 | | - for (const { event } of events) { |
44 | | - if (eventToFind.is(event)) { |
45 | | - // the redeem id has type H256 and is the first item of the event data array |
46 | | - const id = api.createType("Hash", event.data[0]); |
47 | | - ids.push(id); |
48 | | - } |
49 | | - } |
50 | | - |
51 | | - if (ids.length > 0) return ids; |
52 | | - throw new Error("Transaction failed"); |
53 | | -} |
54 | | - |
55 | | -export const getIssueRequestsFromExtrinsicResult = async ( |
56 | | - interBtcApi: InterBtcApi, |
57 | | - result: ISubmittableResult |
58 | | -): Promise<Array<Issue>> => { |
59 | | - const ids = getRequestIdsFromEvents(result.events, interBtcApi.api.events.issue.RequestIssue, interBtcApi.api); |
60 | | - const issueRequests = await interBtcApi.issue.getRequestsByIds(ids); |
61 | | - return issueRequests; |
62 | | -}; |
63 | | - |
64 | | -export const getRedeemRequestsFromExtrinsicResult = async ( |
65 | | - interBtcApi: InterBtcApi, |
66 | | - result: ISubmittableResult |
67 | | -): Promise<Array<Redeem>> => { |
68 | | - const ids = getRequestIdsFromEvents(result.events, interBtcApi.api.events.redeem.RequestRedeem, interBtcApi.api); |
69 | | - const redeemRequests = await interBtcApi.redeem.getRequestsByIds(ids); |
70 | | - return redeemRequests; |
71 | | -}; |
| 4 | +import { WrappedCurrency } from "../types"; |
| 5 | +import { newMonetaryAmount } from "./currency"; |
72 | 6 |
|
73 | 7 | /** |
74 | 8 | * Given a list of vaults with availabilities (e.g. collateral for issue, tokens |
@@ -117,172 +51,3 @@ export function allocateAmountsToVaults( |
117 | 51 | } |
118 | 52 | return allocations; |
119 | 53 | } |
120 | | - |
121 | | -export async function issueSingle( |
122 | | - interBtcApi: InterBtcApi, |
123 | | - bitcoinCoreClient: BitcoinCoreClient, |
124 | | - issuingAccount: KeyringPair, |
125 | | - amount: MonetaryAmount<WrappedCurrency>, |
126 | | - vaultId?: InterbtcPrimitivesVaultId, |
127 | | - autoExecute = true, |
128 | | - triggerRefund = false, |
129 | | - atomic = true |
130 | | -): Promise<IssueResult> { |
131 | | - const prevAccount = interBtcApi.account; |
132 | | - interBtcApi.setAccount(issuingAccount); |
133 | | - try { |
134 | | - const requesterAccountId = newAccountId(interBtcApi.api, issuingAccount.address); |
135 | | - const initialWrappedTokenBalance = (await interBtcApi.tokens.balance(amount.currency, requesterAccountId)).free; |
136 | | - const blocksToMine = 3; |
137 | | - |
138 | | - const collateralCurrency = vaultId |
139 | | - ? await currencyIdToMonetaryCurrency(interBtcApi.api, vaultId.currencies.collateral) |
140 | | - : undefined; |
141 | | - const { extrinsic, event } = await interBtcApi.issue.request( |
142 | | - amount, |
143 | | - vaultId?.accountId, |
144 | | - collateralCurrency, |
145 | | - atomic |
146 | | - ); |
147 | | - |
148 | | - const result = await interBtcApi.transaction.sendLogged(extrinsic, event); |
149 | | - const issueRequests = await getIssueRequestsFromExtrinsicResult(interBtcApi, result); |
150 | | - if (issueRequests.length !== 1) { |
151 | | - throw new Error("More than one issue request created"); |
152 | | - } |
153 | | - const issueRequest = issueRequests[0]; |
154 | | - |
155 | | - let amountAsBtc = issueRequest.wrappedAmount.add(issueRequest.bridgeFee); |
156 | | - if (triggerRefund) { |
157 | | - // Send 1 more Btc than needed |
158 | | - amountAsBtc = amountAsBtc.add(new BitcoinAmount(1)); |
159 | | - } else if (autoExecute === false) { |
160 | | - // Send 1 less Satoshi than requested |
161 | | - // to trigger the user failsafe and disable auto-execution. |
162 | | - const oneSatoshiInBtc = atomicToBaseAmount(1, Bitcoin); |
163 | | - const oneSatoshi = new BitcoinAmount(oneSatoshiInBtc); |
164 | | - amountAsBtc = amountAsBtc.sub(oneSatoshi); |
165 | | - } |
166 | | - |
167 | | - // send btc tx |
168 | | - const vaultBtcAddress = issueRequest.vaultWrappedAddress; |
169 | | - if (vaultBtcAddress === undefined) { |
170 | | - throw new Error("Undefined vault address returned from RequestIssue"); |
171 | | - } |
172 | | - |
173 | | - const txData = await bitcoinCoreClient.sendBtcTxAndMine(vaultBtcAddress, amountAsBtc, blocksToMine); |
174 | | - |
175 | | - if (autoExecute === false) { |
176 | | - console.log("Manually executing, waiting for relay to catchup"); |
177 | | - await waitForBlockFinalization(bitcoinCoreClient, interBtcApi.btcRelay); |
178 | | - console.log("Block successfully relayed"); |
179 | | - await interBtcApi.electrsAPI.waitForTxInclusion(txData.txid, SLEEP_TIME_MS * 10, SLEEP_TIME_MS); |
180 | | - console.log("Transaction included in electrs"); |
181 | | - // execute issue, assuming the selected vault has the `--no-issue-execution` flag enabled |
182 | | - const { extrinsic: executeExtrinsic, event: executeEvent } = await interBtcApi.issue.execute( |
183 | | - issueRequest.id, |
184 | | - txData.txid |
185 | | - ); |
186 | | - await interBtcApi.transaction.sendLogged(executeExtrinsic, executeEvent); |
187 | | - } else { |
188 | | - console.log("Auto-executing, waiting for vault to submit proof"); |
189 | | - // wait for vault to execute issue |
190 | | - while ((await interBtcApi.issue.getRequestById(issueRequest.id)).status !== IssueStatus.Completed) { |
191 | | - await sleep(SLEEP_TIME_MS); |
192 | | - } |
193 | | - } |
194 | | - |
195 | | - const finalWrappedTokenBalance = (await interBtcApi.tokens.balance(amount.currency, requesterAccountId)).free; |
196 | | - return { |
197 | | - request: issueRequest, |
198 | | - initialWrappedTokenBalance, |
199 | | - finalWrappedTokenBalance, |
200 | | - }; |
201 | | - } catch (e) { |
202 | | - // IssueCompleted errors occur when multiple vaults attempt to execute the same request |
203 | | - return Promise.reject(new Error(`Issuing failed: ${e}`)); |
204 | | - } finally { |
205 | | - if (prevAccount) { |
206 | | - interBtcApi.setAccount(prevAccount); |
207 | | - } |
208 | | - } |
209 | | -} |
210 | | - |
211 | | -export async function redeem( |
212 | | - interBtcApi: InterBtcApi, |
213 | | - bitcoinCoreClient: BitcoinCoreClient, |
214 | | - redeemingAccount: KeyringPair, |
215 | | - amount: MonetaryAmount<WrappedCurrency>, |
216 | | - vaultId?: InterbtcPrimitivesVaultId, |
217 | | - autoExecute = ExecuteRedeem.Auto, |
218 | | - atomic = true, |
219 | | - timeout = 5 * 60 * 1000 |
220 | | -): Promise<Redeem> { |
221 | | - const prevAccount = interBtcApi.account; |
222 | | - interBtcApi.setAccount(redeemingAccount); |
223 | | - const btcAddress = "bcrt1qujs29q4gkyn2uj6y570xl460p4y43ruayxu8ry"; |
224 | | - const { extrinsic, event } = await interBtcApi.redeem.request(amount, btcAddress, vaultId, atomic); |
225 | | - const result = await interBtcApi.transaction.sendLogged(extrinsic, event); |
226 | | - const [redeemRequest] = await getRedeemRequestsFromExtrinsicResult(interBtcApi, result); |
227 | | - |
228 | | - switch (autoExecute) { |
229 | | - case ExecuteRedeem.Manually: { |
230 | | - const opreturnData = stripHexPrefix(redeemRequest.id.toString()); |
231 | | - const btcTxId = await interBtcApi.electrsAPI.waitForOpreturn(opreturnData, timeout, 5000).catch((_) => { |
232 | | - throw new Error("Redeem request was not executed, timeout expired"); |
233 | | - }); |
234 | | - // Even if the tx was found, the block needs to be relayed to the parachain before `execute` can be called. |
235 | | - await waitForBlockFinalization(bitcoinCoreClient, interBtcApi.btcRelay); |
236 | | - |
237 | | - // manually execute issue |
238 | | - await interBtcApi.redeem.execute(redeemRequest.id.toString(), btcTxId); |
239 | | - break; |
240 | | - } |
241 | | - case ExecuteRedeem.Auto: { |
242 | | - // wait for vault to execute issue |
243 | | - while ((await interBtcApi.redeem.getRequestById(redeemRequest.id)).status !== RedeemStatus.Completed) { |
244 | | - await sleep(SLEEP_TIME_MS); |
245 | | - } |
246 | | - break; |
247 | | - } |
248 | | - } |
249 | | - if (prevAccount) { |
250 | | - interBtcApi.setAccount(prevAccount); |
251 | | - } |
252 | | - return redeemRequest; |
253 | | -} |
254 | | - |
255 | | -export async function issueAndRedeem( |
256 | | - InterBtcApi: InterBtcApi, |
257 | | - bitcoinCoreClient: BitcoinCoreClient, |
258 | | - account: KeyringPair, |
259 | | - vaultId?: InterbtcPrimitivesVaultId, |
260 | | - issueAmount: MonetaryAmount<WrappedCurrency> = new InterBtcAmount(0.1), |
261 | | - redeemAmount: MonetaryAmount<WrappedCurrency> = new InterBtcAmount(0.009), |
262 | | - autoExecuteIssue = true, |
263 | | - autoExecuteRedeem = ExecuteRedeem.Auto, |
264 | | - triggerRefund = false, |
265 | | - atomic = true |
266 | | -): Promise<[Issue, Redeem]> { |
267 | | - const issueResult = await issueSingle( |
268 | | - InterBtcApi, |
269 | | - bitcoinCoreClient, |
270 | | - account, |
271 | | - issueAmount, |
272 | | - vaultId, |
273 | | - autoExecuteIssue, |
274 | | - triggerRefund, |
275 | | - atomic |
276 | | - ); |
277 | | - |
278 | | - const redeemRequest = await redeem( |
279 | | - InterBtcApi, |
280 | | - bitcoinCoreClient, |
281 | | - account, |
282 | | - redeemAmount, |
283 | | - issueResult.request.vaultId, |
284 | | - autoExecuteRedeem, |
285 | | - atomic |
286 | | - ); |
287 | | - return [issueResult.request, redeemRequest]; |
288 | | -} |
0 commit comments