@ledgerhq/coin-stacks
Version:
Ledger Stacks Coin integration
174 lines (138 loc) • 5.43 kB
text/typescript
import { AxiosRequestConfig, AxiosResponse } from "axios";
import { getEnv } from "@ledgerhq/live-env";
import network from "@ledgerhq/live-network/network";
import {
BalanceResponse,
BroadcastTransactionRequest,
BroadcastTransactionResponse,
EstimatedFeesRequest,
EstimatedFeesResponse,
GetNonceResponse,
MempoolResponse,
MempoolTransaction,
NetworkStatusResponse,
TransactionResponse,
TransactionsResponse,
} from "./api.types";
const getStacksURL = (path?: string): string => {
const baseUrl = getEnv("API_STACKS_ENDPOINT");
if (!baseUrl) throw new Error("API base URL not available");
return `${baseUrl}${path ? path : ""}`;
};
const fetch = async <T>(path: string) => {
const url = getStacksURL(path);
// We force data to this way as network func is not using the correct param type. Changing that func will generate errors in other implementations
const opts: AxiosRequestConfig = {
method: "GET",
url,
};
const rawResponse = await network(opts);
// We force data to this way as network func is not using the correct param type. Changing that func will generate errors in other implementations
const { data } = rawResponse as AxiosResponse<T>;
return data;
};
const send = async <T>(path: string, data: Record<string, any>) => {
const url = getStacksURL(path);
const opts: AxiosRequestConfig = {
method: "POST",
url,
data: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
};
const rawResponse = await network(opts);
// We force data to this way as network func is not using generics. Changing that func will generate errors in other implementations
const { data: responseData } = rawResponse as AxiosResponse<T>;
return responseData;
};
const sendRaw = async <T>(path: string, data: Buffer) => {
const url = getStacksURL(path);
const opts: AxiosRequestConfig = {
method: "POST",
url,
data,
headers: { "Content-Type": "application/octet-stream" },
};
const rawResponse = await network(opts);
// We force data to this way as network func is not using generics. Changing that func will generate errors in other implementations
const { data: responseData } = rawResponse as AxiosResponse<T>;
return responseData;
};
export const fetchBalances = async (addr: string): Promise<BalanceResponse> => {
const data = await fetch<BalanceResponse>(`/extended/v1/address/${addr}/stx`);
return data; // TODO Validate if the response fits this interface
};
export const fetchEstimatedFees = async (
request: EstimatedFeesRequest,
): Promise<EstimatedFeesResponse> => {
const feeRate = await send<EstimatedFeesResponse>(`/v2/fees/transfer`, request);
return feeRate; // TODO Validate if the response fits this interface
};
export const fetchBlockHeight = async (): Promise<NetworkStatusResponse> => {
const data = await fetch<NetworkStatusResponse>("/extended");
return data as NetworkStatusResponse; // TODO Validate if the response fits this interface
};
export const fetchTxs = async (addr: string, offset = 0): Promise<TransactionsResponse> => {
const limit = 50;
try {
const response = await fetch<TransactionsResponse>(
`/extended/v2/addresses/${addr}/transactions?offset=${offset}&limit=${limit}`,
);
return response; // TODO Validate if the response fits this interface
} catch (e) {
return { limit, offset, total: 0, results: [] };
}
};
export const fetchFullTxs = async (addr: string): Promise<TransactionResponse[]> => {
let qty,
offset = 0;
let txs: TransactionResponse[] = [];
do {
const { results, total, limit } = await fetchTxs(addr, offset);
txs = txs.concat(
results.filter(t => {
if (t.tx?.tx_type === "token_transfer") {
return true;
}
if (
t.tx?.tx_type === "contract_call" &&
t.tx?.contract_call?.function_name === "send-many"
) {
return true;
}
return false;
}),
);
offset += limit;
qty = total;
} while (offset < qty);
return txs; // TODO Validate if the response fits this interface
};
export const broadcastTx = async (
message: BroadcastTransactionRequest,
): Promise<BroadcastTransactionResponse> => {
let response = await sendRaw<BroadcastTransactionResponse>(`/v2/transactions`, message);
if (response != "") response = `0x${response}`;
return response; // TODO Validate if the response fits this interface
};
export const fetchMempoolTxs = async (addr: string, offset = 0): Promise<MempoolResponse> => {
const response = await fetch<MempoolResponse>(
`/extended/v1/tx/mempool?sender_address=${addr}&offset=${offset}`,
);
return response; // TODO Validate if the response fits this interface
};
export const fetchFullMempoolTxs = async (addr: string): Promise<MempoolTransaction[]> => {
let qty,
offset = 0;
let txs: MempoolTransaction[] = [];
do {
const { results, total, limit } = await fetchMempoolTxs(addr, offset);
txs = txs.concat(results);
offset += limit;
qty = total;
} while (offset < qty);
return txs; // TODO Validate if the response fits this interface
};
export const fetchNonce = async (addr: string): Promise<GetNonceResponse> => {
const response = await fetch<GetNonceResponse>(`/extended/v1/address/${addr}/nonces`);
return response; // TODO Validate if the response fits this interface
};