@ledgerhq/coin-canton
Version:
337 lines • 13.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCalTokensCached = exports.getEnabledInstrumentsCached = exports.isTopologyChangeRequiredCached = exports.isPartyAlreadyExists = exports.isPartyNotFound = exports.getNetworkType = exports.getKey = exports.DEFAULT_TAP_REQUEST_AMOUNT = exports.SEPARATOR = void 0;
exports.isGatewayEnabled = isGatewayEnabled;
exports.getPartyById = getPartyById;
exports.getPartyByPubKey = getPartyByPubKey;
exports.prepareOnboarding = prepareOnboarding;
exports.isTopologyChangeRequired = isTopologyChangeRequired;
exports.clearIsTopologyChangeRequiredCache = clearIsTopologyChangeRequiredCache;
exports.getEnabledInstruments = getEnabledInstruments;
exports.clearEnabledInstrumentsCache = clearEnabledInstrumentsCache;
exports.submitOnboarding = submitOnboarding;
exports.getBalance = getBalance;
exports.getOperations = getOperations;
exports.getPendingTransferProposals = getPendingTransferProposals;
exports.getTransferPreApproval = getTransferPreApproval;
exports.getLedgerEnd = getLedgerEnd;
exports.prepare = prepare;
exports.prepareTransferRequest = prepareTransferRequest;
exports.prepareTransferInstruction = prepareTransferInstruction;
exports.preparePreApprovalTransaction = preparePreApprovalTransaction;
exports.prepareTapRequest = prepareTapRequest;
exports.submit = submit;
exports.submitTransferInstruction = submitTransferInstruction;
exports.submitPreApprovalTransaction = submitPreApprovalTransaction;
exports.submitTapRequest = submitTapRequest;
const live_env_1 = require("@ledgerhq/live-env");
const live_network_1 = __importDefault(require("@ledgerhq/live-network"));
const cache_1 = require("@ledgerhq/live-network/cache");
const config_1 = __importDefault(require("../config"));
const errors_1 = require("../types/errors");
const gateway_1 = require("../types/gateway");
exports.SEPARATOR = "____";
exports.DEFAULT_TAP_REQUEST_AMOUNT = "100000000000000000000000000000000000000";
const getKey = (id, adminId) => `${id}${exports.SEPARATOR}${adminId}`;
exports.getKey = getKey;
const getGatewayUrl = (currency) => config_1.default.getCoinConfig(currency.id).gatewayUrl;
const getNodeId = (currency) => {
const overrideNodeId = (0, live_env_1.getEnv)("CANTON_NODE_ID_OVERRIDE");
if (overrideNodeId) {
return overrideNodeId;
}
return config_1.default.getCoinConfig(currency.id).nodeId || "ledger-live-devnet";
};
const getNetworkType = (currency) => config_1.default.getCoinConfig(currency.id).networkType;
exports.getNetworkType = getNetworkType;
function isGatewayEnabled(currency) {
return config_1.default.getCoinConfig(currency.id).useGateway === true;
}
const isPartyNotFound = (error) => {
if (error instanceof Error) {
const errorMessage = error.message.toLowerCase().replace(/_/g, " ");
return errorMessage.includes("party") && errorMessage.includes("not found");
}
return false;
};
exports.isPartyNotFound = isPartyNotFound;
const isPartyAlreadyExists = (error) => {
if (error instanceof Error) {
const errorMessage = error.message.toLowerCase().replace(/_/g, " ");
return errorMessage.includes("party") && errorMessage.includes("already exists");
}
return false;
};
exports.isPartyAlreadyExists = isPartyAlreadyExists;
const gatewayNetwork = async (req) => {
const API_KEY = (0, live_env_1.getEnv)("CANTON_API_KEY");
try {
return await (0, live_network_1.default)({
...req,
headers: {
...(req.headers || {}),
...(API_KEY ? { "X-Ledger-Canton-Api-Key": API_KEY } : {}),
},
});
}
catch (error) {
if ((0, exports.isPartyNotFound)(error)) {
throw new errors_1.TopologyChangeError("Topology change detected. Re-onboarding required.");
}
throw error;
}
};
/*
* Parties
* @see https://canton-gateway.api.live.ledger.com/docs/openapi/redoc/index.html#tag/Parties
*/
async function getPartyById(currency, partyId) {
return await getParty(currency, partyId, "party-id");
}
async function getPartyByPubKey(currency, pubKey) {
return await getParty(currency, pubKey, "public-key");
}
async function getParty(currency, identifier, by) {
const { data } = await gatewayNetwork({
method: "GET",
url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/party/${identifier}?by=${by}`,
});
return data;
}
async function prepareOnboarding(currency, pubKey) {
const gatewayUrl = getGatewayUrl(currency);
const nodeId = getNodeId(currency);
const fullUrl = `${gatewayUrl}/v1/node/${nodeId}/onboarding/prepare`;
const { data } = await gatewayNetwork({
method: "POST",
url: fullUrl,
data: {
public_key: pubKey,
public_key_type: "ed25519",
},
});
return data;
}
async function isTopologyChangeRequired(currency, pubKey) {
try {
const response = await prepareOnboarding(currency, pubKey);
// if response is not undefined (we have a transaction to sign) topology change is required
if (response) {
return true;
}
return false;
}
catch (error) {
if ((0, exports.isPartyAlreadyExists)(error)) {
return false;
}
throw error;
}
}
const getIsTopologyChangeRequiredCacheKey = (currency, pubKey) => {
const nodeId = getNodeId(currency);
return `${pubKey}_${nodeId}`;
};
exports.isTopologyChangeRequiredCached = (0, cache_1.makeLRUCache)(isTopologyChangeRequired, getIsTopologyChangeRequiredCacheKey, (0, cache_1.minutes)(10));
function clearIsTopologyChangeRequiredCache(currency, pubKey) {
const cacheKey = getIsTopologyChangeRequiredCacheKey(currency, pubKey);
exports.isTopologyChangeRequiredCached.clear(cacheKey);
}
async function getEnabledInstruments(currency) {
try {
const { data } = await gatewayNetwork({
method: "GET",
url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/instruments`,
});
return new Set(data.map(({ id, admin }) => (0, exports.getKey)(id, admin)));
}
catch (error) {
// If API fails, return empty array (fail-safe: only native instrument will work)
console.error("Failed to fetch enabled instruments:", error);
return new Set();
}
}
const getEnabledInstrumentsCacheKey = (currency) => {
const nodeId = getNodeId(currency);
return `instruments_${nodeId}`;
};
exports.getEnabledInstrumentsCached = (0, cache_1.makeLRUCache)(getEnabledInstruments, getEnabledInstrumentsCacheKey, (0, cache_1.minutes)(15));
function clearEnabledInstrumentsCache(currency) {
const cacheKey = getEnabledInstrumentsCacheKey(currency);
exports.getEnabledInstrumentsCached.clear(cacheKey);
}
async function submitOnboarding(currency, publicKey, prepareResponse, { signature, applicationSignature }) {
try {
const { data } = await gatewayNetwork({
method: "POST",
url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/onboarding/submit`,
data: {
prepare_request: {
public_key: publicKey,
public_key_type: "ed25519",
},
prepare_response: prepareResponse,
signature,
...(applicationSignature ? { application_signature: applicationSignature } : {}),
},
});
return data;
}
catch (error) {
if ((0, exports.isPartyAlreadyExists)(error)) {
// If party already exists, use party_id from prepare response
// The network layer strips custom properties from errors, so we can't extract partyId from error
return {
party: {
party_id: prepareResponse.party_id,
public_key: publicKey,
},
};
}
throw error;
}
}
/*
* State
* @see https://canton-gateway.api.live.ledger.com/docs/openapi/redoc/index.html#tag/State
*/
async function getBalance(currency, partyId) {
const { data } = await gatewayNetwork({
method: "GET",
url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/party/${partyId}/balance`,
});
return Array.isArray(data) ? data : (data.balances ?? []);
}
async function getOperations(currency, partyId, options) {
const { data } = await gatewayNetwork({
method: "GET",
url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/party/${partyId}/operations`,
params: options,
});
return data;
}
async function getPendingTransferProposals(currency, partyId) {
const { data } = await gatewayNetwork({
method: "GET",
url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/party/${partyId}/transfer-proposals?timestamp=${Date.now()}`,
});
return data;
}
async function getTransferPreApproval(currency, partyId) {
const { data } = await gatewayNetwork({
method: "GET",
url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/party/${partyId}/transfer-preapproval`,
});
return data;
}
async function getLedgerEnd(currency) {
const { data } = await gatewayNetwork({
method: "GET",
url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/ledger-end`,
});
return data;
}
/*
* Transaction
* @see https://canton-gateway.api.live.ledger.com/docs/openapi/redoc/index.html#tag/Transaction
*/
async function prepare(currency, partyId, params) {
const { data } = await gatewayNetwork({
method: "POST",
url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/party/${partyId}/transaction/prepare`,
data: params,
});
return data;
}
async function prepareTransferRequest(currency, partyId, params) {
return prepare(currency, partyId, params);
}
async function prepareTransferInstruction(currency, partyId, params) {
return prepare(currency, partyId, params);
}
async function preparePreApprovalTransaction(currency, partyId) {
const { data } = await gatewayNetwork({
method: "POST",
url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/party/${partyId}/transaction/prepare`,
data: {
type: gateway_1.TransactionType.TRANSFER_PRE_APPROVAL_PROPOSAL,
receiver: partyId,
},
});
return data;
}
async function prepareTapRequest(currency, { partyId, amount }) {
// Default to 1.0 in fixed-point representation (1 * 10^38)
const fixedPointAmount = amount ?? exports.DEFAULT_TAP_REQUEST_AMOUNT;
const { data } = await gatewayNetwork({
method: "POST",
url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/party/${partyId}/transaction/prepare`,
data: {
amount: fixedPointAmount.toString(),
type: gateway_1.TransactionType.TAP_REQUEST,
},
});
return data;
}
async function submit(currency, partyId, serialized, signature) {
const { data } = await gatewayNetwork({
method: "POST",
url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/party/${partyId}/transaction/submit`,
data: {
serialized,
signature,
},
});
return data;
}
async function submitTransferInstruction(currency, partyId, serialized, signature) {
return submit(currency, partyId, serialized, signature);
}
async function submitPreApprovalTransaction(currency, partyId, { serialized }, signature) {
const { data } = await gatewayNetwork({
method: "POST",
url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/party/${partyId}/transaction/submit`,
data: {
serialized,
signature,
},
});
return {
isApproved: true,
submissionId: data.submission_id,
updateId: data.update_id,
};
}
async function submitTapRequest(currency, { partyId, serialized, signature }) {
const { data } = await gatewayNetwork({
method: "POST",
url: `${getGatewayUrl(currency)}/v1/node/${getNodeId(currency)}/party/${partyId}/transaction/submit`,
data: {
serialized,
signature,
},
});
return data;
}
/**
* Fetch Canton tokens from CAL service and create a map of id -> token_identifier
*/
async function getCalTokens(currency) {
const calUrl = (0, live_env_1.getEnv)("CAL_SERVICE_URL");
const { data: calTokens } = await gatewayNetwork({
method: "GET",
url: `${calUrl}/v1/tokens?network=${currency.id}&output=id,name,ticker,network,contract_address,token_identifier,units,standard`,
});
// Map id -> token_identifier
const tokenIdentifierMap = new Map();
for (const token of calTokens) {
tokenIdentifierMap.set(token.id, token.token_identifier);
}
return tokenIdentifierMap;
}
const getCalTokensCacheKey = (currency) => currency.id;
exports.getCalTokensCached = (0, cache_1.makeLRUCache)(getCalTokens, getCalTokensCacheKey, (0, cache_1.minutes)(30));
//# sourceMappingURL=gateway.js.map