@ledgerhq/coin-canton
Version:
175 lines • 8.69 kB
JavaScript
;
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