@renproject/rentx
Version:
XState Statemachines for tracking RenVM transactions reactively
249 lines (245 loc) • 10.7 kB
JavaScript
;
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),
},
};