UNPKG

@renproject/rentx

Version:

XState Statemachines for tracking RenVM transactions reactively

249 lines (245 loc) 10.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.mintConfig = exports.renLockAndMint = void 0; const ren_1 = __importDefault(require("@renproject/ren")); const xstate_1 = require("xstate"); const deposit_1 = require("../machines/deposit"); /* Sample mintChainMap / lockChainMap implementations We don't implement these to prevent mandating specific chains const mintChainMap: { [key in string]: (c: GatewayMachineContext) => MintChain<any>; } = { binanceSmartChain: (context: GatewayMachineContext) => { const { destAddress, destNetwork } = context.tx; const { providers } = context; return new BinanceSmartChain(providers[destNetwork]).Account({ address: destAddress, }) as MintChain<any>; }, ethereum: (context: GatewayMachineContext): MintChain => { const { destAddress, destNetwork } = context.tx; const { providers } = context; return Ethereum(providers[destNetwork]).Account({ address: destAddress, }) as MintChain<any>; }, }; const lockChainMap = { bitcoin: () => Bitcoin(), zcash: () => Zcash(), bitcoinCash: () => BitcoinCash(), }; */ exports.renLockAndMint = (context) => __awaiter(void 0, void 0, void 0, function* () { const { nonce, destNetwork, suggestedAmount, sourceNetwork, sourceAsset, } = context.tx; const { sdk, fromChainMap, toChainMap } = context; const mint = yield sdk.lockAndMint({ asset: sourceAsset.toUpperCase(), suggestedAmount, from: fromChainMap[sourceNetwork](context), to: toChainMap[destNetwork](context), nonce, }); return mint; }); // Format a transaction and get the gateway address const txCreator = (context) => __awaiter(void 0, void 0, void 0, function* () { // TX may be in a state where the gateway address was provided, // but no deposit was picked up if (!context.tx.nonce) { context.tx.nonce = ren_1.default.utils.randomNonce().toString("hex"); } const { targetAmount, sourceAsset } = context.tx; try { const fees = yield context.sdk.getFees(); context.tx.suggestedAmount = Math.floor(fees[sourceAsset.toLowerCase()].lock + (Number(targetAmount) || 0.0001) * 1e8); } catch (error) { console.error(error); context.tx.suggestedAmount = 0.0008 * 1e8; } const minter = yield exports.renLockAndMint(context); const gatewayAddress = minter === null || minter === void 0 ? void 0 : minter.gatewayAddress; const newTx = Object.assign(Object.assign({}, context.tx), { gatewayAddress }); return newTx; }); // Listen for confirmations on the source chain const depositListener = (context) => (callback, receive) => { let cleanup = () => { }; exports.renLockAndMint(context).then((minter) => __awaiter(void 0, void 0, void 0, function* () { cleanup = () => minter.removeAllListeners(); minter.on("deposit", (deposit) => __awaiter(void 0, void 0, void 0, function* () { // Register event handlers prior to setup in case events land early receive((event) => { switch (event.type) { case "SETTLE": deposit .confirmed() .on("confirmation", (confs, targetConfs) => { const confirmedTx = { sourceTxConfs: confs, sourceTxConfTarget: targetConfs, }; callback({ type: "CONFIRMATION", data: confirmedTx, }); }) .then(() => { callback({ type: "CONFIRMED", }); }); break; case "SIGN": deposit === null || deposit === void 0 ? void 0 : deposit.signed().on("status", (state) => console.log(state)).then((v) => { var _a, _b, _c; return callback({ type: "SIGNED", data: { renResponse: (_a = v._queryTxResult) === null || _a === void 0 ? void 0 : _a.out, signature: (_c = (_b = v._queryTxResult) === null || _b === void 0 ? void 0 : _b.out) === null || _c === void 0 ? void 0 : _c.signature, }, }); }).catch((e) => callback({ type: "SIGN_ERROR", data: e })); break; case "MINT": deposit === null || deposit === void 0 ? void 0 : deposit.mint().on("transactionHash", (txHash) => { const submittedTx = { destTxHash: txHash, }; callback({ type: "SUBMITTED", data: submittedTx, }); }).catch((e) => callback({ type: "SUBMIT_ERROR", data: e })); break; } }); const txHash = yield deposit.txHash(); const persistedTx = context.tx.transactions[txHash]; // Prevent deposit machine tx listeners from interacting with other deposits const targetDeposit = context.deposit; if (targetDeposit) { if (targetDeposit.sourceTxHash !== txHash) { console.error("wrong deposit:", targetDeposit.sourceTxHash, txHash); return; } } // If we don't have a sourceTxHash, we haven't seen a deposit yet const rawSourceTx = deposit.depositDetails.transaction; const depositState = persistedTx || { sourceTxHash: txHash, sourceTxAmount: rawSourceTx.amount, sourceTxVOut: rawSourceTx.vOut, rawSourceTx, }; if (!persistedTx) { callback({ type: "DEPOSIT", data: Object.assign({}, depositState), }); } else { callback("DETECTED"); } })); receive((event) => { switch (event.type) { case "RESTORE": try { minter.processDeposit(event.data); } catch (e) { console.error(e); } break; } }); callback("LISTENING"); })); return () => { cleanup(); }; }; // Spawn an actor that will listen for either all deposits to a gatewayAddress, // or to a single deposit if present in the context const listenerAction = xstate_1.assign({ depositListenerRef: (c, _e) => { let actorName = `${c.tx.id}SessionListener`; const deposit = c.deposit; if (deposit) { actorName = `${deposit.sourceTxHash}DepositListener`; } if (c.depositListenerRef && !deposit) { console.warn("listener already exists"); return c.depositListenerRef; } const cb = depositListener(c); return xstate_1.spawn(cb, actorName); }, }); const spawnDepositMachine = (machineContext, name) => xstate_1.spawn(deposit_1.depositMachine .withContext(machineContext) .withConfig({ actions: { listenerAction: listenerAction, }, }), { sync: true, name, }); exports.mintConfig = { services: { txCreator, depositListener, }, actions: { spawnDepositMachine: xstate_1.assign({ depositMachines: (context, evt) => { var _a; const machines = context.depositMachines || {}; if (machines[(_a = evt.data) === null || _a === void 0 ? void 0 : _a.sourceTxHash] || !evt.data) { return machines; } const machineContext = Object.assign(Object.assign({}, context), { deposit: evt.data }); // We don't want child machines to have references to siblings delete machineContext.depositMachines; machines[evt.data.sourceTxHash] = spawnDepositMachine(machineContext, `${evt.data.sourceTxHash}DepositMachine`); return machines; }, }), depositMachineSpawner: xstate_1.assign({ depositMachines: (context, _) => { const machines = context.depositMachines || {}; for (let i of Object.entries(context.tx.transactions || {})) { const machineContext = Object.assign(Object.assign({}, context), { deposit: i[1] }); // We don't want child machines to have references to siblings delete machineContext.depositMachines; machines[i[0]] = spawnDepositMachine(machineContext, `${machineContext.deposit.sourceTxHash}DepositMachine`); } return machines; }, }), listenerAction: listenerAction, }, guards: { isCompleted: ({ tx }, evt) => { var _a; return ((_a = evt.data) === null || _a === void 0 ? void 0 : _a.sourceTxAmount) >= tx.targetAmount; }, isExpired: ({ tx }) => tx.expiryTime < new Date().getTime(), isCreated: ({ tx }) => (tx.gatewayAddress ? true : false), }, };