@funded-labs/plug-controller
Version:
Internet Computer Plug wallet's controller
439 lines (438 loc) • 20.1 kB
JavaScript
"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;