@ledgerhq/coin-stacks
Version:
Ledger Stacks Coin integration
217 lines • 7.46 kB
JavaScript
import { getEnv } from "@ledgerhq/live-env";
import network from "@ledgerhq/live-network/network";
import { extractTokenTransferTransactions, extractSendManyTransactions, extractContractTransactions, } from "./transformers";
import { makeLRUCache, minutes } from "@ledgerhq/live-network/cache";
import { StacksMainnet, StacksTestnet } from "@stacks/network";
export const StacksNetwork = {
mainnet: new StacksMainnet({ url: getEnv("API_STACKS_ENDPOINT") }),
testnet: new StacksTestnet(),
};
/**
* Builds the Stacks API URL with an optional path
*/
const getStacksURL = (path) => {
const baseUrl = getEnv("API_STACKS_ENDPOINT");
if (!baseUrl)
throw new Error("API base URL not available");
return `${baseUrl}${path ? path : ""}`;
};
/**
* Basic GET request to the Stacks API
*/
const fetch = async (path) => {
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 = {
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;
return data;
};
/**
* Basic POST request with JSON data to the Stacks API
*/
const send = async (path, data) => {
const url = getStacksURL(path);
const opts = {
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;
return responseData;
};
/**
* Basic POST request with raw binary data to the Stacks API
*/
const sendRaw = async (path, data) => {
const url = getStacksURL(path);
const opts = {
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;
return responseData;
};
/**
* Fetches STX balance for an address
*/
export const fetchBalances = async (addr) => {
const data = await fetch(`/extended/v1/address/${addr}/stx`);
return data;
};
/**
* Fetches a page of token balances for an address
*/
export const fetchTokenBalancesPage = async (addr, offset = 0, limit = 50) => {
try {
const response = await fetch(`/extended/v2/addresses/${addr}/balances/ft?offset=${offset}&limit=${limit}`);
return response;
}
catch {
return { limit, offset, total: 0, results: [] };
}
};
/**
* Fetches all token balances for an address by paginating through results
*/
export const fetchAllTokenBalances = async (addr) => {
const limit = 50;
let offset = 0;
let total = 0;
const tokenBalanceMap = {};
do {
const response = await fetchTokenBalancesPage(addr, offset, limit);
// Map token balances to a more convenient format
for (const item of response.results) {
tokenBalanceMap[item.token.toLowerCase()] = item.balance;
}
offset += limit;
total = response.total;
} while (offset < total);
return tokenBalanceMap;
};
/**
* Fetches estimated fees for a transfer
*/
export const fetchEstimatedFees = async (request) => {
// Cast to Record<string, unknown> to satisfy type constraints
const feeRate = await send(`/v2/fees/transfer`, request);
return feeRate;
};
/**
* Fetches current blockchain status, including block height
*/
export const fetchBlockHeight = async () => {
const data = await fetch("/extended");
return data;
};
/**
* Fetches a page of transactions for an address
*/
export const fetchTransactionsPage = async (addr, offset = 0, limit = 50) => {
try {
const response = await fetch(`/extended/v2/addresses/${addr}/transactions?offset=${offset}&limit=${limit}`);
return response;
}
catch {
return { limit, offset, total: 0, results: [] };
}
};
/**
* Fetches all transactions for an address
*/
export const fetchAllTransactions = async (addr) => {
let qty;
let offset = 0;
const limit = 50;
const allTransactions = [];
// Fetch all transactions in pages
do {
const { results, total } = await fetchTransactionsPage(addr, offset, limit);
allTransactions.push(...results);
offset += limit;
qty = total;
} while (offset < qty);
return allTransactions;
};
/**
* Fetches all transactions for an address and organizes them by type
*/
export const fetchFullTxs = async (addr) => {
// 1. Fetch all transactions
const allTransactions = await fetchAllTransactions(addr);
// 2. Extract regular token transfers
const tokenTransfers = extractTokenTransferTransactions(allTransactions);
// 3. Extract and group contract calls
const contractTransactions = await extractContractTransactions(allTransactions);
// 4. Add send-many transactions to token transfers
const sendManyTransactions = extractSendManyTransactions(allTransactions);
tokenTransfers.push(...sendManyTransactions);
return [tokenTransfers, contractTransactions];
};
/**
* Broadcasts a signed transaction to the Stacks network
*/
export const broadcastTx = async (message) => {
let response = await sendRaw(`/v2/transactions`, message);
if (response !== "")
response = `0x${response}`;
return response;
};
/**
* Fetches a page of mempool transactions for an address
*/
export const fetchMempoolTransactionsPage = async (addr, offset = 0, limit = 50) => {
const response = await fetch(`/extended/v1/tx/mempool?sender_address=${addr}&offset=${offset}&limit=${limit}`);
return response;
};
/**
* Fetches all mempool transactions for an address
*/
export const fetchFullMempoolTxs = async (addr) => {
let qty;
let offset = 0;
const limit = 50;
let txs = [];
do {
const { results, total } = await fetchMempoolTransactionsPage(addr, offset, limit);
txs = txs.concat(results);
offset += limit;
qty = total;
} while (offset < qty);
return txs;
};
/**
* Fetches the nonce for an address
*/
export const fetchNonce = async (addr) => {
const response = await fetch(`/extended/v1/address/${addr}/nonces`);
return response;
};
/**
* Fetches metadata for a fungible token by contract address
* This can be used to extract the asset_identifier from a contract_id
* @param contractAddress - The contract address in format: ADDRESS.CONTRACT_NAME
* @returns The fungible token metadata including asset_identifier
*/
export const fetchFungibleTokenMetadata = async (contractAddress) => {
const url = `/metadata/v1/ft?address=${contractAddress}`;
const response = await fetch(url);
return response;
};
/**
* Fetches metadata for a fungible token by contract address with caching
*/
export const fetchFungibleTokenMetadataCached = makeLRUCache(fetchFungibleTokenMetadata, args => args, minutes(60));
//# sourceMappingURL=api.js.map