Merge branch 'main' of https://github.com/hyle-team/zano_web3
This commit is contained in:
commit
ce045c02ba
1 changed files with 268 additions and 261 deletions
|
|
@ -1,290 +1,297 @@
|
|||
import axios from 'axios';
|
||||
import axios from "axios";
|
||||
import Big from "big.js";
|
||||
import {
|
||||
AuthData,
|
||||
AliasAuth,
|
||||
PkeyAuth,
|
||||
ValidationParams,
|
||||
BalanceInfo,
|
||||
TxInfo,
|
||||
} from './types';
|
||||
|
||||
import { ZANO_ASSET_ID, ZanoError } from './utils';
|
||||
import { APIAsset, APIBalance } from './types';
|
||||
AuthData,
|
||||
AliasAuth,
|
||||
PkeyAuth,
|
||||
ValidationParams,
|
||||
BalanceInfo,
|
||||
TxInfo,
|
||||
} from "./types";
|
||||
|
||||
import { ZANO_ASSET_ID, ZanoError } from "./utils";
|
||||
import { APIAsset, APIBalance } from "./types";
|
||||
|
||||
interface ConstructorParams {
|
||||
walletUrl: string;
|
||||
daemonUrl: string;
|
||||
walletUrl: string;
|
||||
daemonUrl: string;
|
||||
}
|
||||
|
||||
interface GetTxsParams {
|
||||
count: number;
|
||||
offset: number;
|
||||
exclude_mining_txs?: boolean;
|
||||
exclude_unconfirmed?: boolean;
|
||||
order?: string;
|
||||
update_provision_info?: boolean;
|
||||
count: number;
|
||||
offset: number;
|
||||
exclude_mining_txs?: boolean;
|
||||
exclude_unconfirmed?: boolean;
|
||||
order?: string;
|
||||
update_provision_info?: boolean;
|
||||
}
|
||||
|
||||
class ServerWallet {
|
||||
private walletUrl: string;
|
||||
private daemonUrl: string;
|
||||
private walletUrl: string;
|
||||
private daemonUrl: string;
|
||||
|
||||
constructor(params: ConstructorParams) {
|
||||
this.walletUrl = params.walletUrl;
|
||||
this.daemonUrl = params.daemonUrl;
|
||||
}
|
||||
constructor(params: ConstructorParams) {
|
||||
this.walletUrl = params.walletUrl;
|
||||
this.daemonUrl = params.daemonUrl;
|
||||
}
|
||||
|
||||
private async fetchDaemon(method: string, params: any) {
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
private async fetchDaemon(method: string, params: any) {
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
|
||||
const data = {
|
||||
jsonrpc: "2.0",
|
||||
id: 0,
|
||||
method: method,
|
||||
params: params
|
||||
};
|
||||
|
||||
return axios.post(this.daemonUrl, data, { headers })
|
||||
}
|
||||
|
||||
private async fetchWallet(method: string, params: any) {
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
|
||||
const data = {
|
||||
jsonrpc: "2.0",
|
||||
id: 0,
|
||||
method: method,
|
||||
params: params
|
||||
};
|
||||
|
||||
return axios.post(this.walletUrl, data, { headers })
|
||||
}
|
||||
|
||||
async updateWalletRpcUrl(rpcUrl: string) {
|
||||
this.walletUrl = rpcUrl;
|
||||
}
|
||||
|
||||
async updateDaemonRpcUrl(rpcUrl: string) {
|
||||
this.daemonUrl = rpcUrl;
|
||||
}
|
||||
|
||||
async getAssetsList() {
|
||||
const count = 100; // Number of items to fetch per request
|
||||
let offset = 0;
|
||||
let allAssets: APIAsset[] = [];
|
||||
let keepFetching = true;
|
||||
|
||||
while (keepFetching) {
|
||||
try {
|
||||
const response = await this.fetchDaemon("get_assets_list", { count, offset });
|
||||
|
||||
const assets = response.data.result.assets;
|
||||
if (assets.length < count) {
|
||||
keepFetching = false;
|
||||
}
|
||||
allAssets = allAssets.concat(assets);
|
||||
offset += count;
|
||||
} catch (error) {
|
||||
throw new ZanoError("Failed to fetch assets list", "ASSETS_FETCH_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
return allAssets as APIAsset[];
|
||||
const data = {
|
||||
jsonrpc: "2.0",
|
||||
id: 0,
|
||||
method: method,
|
||||
params: params,
|
||||
};
|
||||
|
||||
async getAssetDetails(assetId: string) {
|
||||
const assets = await this.getAssetsList();
|
||||
const asset = assets.find((a) => a.asset_id === assetId);
|
||||
return axios.post(this.daemonUrl, data, { headers });
|
||||
}
|
||||
|
||||
if (!asset) {
|
||||
throw new ZanoError(
|
||||
`Asset with ID ${assetId} not found`,
|
||||
"ASSET_NOT_FOUND"
|
||||
);
|
||||
}
|
||||
private async fetchWallet(method: string, params: any) {
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
|
||||
return asset as APIAsset;
|
||||
}
|
||||
|
||||
|
||||
async getAssetInfo(assetId: string) {
|
||||
try {
|
||||
const response = await this.fetchDaemon("get_asset_info", { asset_id: assetId });
|
||||
|
||||
if (response.data.result) {
|
||||
return response.data.result;
|
||||
} else {
|
||||
throw new ZanoError(
|
||||
`Error fetching info for asset ID ${assetId}`,
|
||||
"ASSET_INFO_ERROR"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new ZanoError("Failed to fetch asset info", "ASSET_INFO_FETCH_ERROR");
|
||||
}
|
||||
const data = {
|
||||
jsonrpc: "2.0",
|
||||
id: 0,
|
||||
method: method,
|
||||
params: params,
|
||||
};
|
||||
|
||||
async sendTransfer(assetId: string, address: string, amount: string) {
|
||||
let decimalPoint: number;
|
||||
return axios.post(this.walletUrl, data, { headers });
|
||||
}
|
||||
|
||||
if (assetId === ZANO_ASSET_ID) {
|
||||
decimalPoint = 12;
|
||||
} else {
|
||||
const asset = await this.getAssetDetails(assetId);
|
||||
decimalPoint = asset.decimal_point;
|
||||
}
|
||||
async updateWalletRpcUrl(rpcUrl: string) {
|
||||
this.walletUrl = rpcUrl;
|
||||
}
|
||||
|
||||
const bigAmount = new Big(amount)
|
||||
.times(new Big(10).pow(decimalPoint))
|
||||
.toString();
|
||||
async updateDaemonRpcUrl(rpcUrl: string) {
|
||||
this.daemonUrl = rpcUrl;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.fetchWallet("transfer", {
|
||||
destinations: [{ address, amount: bigAmount, asset_id: assetId }],
|
||||
fee: "10000000000",
|
||||
mixin: 15,
|
||||
});
|
||||
async getAssetsList() {
|
||||
const count = 100;
|
||||
let offset = 0;
|
||||
let allAssets: APIAsset[] = [];
|
||||
let keepFetching = true;
|
||||
|
||||
if (response.data.result) {
|
||||
return response.data.result;
|
||||
} else if (
|
||||
response.data.error &&
|
||||
response.data.error.message === "WALLET_RPC_ERROR_CODE_NOT_ENOUGH_MONEY"
|
||||
) {
|
||||
throw new ZanoError("Not enough funds", "NOT_ENOUGH_FUNDS");
|
||||
} else {
|
||||
throw new ZanoError("Error sending transfer", "TRANSFER_ERROR");
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (error instanceof ZanoError) {
|
||||
throw error; // Re-throw the custom error
|
||||
} else {
|
||||
throw new ZanoError("Failed to send transfer", "TRANSFER_SEND_ERROR");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async getAliasByAddress(address: string) {
|
||||
try {
|
||||
const response = await this.fetchDaemon("get_alias_by_address", address);
|
||||
|
||||
if (response.data.result) {
|
||||
return response.data.result;
|
||||
} else {
|
||||
throw new ZanoError(
|
||||
`Error fetching alias for address ${address}`,
|
||||
"ALIAS_FETCH_ERROR"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new ZanoError("Failed to fetch alias", "ALIAS_FETCH_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
async getBalances() {
|
||||
try {
|
||||
const response = await this.fetchWallet("getbalance", {});
|
||||
const balancesData = response.data.result.balances as APIBalance[];
|
||||
|
||||
|
||||
const balances = balancesData.map((asset) => ({
|
||||
name: asset.asset_info.full_name,
|
||||
ticker: asset.asset_info.ticker,
|
||||
id: asset.asset_info.asset_id,
|
||||
amount: new Big(asset.unlocked)
|
||||
.div(new Big(10).pow(asset.asset_info.decimal_point))
|
||||
.toString(),
|
||||
awaiting_in: new Big(asset.awaiting_in).toString(),
|
||||
awaiting_out: new Big(asset.awaiting_out).toString(),
|
||||
total: new Big(asset.total).toString(),
|
||||
unlocked: new Big(asset.unlocked).toString(),
|
||||
asset_info: asset.asset_info
|
||||
}));
|
||||
|
||||
return balances.sort((a, b) => {
|
||||
if (a.id === ZANO_ASSET_ID)
|
||||
return -1;
|
||||
if (b.id === ZANO_ASSET_ID )
|
||||
return 1;
|
||||
return 0;
|
||||
}) as BalanceInfo[];
|
||||
|
||||
} catch (error) {
|
||||
throw new ZanoError("Failed to fetch balances", "BALANCES_FETCH_ERROR");
|
||||
}
|
||||
};
|
||||
|
||||
async validateWallet(authData: AuthData) {
|
||||
|
||||
const { message, address, signature } = authData;
|
||||
|
||||
const alias = (authData as AliasAuth).alias || undefined;
|
||||
const pkey = (authData as PkeyAuth).pkey || undefined;
|
||||
|
||||
if (!message || (!alias && !pkey) || !signature) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const validationParams: ValidationParams = {
|
||||
"buff": Buffer.from(message).toString("base64"),
|
||||
"sig": signature
|
||||
};
|
||||
|
||||
if (alias) {
|
||||
validationParams['alias'] = alias;
|
||||
} else {
|
||||
validationParams['pkey'] = pkey;
|
||||
}
|
||||
|
||||
const response = await this.fetchDaemon(
|
||||
'validate_signature',
|
||||
validationParams
|
||||
);
|
||||
|
||||
const valid = response?.data?.result?.status === 'OK';
|
||||
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (alias) {
|
||||
const aliasDetailsResponse = await this.fetchDaemon(
|
||||
'get_alias_details',
|
||||
{
|
||||
"alias": alias,
|
||||
}
|
||||
);
|
||||
|
||||
const aliasDetails = aliasDetailsResponse?.data?.result?.alias_details;
|
||||
const aliasAddress = aliasDetails?.address;
|
||||
|
||||
const addressValid = !!aliasAddress && aliasAddress === address;
|
||||
|
||||
if (!addressValid) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
async getTxs(params: GetTxsParams) {
|
||||
const txs = await this.fetchWallet("get_recent_txs_and_info2", {
|
||||
"count": params.count,
|
||||
"exclude_mining_txs": params.exclude_mining_txs || false,
|
||||
"exclude_unconfirmed": params.exclude_unconfirmed || false,
|
||||
"offset": params.offset,
|
||||
"order": params.order || "FROM_END_TO_BEGIN",
|
||||
"update_provision_info": params.update_provision_info || true
|
||||
while (keepFetching) {
|
||||
try {
|
||||
const response = await this.fetchDaemon("get_assets_list", {
|
||||
count,
|
||||
offset,
|
||||
});
|
||||
|
||||
return txs.data.result as TxInfo;
|
||||
const assets = response.data.result.assets;
|
||||
if (assets.length < count) {
|
||||
keepFetching = false;
|
||||
}
|
||||
allAssets = allAssets.concat(assets);
|
||||
offset += count;
|
||||
} catch (error) {
|
||||
throw new ZanoError(
|
||||
"Failed to fetch assets list",
|
||||
"ASSETS_FETCH_ERROR"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return allAssets as APIAsset[];
|
||||
}
|
||||
|
||||
async getAssetDetails(assetId: string) {
|
||||
const assets = await this.getAssetsList();
|
||||
const asset = assets.find((a) => a.asset_id === assetId);
|
||||
|
||||
if (!asset) {
|
||||
throw new ZanoError(
|
||||
`Asset with ID ${assetId} not found`,
|
||||
"ASSET_NOT_FOUND"
|
||||
);
|
||||
}
|
||||
|
||||
return asset as APIAsset;
|
||||
}
|
||||
|
||||
async getAssetInfo(assetId: string) {
|
||||
try {
|
||||
const response = await this.fetchDaemon("get_asset_info", {
|
||||
asset_id: assetId,
|
||||
});
|
||||
|
||||
if (response.data.result) {
|
||||
return response.data.result;
|
||||
} else {
|
||||
throw new ZanoError(
|
||||
`Error fetching info for asset ID ${assetId}`,
|
||||
"ASSET_INFO_ERROR"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new ZanoError(
|
||||
"Failed to fetch asset info",
|
||||
"ASSET_INFO_FETCH_ERROR"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async sendTransfer(assetId: string, address: string, amount: string) {
|
||||
let decimalPoint: number;
|
||||
let auditable: boolean;
|
||||
|
||||
if (assetId === ZANO_ASSET_ID) {
|
||||
decimalPoint = 12;
|
||||
} else {
|
||||
const asset = await this.getAssetDetails(assetId);
|
||||
decimalPoint = asset.decimal_point;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.fetchWallet("getaddress", {});
|
||||
auditable = response.data.result.address.startsWith("a");
|
||||
} catch (error) {
|
||||
throw new ZanoError("Failed to fetch address", "ADDRESS_FETCH_ERROR");
|
||||
}
|
||||
|
||||
const bigAmount = new Big(amount)
|
||||
.times(new Big(10).pow(decimalPoint))
|
||||
.toString();
|
||||
|
||||
try {
|
||||
const response = await this.fetchWallet("transfer", {
|
||||
destinations: [{ address, amount: bigAmount, asset_id: assetId }],
|
||||
fee: "10000000000",
|
||||
mixin: auditable ? 0 : 15,
|
||||
});
|
||||
|
||||
if (response.data.result) {
|
||||
return response.data.result;
|
||||
} else if (
|
||||
response.data.error &&
|
||||
response.data.error.message === "WALLET_RPC_ERROR_CODE_NOT_ENOUGH_MONEY"
|
||||
) {
|
||||
throw new ZanoError("Not enough funds", "NOT_ENOUGH_FUNDS");
|
||||
} else {
|
||||
throw new ZanoError("Error sending transfer", "TRANSFER_ERROR");
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof ZanoError) {
|
||||
throw error;
|
||||
} else {
|
||||
throw new ZanoError("Failed to send transfer", "TRANSFER_SEND_ERROR");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getAliasByAddress(address: string) {
|
||||
try {
|
||||
const response = await this.fetchDaemon("get_alias_by_address", address);
|
||||
|
||||
if (response.data.result) {
|
||||
return response.data.result;
|
||||
} else {
|
||||
throw new ZanoError(
|
||||
`Error fetching alias for address ${address}`,
|
||||
"ALIAS_FETCH_ERROR"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new ZanoError("Failed to fetch alias", "ALIAS_FETCH_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
async getBalances() {
|
||||
try {
|
||||
const response = await this.fetchWallet("getbalance", {});
|
||||
const balancesData = response.data.result.balances as APIBalance[];
|
||||
|
||||
const balances = balancesData.map((asset) => ({
|
||||
name: asset.asset_info.full_name,
|
||||
ticker: asset.asset_info.ticker,
|
||||
id: asset.asset_info.asset_id,
|
||||
amount: new Big(asset.unlocked)
|
||||
.div(new Big(10).pow(asset.asset_info.decimal_point))
|
||||
.toString(),
|
||||
awaiting_in: new Big(asset.awaiting_in).toString(),
|
||||
awaiting_out: new Big(asset.awaiting_out).toString(),
|
||||
total: new Big(asset.total).toString(),
|
||||
unlocked: new Big(asset.unlocked).toString(),
|
||||
asset_info: asset.asset_info,
|
||||
}));
|
||||
|
||||
return balances.sort((a, b) => {
|
||||
if (a.id === ZANO_ASSET_ID) return -1;
|
||||
if (b.id === ZANO_ASSET_ID) return 1;
|
||||
return 0;
|
||||
}) as BalanceInfo[];
|
||||
} catch (error) {
|
||||
throw new ZanoError("Failed to fetch balances", "BALANCES_FETCH_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
async validateWallet(authData: AuthData) {
|
||||
const { message, address, signature } = authData;
|
||||
|
||||
const alias = (authData as AliasAuth).alias || undefined;
|
||||
const pkey = (authData as PkeyAuth).pkey || undefined;
|
||||
|
||||
if (!message || (!alias && !pkey) || !signature) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const validationParams: ValidationParams = {
|
||||
buff: Buffer.from(message).toString("base64"),
|
||||
sig: signature,
|
||||
};
|
||||
|
||||
if (alias) {
|
||||
validationParams["alias"] = alias;
|
||||
} else {
|
||||
validationParams["pkey"] = pkey;
|
||||
}
|
||||
|
||||
const response = await this.fetchDaemon(
|
||||
"validate_signature",
|
||||
validationParams
|
||||
);
|
||||
|
||||
const valid = response?.data?.result?.status === "OK";
|
||||
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (alias) {
|
||||
const aliasDetailsResponse = await this.fetchDaemon("get_alias_details", {
|
||||
alias: alias,
|
||||
});
|
||||
|
||||
const aliasDetails = aliasDetailsResponse?.data?.result?.alias_details;
|
||||
const aliasAddress = aliasDetails?.address;
|
||||
|
||||
const addressValid = !!aliasAddress && aliasAddress === address;
|
||||
|
||||
if (!addressValid) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
async getTxs(params: GetTxsParams) {
|
||||
const txs = await this.fetchWallet("get_recent_txs_and_info2", {
|
||||
count: params.count,
|
||||
exclude_mining_txs: params.exclude_mining_txs || false,
|
||||
exclude_unconfirmed: params.exclude_unconfirmed || false,
|
||||
offset: params.offset,
|
||||
order: params.order || "FROM_END_TO_BEGIN",
|
||||
update_provision_info: params.update_provision_info || true,
|
||||
});
|
||||
|
||||
return txs.data.result as TxInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default ServerWallet;
|
||||
export default ServerWallet;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue