@saberhq/sail
Version:
Account caching and batched loading for React-based Solana applications.
175 lines • 7.93 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.useHandleTXsInternal = void 0;
const tslib_1 = require("tslib");
const solana_contrib_1 = require("@saberhq/solana-contrib");
const use_solana_1 = require("@saberhq/use-solana");
const react_1 = require("react");
const tiny_invariant_1 = tslib_1.__importDefault(require("tiny-invariant"));
const __1 = require("..");
const errors_1 = require("../errors");
const DEBUG_MODE = !!process.env.REACT_APP_LOCAL_PUBKEY ||
!!process.env.LOCAL_PUBKEY ||
!!process.env.DEBUG_MODE;
/**
* Generates a random identifier for a handled transaction.
* @returns string
*/
const genRandomBundleID = () => `bundle-${Math.random()}`;
const useHandleTXsInternal = ({ refetchMany, onBeforeTxSend, onTxSend, onError, txRefetchDelayMs = 1000, waitForConfirmation = false, }) => {
const { network } = (0, use_solana_1.useSolana)();
const handleTXs = (0, react_1.useCallback)(async (txs, message, options) => {
var _a, _b;
if (txs.length === 0) {
return {
success: true,
pending: [],
};
}
if ((_a = options === null || options === void 0 ? void 0 : options.debugMode) !== null && _a !== void 0 ? _a : DEBUG_MODE) {
const txTable = await Promise.all(txs.map(async (tx) => {
return await tx.simulateTable({ verifySigners: false, ...options });
}));
txs.forEach((tx, i) => {
const table = txTable[i];
if (network !== "localnet") {
console.debug(tx.generateInspectLink(network));
}
console.debug(table);
});
}
const bundleID = genRandomBundleID();
onBeforeTxSend === null || onBeforeTxSend === void 0 ? void 0 : onBeforeTxSend({ bundleID, network, txs, message });
try {
const firstTX = txs[0];
(0, tiny_invariant_1.default)(firstTX, "firstTX");
const provider = firstTX.provider;
// TODO(igm): when we support other accounts being the payer,
// we need to alter this check
const [nativeAccount] = await refetchMany([provider.wallet.publicKey]);
const nativeBalance = (0, solana_contrib_1.mapN)((nativeAccount) => "data" in nativeAccount ? nativeAccount.lamports : null, nativeAccount);
if (!nativeBalance) {
const error = new errors_1.InsufficientSOLError(nativeBalance);
onError(error);
return {
success: false,
pending: [],
errors: [error],
};
}
let signedTXs;
try {
signedTXs = await provider.signer.signAll(txs.map((tx) => ({ tx: tx.build(), signers: tx.signers })), options);
}
catch (e) {
const fail = new errors_1.SailTransactionSignError(e, txs);
onError(fail);
return {
success: false,
pending: [],
errors: [fail],
};
}
const errors = [];
const maybePending = await Promise.all(signedTXs.map(async (signedTX, i) => {
try {
return await provider.broadcaster.broadcast(signedTX, options);
}
catch (e) {
const txEnvelope = txs[i];
if (!txEnvelope) {
// should be impossible
throw new Error(`Unknown TX: ${i} of ${txs.length}`);
}
const txError = new errors_1.SailTransactionError(network, e, txEnvelope, message);
console.error(`Error sending TX ${i}: ${txError.message}`);
console.debug(txError.generateLogMessage());
errors.push(txError);
onError(txError);
return null;
}
}));
// if any TXs could not send, do not continue.
const pending = maybePending.filter((p) => !!p);
if (errors.length > 0) {
// don't throw anything here because we already threw the errors above
return {
success: false,
pending,
errors,
};
}
// get the unique writable keys for every transaction
const writable = (0, __1.uniqKeys)(txs.flatMap((tx) => tx.writableKeys));
// refetch everything
void (async () => {
var _a;
const refetchAfterTX = options === null || options === void 0 ? void 0 : options.refetchAfterTX;
try {
// wait for the tx to be confirmed
// it is possible that it never gets
await Promise.all(pending.map((p) => p
.wait({
commitment: "confirmed",
minTimeout: 1000,
...refetchAfterTX,
})
.catch((err) => {
var _a;
throw new Error(`Could not await confirmation of transaction ${p.signature}: ${(_a = (0, errors_1.extractErrorMessage)(err)) !== null && _a !== void 0 ? _a : "unknown"}`);
})));
// then fetch, after a delay
setTimeout(() => {
void refetchMany(writable).catch((e) => {
onError(new errors_1.SailRefetchAfterTXError(e, writable, pending));
});
}, (_a = refetchAfterTX === null || refetchAfterTX === void 0 ? void 0 : refetchAfterTX.refetchDelayMs) !== null && _a !== void 0 ? _a : txRefetchDelayMs);
}
catch (e) {
onError(new errors_1.SailRefetchAfterTXError(e, writable, pending));
}
})();
onTxSend === null || onTxSend === void 0 ? void 0 : onTxSend({ bundleID, network, txs, pending, message });
if (waitForConfirmation) {
// await for the tx to be confirmed
await Promise.all(pending.map((p) => p.wait()));
}
return {
success: true,
pending,
};
}
catch (e) {
// Log the instruction logs
console.error("Transaction failed.", e);
txs.forEach((tx, i) => {
if (txs.length > 1) {
console.debug(`TX #${i + 1} of ${txs.length}`);
}
console.debug(tx.debugStr);
if (network !== "localnet") {
console.debug(`View on Solana Explorer: ${tx.generateInspectLink(network)}`);
}
});
const sailError = (_b = errors_1.SailError.tryInto(e)) !== null && _b !== void 0 ? _b : new errors_1.SailUnknownTXFailError(e, network, txs);
onError(sailError);
return { success: false, pending: [], errors: [sailError] };
}
}, [
network,
onBeforeTxSend,
onError,
onTxSend,
refetchMany,
txRefetchDelayMs,
waitForConfirmation,
]);
const handleTX = (0, react_1.useCallback)(async (txEnv, message, options) => {
var _a;
const { success, pending, errors } = await handleTXs([txEnv], message, options);
return { success, pending: (_a = pending[0]) !== null && _a !== void 0 ? _a : null, errors };
}, [handleTXs]);
return { handleTX, handleTXs };
};
exports.useHandleTXsInternal = useHandleTXsInternal;
//# sourceMappingURL=useHandleTXs.js.map
;