UNPKG

@ledgerhq/coin-canton

Version:
175 lines 8.69 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildAuthorizePreapproval = exports.buildOnboardAccount = exports.isCantonCoinPreapproved = exports.isAccountOnboarded = void 0; const errors_1 = require("@ledgerhq/ledger-wallet-framework/errors"); const errors_2 = require("../errors"); const accountId_1 = require("@ledgerhq/ledger-wallet-framework/account/accountId"); const logs_1 = require("@ledgerhq/logs"); const rxjs_1 = require("rxjs"); const sign_1 = require("../common-logic/transaction/sign"); const gateway_1 = require("../network/gateway"); const signer_1 = __importDefault(require("../signer")); const onboard_1 = require("../types/onboard"); const isAccountOnboarded = async (currency, publicKey) => { try { const { party_id } = await (0, gateway_1.getPartyByPubKey)(currency, publicKey); if (party_id) { return { isOnboarded: true, partyId: party_id }; } else { return { isOnboarded: false }; } } catch { return { isOnboarded: false }; } }; exports.isAccountOnboarded = isAccountOnboarded; // Matches the gateway "party already exists" error, e.g. // `Party with id "ldg::1220…" already exists and its topology is up to date`, and captures the id. const PARTY_ALREADY_EXISTS_ID_RE = /party with id\s+"?([^"\s]+)"?\s+already exists/i; const extractExistingPartyId = (error) => error instanceof Error ? (error.message.match(PARTY_ALREADY_EXISTS_ID_RE)?.[1] ?? undefined) : undefined; const isCantonCoinPreapproved = async (currency, partyId) => { const { expires_at, receiver } = await (0, gateway_1.getTransferPreApproval)(currency, partyId); const isReceiver = receiver === partyId; const isExpired = new Date(expires_at) < new Date(); const isPreapproved = !isExpired && isReceiver; return isPreapproved; }; exports.isCantonCoinPreapproved = isCantonCoinPreapproved; const createOnboardedAccount = (account, partyId, currency) => { const cantonAccount = account; return { ...cantonAccount, xpub: partyId, cantonResources: { ...cantonAccount.cantonResources, isOnboarded: true }, id: (0, accountId_1.encodeAccountId)({ type: "js", version: "2", currencyId: currency.id, xpubOrAddress: partyId, derivationMode: account.derivationMode, }), }; }; const buildOnboardAccount = (signerContext) => (currency, deviceId, account) => new rxjs_1.Observable(o => { async function main() { o.next({ status: onboard_1.OnboardStatus.INIT }); const getAddress = (0, signer_1.default)(signerContext); const { publicKey } = await getAddress(deviceId, { path: account.freshAddressPath, currency, derivationMode: account.derivationMode, }); o.next({ status: onboard_1.OnboardStatus.PREPARE }); let { partyId } = await (0, exports.isAccountOnboarded)(currency, publicKey); // Skip submission only if account is onboarded on network but has no local xpub. // For re-onboarding (account has xpub), always proceed to submit a new onboarding transaction. if (partyId && !account.xpub) { const onboardedAccount = createOnboardedAccount(account, partyId, currency); o.next({ partyId, account: onboardedAccount }); // success return; } const preparedTransaction = await (0, gateway_1.prepareOnboarding)(currency, publicKey).catch((error) => { // The gateway can report the party "already exists" while getPartyByPubKey still 404s for // minutes during its by-public-key indexing lag (e.g. re-adding a just-onboarded account). // The party_id is in the error message, so link the existing party directly — instantly, // without waiting for the index — instead of failing or creating a duplicate. LIVE-32985 const existingPartyId = (0, gateway_1.isPartyAlreadyExists)(error) ? extractExistingPartyId(error) : undefined; if (existingPartyId) { o.next({ partyId: existingPartyId, account: createOnboardedAccount(account, existingPartyId, currency), }); // success (linked existing party, no submission) return undefined; } throw error; }); if (!preparedTransaction) { return; // linked an already-existing party above; nothing left to sign/submit } partyId = preparedTransaction.party_id; o.next({ status: onboard_1.OnboardStatus.SIGN }); const signature = await signerContext(deviceId, async (signer) => { return await (0, sign_1.signTransaction)(signer, account.freshAddressPath, preparedTransaction); }); o.next({ status: onboard_1.OnboardStatus.SUBMIT }); await (0, gateway_1.submitOnboarding)(currency, publicKey, preparedTransaction, signature); (0, gateway_1.clearIsTopologyChangeRequiredCache)(currency, publicKey); const onboardedAccount = createOnboardedAccount(account, partyId, currency); o.next({ partyId, account: onboardedAccount }); // success } main().then(() => o.complete(), error => { (0, logs_1.log)("[canton:onboard] onboardAccount failed:", error); const handledError = handleDeviceErrors(error); o.error(handledError || error); }); }); exports.buildOnboardAccount = buildOnboardAccount; const buildAuthorizePreapproval = (signerContext) => (currency, deviceId, account, partyId) => new rxjs_1.Observable(o => { async function main() { o.next({ status: onboard_1.AuthorizeStatus.INIT }); const isPreapproved = await (0, exports.isCantonCoinPreapproved)(currency, partyId); if (!isPreapproved) { o.next({ status: onboard_1.AuthorizeStatus.PREPARE }); const preparedTransaction = await (0, gateway_1.preparePreApprovalTransaction)(currency, partyId); o.next({ status: onboard_1.AuthorizeStatus.SIGN }); const { signature } = await signerContext(deviceId, async (signer) => { return await (0, sign_1.signTransaction)(signer, account.freshAddressPath, preparedTransaction); }); o.next({ status: onboard_1.AuthorizeStatus.SUBMIT }); await (0, gateway_1.submitPreApprovalTransaction)(currency, partyId, preparedTransaction, signature); } o.next({ isApproved: true }); // success if ((0, gateway_1.getNetworkType)(currency) !== "mainnet") { const handleTapRequest = async () => { try { const { serialized, hash } = await (0, gateway_1.prepareTapRequest)(currency, { partyId }); if (serialized && hash) { o.next({ status: onboard_1.AuthorizeStatus.SIGN }); const { signature } = await signerContext(deviceId, signer => signer.signTransaction(account.freshAddressPath, hash)); o.next({ status: onboard_1.AuthorizeStatus.SUBMIT }); await (0, gateway_1.submitTapRequest)(currency, { partyId, serialized, signature, }); } } catch { // Tap request failure should not break the pre-approval flow } }; await handleTapRequest(); } } main().then(() => o.complete(), error => { (0, logs_1.log)("[canton:onboard] authorizePreapproval failed:", error); const handledError = handleDeviceErrors(error); o.error(handledError || error); }); }); exports.buildAuthorizePreapproval = buildAuthorizePreapproval; /** * Check if an error is a LockedDeviceError or UserRefusedOnDevice and create user-friendly error messages */ const handleDeviceErrors = (error) => { const err = error; if (err?.name === "TransportStatusError") { if (err.statusCode === 0x6985) { return new errors_1.UserRefusedOnDevice("errors.UserRefusedOnDevice.description"); } if (err.statusCode === 0x5515) { return new errors_2.LockedDeviceError("errors.LockedDeviceError.description"); } } return null; }; //# sourceMappingURL=onboard.js.map