UNPKG

@funded-labs/plug-controller

Version:

Internet Computer Plug wallet's controller

439 lines (438 loc) 20.1 kB
"use strict"; 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 }); const principal_1 = require("@dfinity/principal"); const identity_1 = require("@dfinity/identity"); const dab_js_1 = require("@funded-labs/dab-js"); const random_color_1 = __importDefault(require("random-color")); const identity_secp256k1_1 = require("@dfinity/identity-secp256k1"); const errors_1 = require("../errors"); const utils_1 = require("../PlugKeyRing/utils"); const dfx_1 = require("../utils/dfx"); const rosetta_1 = require("../utils/dfx/history/rosetta"); const tokens_1 = require("../constants/tokens"); const xtcHistory_1 = require("../utils/dfx/history/xtcHistory"); const cap_1 = require("../utils/dfx/history/cap"); const icns_1 = __importDefault(require("../utils/dfx/icns")); const utils_2 = require("../utils/dfx/icns/utils"); const Network_1 = require("../PlugKeyRing/modules/NetworkModule/Network"); const account_1 = require("../utils/account"); const getTokensFromCollection_1 = require("../utils/getTokensFromCollection"); const transactionFormatter_1 = require("../utils/formatter/transactionFormatter"); class PlugWallet { constructor({ name, icon, walletId, orderNumber, walletNumber, fetch, icnsData = {}, network, identity, type, blsVerify, }) { this.populateAndTrimNFTs = (collections) => __awaiter(this, void 0, void 0, function* () { const icnsAdapter = new icns_1.default(this.agent); const collectionWithTokens = yield (0, getTokensFromCollection_1.getTokensFromCollections)(this.network.registeredNFTS, this.principal, this.agent); const icnsCollection = yield icnsAdapter.getICNSCollection(); const unique = (0, Network_1.uniqueTokens)([ ...collections, icnsCollection, ...collectionWithTokens, ]); const simplifiedCollections = unique.map((collection) => (Object.assign(Object.assign({}, collection), { tokens: collection.tokens.map(token => ({ index: token.index, url: token.url, canister: token.canister, standard: token.standard, name: token.name, })) }))); const completeCollections = simplifiedCollections.filter(collection => collection.tokens.length > 0); return completeCollections; }); this.nativeGetNFTs = () => __awaiter(this, void 0, void 0, function* () { try { const collections = yield (0, dab_js_1.getAllUserNFTs)({ user: this.principal, agent: this.agent, }); const populatedCollections = yield this.populateAndTrimNFTs(collections); return populatedCollections; } catch (e) { console.warn('Error when trying to fetch NFTs natively from the IC', e); return null; } }); // TODO: Make generic when standard is adopted. Just supports ICPunks rn. this.getNFTs = (args) => __awaiter(this, void 0, void 0, function* () { if (this.network.isCustom) return []; try { const collections = yield (0, dab_js_1.getCachedUserNFTs)({ userPID: this.principal, refresh: args === null || args === void 0 ? void 0 : args.refresh, }); const populatedCollections = yield this.populateAndTrimNFTs(collections); return populatedCollections; } catch (e) { console.warn('Error when trying to fetch NFTs from Kyasshu. Fetching natively...', e); // If kya fails, try native integration return yield this.nativeGetNFTs(); } }); this.transferNFT = (args) => __awaiter(this, void 0, void 0, function* () { const { token, to } = args; if (!(0, utils_1.validatePrincipalId)(to)) { throw new Error(errors_1.ERRORS.INVALID_PRINCIPAL_ID); } try { const NFT = (0, dab_js_1.getNFTActor)({ canisterId: token.canister, agent: this.agent, standard: token.standard.toUpperCase(), blsVerify: this.blsVerify, }); yield NFT.transfer(principal_1.Principal.fromText(to), parseInt(token.index.toString(), 10)); (0, dab_js_1.getCachedUserNFTs)({ userPID: this.principal, refresh: true }).catch(console.warn); return true; } catch (e) { console.warn('NFT transfer error: ', e); throw new Error(errors_1.ERRORS.TRANSFER_NFT_ERROR); } }); this.getTokenInfo = ({ canisterId, standard }) => __awaiter(this, void 0, void 0, function* () { const token = yield this.network.getTokenInfo({ canisterId, standard, defaultIdentity: this.identity, }); const balance = yield this.getTokenBalance({ token }); return balance; }); this.getNFTInfo = ({ canisterId, standard, }) => __awaiter(this, void 0, void 0, function* () { const nft = yield this.network.getNftInfo({ canisterId, identity: this.identity, standard, }); return nft; }); this.registerNFT = ({ canisterId, standard, }) => __awaiter(this, void 0, void 0, function* () { const nfts = yield this.network.registerNFT({ canisterId, standard, walletId: this.walletId, identity: this.identity, }); const registeredNFT = nfts.find(nft => nft.canisterId === canisterId); return registeredNFT; }); this.registerToken = (args) => __awaiter(this, void 0, void 0, function* () { const { canisterId, standard = 'ext', logo, shouldFetchBalance = true } = args || {}; // Register token in network const tokens = yield this.network.registerToken({ canisterId, standard, walletId: this.walletId, defaultIdentity: this.identity, logo, }); let balance = ''; if (shouldFetchBalance) { // Get token balance const tokenActor = yield (0, dab_js_1.getTokenActor)({ canisterId, agent: this.agent, standard, blsVerify: this.blsVerify, }); balance = (yield tokenActor.getBalance(principal_1.Principal.fromText(this.principal))).value; } // Format token and add asset to wallet state const color = (0, random_color_1.default)({ luminosity: 'light' }); const registeredToken = tokens.find(t => t.canisterId === canisterId); const tokenDescriptor = { amount: balance, token: Object.assign(Object.assign({}, registeredToken), { decimals: parseInt(registeredToken.decimals.toString(), 10), canisterId, color, standard, logo }), }; return tokenDescriptor; }); this.toJSON = () => ({ name: this.name, walletId: this.walletId, orderNumber: this.orderNumber, walletNumber: this.walletNumber, principal: this.identity.getPrincipal().toText(), accountId: this.accountId, icon: this.icon, icnsData: this.icnsData, type: this.type, keyPair: JSON.stringify(this.identity.toJSON()), }); this.burnXTC = (args) => __awaiter(this, void 0, void 0, function* () { if (!(0, utils_1.validateCanisterId)(args.to)) { throw new Error(errors_1.ERRORS.INVALID_CANISTER_ID); } const xtcActor = yield (0, dab_js_1.getTokenActor)({ canisterId: tokens_1.TOKENS.XTC.canisterId, agent: this.agent, standard: tokens_1.TOKENS.XTC.standard, blsVerify: this.blsVerify, }); const burnResult = yield xtcActor.burnXTC({ to: principal_1.Principal.fromText(args.to), amount: args.amount, }); try { if ('Ok' in burnResult) { const trxId = burnResult.Ok; yield (0, xtcHistory_1.requestCacheUpdate)(this.principal, [trxId]); } } catch (e) { console.log('Kyasshu error', e); } return burnResult; }); this.getTokenBalance = ({ token, }) => __awaiter(this, void 0, void 0, function* () { var _a; try { const tokenActor = yield (0, dab_js_1.getTokenActor)({ canisterId: token.canisterId, agent: this.agent, standard: token.standard, blsVerify: this.blsVerify, }); const balance = yield tokenActor.getBalance(this.identity.getPrincipal()); const tokenMetadata = (yield tokenActor.getMetadata()); return { amount: balance.value, token: Object.assign(Object.assign({}, token), { fee: (_a = tokenMetadata === null || tokenMetadata === void 0 ? void 0 : tokenMetadata.fungible) === null || _a === void 0 ? void 0 : _a.fee }), }; } catch (e) { console.warn('Get Balance error:', e); return { amount: 'Error', token, error: e.message, }; } }); /* ** Returns XTC, ICP and WICP balances and all associated registered token balances ** If any token balance fails to be fetched, it will be flagged with an error */ this.getBalances = () => __awaiter(this, void 0, void 0, function* () { // Get Custom Token Balances const walletTokens = this.network.getTokens(this.walletId); const tokenBalances = yield Promise.all(walletTokens.map(token => this.getTokenBalance({ token }))); return tokenBalances; }); this.getTransactions = ({ icpPrice, }) => __awaiter(this, void 0, void 0, function* () { if (this.network.isCustom) return { total: 0, transactions: [] }; const icnsAdapter = new icns_1.default(this.agent); const [icpTrxs, xtcTransactions, capTransactions] = yield Promise.all([ (0, rosetta_1.getICPTransactions)(this.accountId), (0, xtcHistory_1.getXTCTransactions)(this.principal), (0, cap_1.getCapTransactions)({ principalId: this.principal, agent: this.agent, }), ]); let transactionsGroup = [ ...capTransactions.transactions, ...icpTrxs.transactions, ...xtcTransactions.transactions, ]; const principals = (0, utils_2.recursiveFindPrincipals)(transactionsGroup); const icnsMapping = yield icnsAdapter.getICNSMappings(principals); transactionsGroup = transactionsGroup.map(tx => (0, utils_2.replacePrincipalsForICNS)(tx, icnsMapping)); const formattedTransactions = (0, transactionFormatter_1.formatTransactions)(transactionsGroup, this.principal, this.accountId, this.network, icpPrice); return formattedTransactions; }); this.send = (args) => __awaiter(this, void 0, void 0, function* () { const { to, amount, canisterId, opts } = args || {}; const savedToken = this.network.tokenByCanisterId(canisterId); if (!savedToken) throw new Error(errors_1.ERRORS.TOKEN_NOT_REGISTERED); const tokenActor = yield (0, dab_js_1.getTokenActor)({ canisterId, agent: this.agent, standard: savedToken.standard, blsVerify: this.blsVerify, }); const result = yield tokenActor.send({ to, from: this.identity.getPrincipal().toString(), amount: BigInt(amount), opts, }); if (canisterId === tokens_1.TOKENS.XTC.canisterId) { try { if ('transactionId' in result) { const trxId = result.transactionId; yield (0, xtcHistory_1.requestCacheUpdate)(this.principal, [BigInt(trxId)]); } } catch (e) { console.log('Kyasshu error', e); } } return result; }); this.approve = (args) => __awaiter(this, void 0, void 0, function* () { const { spender, amount, canisterId, nonce } = args || {}; const savedToken = this.network.tokenByCanisterId(canisterId); if (!savedToken) throw new Error(errors_1.ERRORS.TOKEN_NOT_REGISTERED); const tokenActor = yield (0, dab_js_1.getTokenActor)({ canisterId, agent: this.agent, standard: savedToken.standard, blsVerify: this.blsVerify, }); const result = yield tokenActor.approve({ spender: principal_1.Principal.fromText(spender), amount, nonce, }); }); this.getICNSData = () => __awaiter(this, void 0, void 0, function* () { if (this.network.isCustom) return { names: [], reverseResolvedName: undefined }; const icnsAdapter = new icns_1.default(this.agent); const names = yield icnsAdapter.getICNSNames(); const stringNames = names.map(name => name === null || name === void 0 ? void 0 : name.name.toString()); const reverseResolvedName = yield icnsAdapter.getICNSReverseResolvedName(); this.icnsData = { names: stringNames, reverseResolvedName }; return { names: stringNames, reverseResolvedName }; }); this.getReverseResolvedName = () => __awaiter(this, void 0, void 0, function* () { const icnsAdapter = new icns_1.default(this.agent); return icnsAdapter.getICNSReverseResolvedName(); }); this.setReverseResolvedName = ({ name, }) => __awaiter(this, void 0, void 0, function* () { const icnsAdapter = new icns_1.default(this.agent); return icnsAdapter.setICNSReverseResolvedName(name); }); this.getContacts = () => __awaiter(this, void 0, void 0, function* () { if (this.network.isCustom) return []; try { return yield (0, dab_js_1.getAddresses)(this.agent); } catch (e) { return []; } }); this.addContact = ({ contact, }) => __awaiter(this, void 0, void 0, function* () { try { if ('PrincipalId' in contact.value) { const principal = contact.value.PrincipalId; contact.value = { PrincipalId: principal_1.Principal.fromText(principal.toString()), }; } const contactResponse = yield (0, dab_js_1.addAddress)(this.agent, contact); return contactResponse.hasOwnProperty('Ok') ? true : false; } catch (e) { return false; } }); this.deleteContact = ({ addressName, }) => __awaiter(this, void 0, void 0, function* () { try { const contactResponse = yield (0, dab_js_1.removeAddress)(this.agent, addressName); return contactResponse.hasOwnProperty('Ok') ? true : false; } catch (e) { return false; } }); this.removeToken = (args) => __awaiter(this, void 0, void 0, function* () { const { canisterId } = args || {}; const isDefaultAsset = Object.keys(tokens_1.DEFAULT_MAINNET_ASSETS).includes(canisterId); // If it's a default asset, early return if (isDefaultAsset) return this.network.tokens; const tokens = yield this.network.removeToken({ canisterId, }); return tokens; }); this.delegateIdentity = (args) => __awaiter(this, void 0, void 0, function* () { const { to, targets } = args; const pidTargets = targets.map(target => principal_1.Principal.fromText(target)); const publicKey = identity_secp256k1_1.Secp256k1PublicKey.fromDer(to); const delagationChain = yield identity_1.DelegationChain.create(this.identity, publicKey, undefined, // Expiration arg, default to 15 mins { targets: pidTargets }); return JSON.stringify(delagationChain.toJSON()); }); this.name = name || 'Account 1'; this.icon = icon; this.walletId = walletId; this.orderNumber = orderNumber; this.walletNumber = walletNumber; this.icnsData = icnsData; this.identity = identity; this.principal = identity.getPrincipal().toText(); this.fetch = fetch; this.network = network; this.type = type; this.agent = (0, dfx_1.createAgent)({ defaultIdentity: this.identity, fetch: this.fetch, }); this.blsVerify = blsVerify; } get accountId() { return (0, account_1.getAccountId)(this.identity.getPrincipal()); } setNetwork(network) { return __awaiter(this, void 0, void 0, function* () { this.network = network; this.agent = network.createAgent({ defaultIdentity: this.identity }); }); } setName(val) { this.name = val; } sign({ payload, }) { return __awaiter(this, void 0, void 0, function* () { return this.identity.sign(payload); }); } setIcon(val) { this.icon = val; } getAgent({ host }) { if (host) { return (0, dfx_1.createAgent)({ defaultIdentity: this.identity, host, wrapped: false, fetch: this.fetch, }); } return this.agent; } get publicKey() { return this.identity.getPublicKey(); } get pemFile() { return this.identity.getPem(); } getIdentity() { return this.identity; } } exports.default = PlugWallet;