satsterminal-sdk
Version:
A TypeScript SDK for interacting with the SatsTerminal ecosystem.
556 lines (432 loc) • 16.9 kB
Markdown
# SatsTerminal SDK
A TypeScript/JavaScript SDK for interacting with the SatsTerminal ecosystem. This SDK allows developers to integrate SatsTerminal functionalities into their Node.js applications easily.
## Version Changelog
### v1.6.6
- Added `fill` parameter to `fetchQuote` method to automatically use the best-priced AMM when no orders are found in specified marketplaces
### v1.6.4 & v1.6.5
- PSBT inputs to sign for non RBF protected orders added to PSBT response
- Improved type definitions
### v1.6.3
- Added RBF protection support for rune transactions
### v1.6.2
- Added support for multiple marketplaces
- Performance improvements and bug fixes
## Table of Contents
- [Installation](#installation)
- [Environment Variables](#environment-variables)
- [Quick Start](#quick-start)
- [API Reference](#api-reference)
- [Configuration](#configuration)
- [Methods](#methods)
- [signIn](#signin)
- [bind](#bind)
- [points](#points)
- [search](#search)
- [popularCollections](#popularcollections)
- [fetchQuote](#fetchquote)
- [getPSBT](#getpsbt)
- [confirmPSBT](#confirmpsbt)
- [Example Flow](#example-flow)
- [Error Handling](#error-handling)
- [Common Issues](#common-issues)
## Installation
Install the SDK via npm:
```bash
npm install satsterminal-sdk@latest
```
## Environment Variables
The SDK requires configuration including an API key (which you can get from the SatsTerminal Admin Dashboard). You can set this up using environment variables or pass it directly to the constructor.
### Setting Environment Variables
Create a `.env` file in your project root:
```env
SATSTERMINAL_API_KEY=your_api_key_here
```
Load it using dotenv:
```javascript
require('dotenv').config();
```
## Quick Start
```typescript
import { SatsTerminal } from 'satsterminal-sdk';
const satsTerminal = new SatsTerminal({
apiKey: 'your_api_key_here'
});
// Example: Searching for runes
const searchResult = await satsTerminal.search({ rune_name: 'DOG•GO•TO•THE•MOON' });
```
## API Reference
### Configuration
The SDK requires configuration when initializing:
```typescript
interface SatsTerminalConfig {
apiKey: string;
}
```
### Methods
#### signIn
Register and authenticate a user with their Bitcoin and Ordinals addresses.
```typescript
async signIn(params: SignInParams): Promise<SignInResponse>
```
**Parameters:**
- `ord_address` (string): Ordinals address
- `btc_address` (string): Bitcoin address
- `ord_public_key` (string): Ordinals public key
- `btc_public_key` (string): Bitcoin public key
- `provider` (string): Wallet provider (e.g., 'xverse', 'unisat')
**Example:**
```typescript
const signIn = await satsTerminal.signIn({
ord_address: "bc1...",
btc_address: "3Pc...",
ord_public_key: "...",
btc_public_key: "...",
provider: "xverse"
});
```
#### bind
Bind a user's wallet to Unisat and DotSwap. This is required only once before performing transactions.
```typescript
async bind(params: BindParams): Promise<BindResponse>
```
**Parameters:**
- `btcAddress` (string): Bitcoin address
- `nftAddress` (string): Ordinals address
- `sign` (string): User's signature of the bind message
**Example:**
```typescript
const bind = await satsTerminal.bind({
btcAddress: "3Pc...",
nftAddress: "bc1...",
sign: "user_signature_here"
});
```
#### points
Get the user's accumulated Amber points balance.
```typescript
async points(params: PointsParams): Promise<PointsResponse>
```
**Parameters:**
- `ord_address` (string): Ordinals address
**Example:**
```typescript
const points = await satsTerminal.points({
ord_address: "bc1..."
});
```
#### search
Search for runes by name.
```typescript
async search(params: SearchParams): Promise<SearchResponse>
```
**Parameters:**
- `rune_name` (string): The name of the rune to search for
- `sell` (boolean, optional): Whether to search for sell orders
**Example:**
```typescript
const result = await satsTerminal.search({
rune_name: 'LOBO•THE•WOLF•PUP',
sell: false
});
```
#### popularCollections
Fetch popular rune collections.
```typescript
async popularCollections(params: PopularCollectionsParams): Promise<PopularCollectionsResponse>
```
**Example:**
```typescript
const collections = await satsTerminal.popularCollections();
```
#### fetchQuote
Fetch a quote for a rune purchase.
```typescript
async fetchQuote(params: QuoteParams): Promise<QuoteResponse>
```
**Parameters:**
- `btcAmount` (number/string): Amount of BTC to spend
- `runeName` (string): Name of the rune
- `address` (string): Bitcoin address
- `themeID?` (string): Optional widget theme ID
- `sell?` (boolean): Optional flag for sell orders (default: false)
- `marketplaces?` (string[]): Optional list of marketplaces to use (default: empty, uses all available)
- `rbfProtection?` (boolean): Optional RBF protection flag (default: false)
- `fill?` (boolean): Optional flag that, when true, will use the best AMM to fulfill the order if no order is found in the specified marketplaces (default: false)
**Example:**
```typescript
const quote = await satsTerminal.fetchQuote({
btcAmount: 0.0001,
runeName: "LOBO•THE•WOLF•PUP",
address: "bc1...",
marketplaces: ["MagicEden"],
rbfProtection: true,
fill: true
});
```
**Example Response:**
```javascript
{
bestMarketplace: 'MagicEden',
selectedOrders: [
{
amount: '2039350000000',
formattedAmount: '20393.5',
id: '5b35441a-297b-45b4-8277-a3048309837b',
isPending: false,
rune: 'LOBOTHEWOLFPUP',
maker: 'bc1p97mujnqerk098gnd9actkpev6waeauvzk2yt3zznllshz5evz66q55vjw6',
makerReceiveAddress: '393PuAEiJ7JTFmVvkJJSG42s7241gG1ncC',
makerFeeBps: 0,
price: 10000,
side: 'sell',
status: 'valid',
formattedUnitPrice: '0.490352',
utxoIds: [
'f99df727b7268bd4662798a8893d965940a63a68b77bdcc92d924ccecee66035:0'
],
filledAmount: '0',
formattedFilledAmount: '0',
rbfProtectionShield: true,
rbfProtectedSacAddress: 'bc1pg5lj78n75nhmvv47xq8y0kf6wzzed6ewesuq9p4qu6hmsnketz9sgmfdeh',
market: 'MagicEden'
}
],
totalFormattedAmount: '20393.5',
totalPrice: '0.0001',
metrics: { /* metrics data */ },
marketplaces: { /* marketplace data */ }
}
```
#### getPSBT
Generate a Partially Signed Bitcoin Transaction (PSBT).
```typescript
async getPSBT(params: GetPSBTParams): Promise<PSBTResponse>
```
**Parameters:**
- `orders` (RuneOrder[]): Array of orders to include (from fetchQuote)
- `address` (string): Bitcoin address
- `publicKey` (string): Bitcoin public key
- `paymentAddress` (string): Payment address
- `paymentPublicKey` (string): Payment public key
- `runeName` (string): Rune name
- `utxos?` (UTXO[]): Optional UTXOs to use
- `feeRate?` (number): Optional fee rate
- `slippage?` (number): Optional slippage percentage
- `themeID?` (string): Optional widget theme ID
- `sell?` (boolean): Optional flag for sell orders (default: false)
- `rbfProtection?` (boolean): Optional RBF protection flag (default: false)
**Example:**
```typescript
const psbtResponse = await satsTerminal.getPSBT({
orders: quote.selectedOrders,
address: "bc1...",
publicKey: "...",
paymentAddress: "3Pc...",
paymentPublicKey: "...",
feeRate: 5,
runeName: "LOBO•THE•WOLF•PUP",
slippage: 9,
rbfProtection: true
});
```
**Example Response:**
```javascript
{
marketplace: 'magiceden',
swapId: '0b0fef9zmab9-bocn3afkmr',
validOrders: [ '5b35441a-297b-45b4-8277-a3048309837b' ],
inputs: [ 0 ],
rbfProtected: {
base64: 'cHNidP8BAKkCAAAAASdY5eq28qE1btcTbldfKCNVaaBNEBo6EYpvVKrVBaGqAQAAAAD/////AyMqAAAAAAAAIlEg1pNbOdpsqSDkdCcK+uN7LUaajvtMQqFlLaugrMI2cvSSBwAAAAAAACJRINaTWznabKkg5HQnCvrjey1Gmo77TEKhZS2roKzCNnL0VbIAAAAAAAAXqRTwcmaf/igf0hSlNZd3ss2y7lyJpYcAAAAACPwCbWUDc2lnQHpbBxAVPvbM3I975/SZDbOrmdw+3KjUk0gxbYeYxwVqAHdIYFxtfXbHHW5lMHU51KNrFa+h0OFOfVHVpSHUL/gJ/AJtZQRwZmVlAzU4MAv8Am1lBnNpZ2V4cAhCeVg6nAVwABT8Am1lD3RyYW5zYWN0aW9uVHlwZQdyYmYtYnV5GPwCbWUTdGFrZXJQYXltZW50QWRkcmVzcyIzUGNQOUo2QzlaTlFwVFJIcnRxRkJoUDNMb0JqU21Na1pxGPwCbWUTdGFrZXJSZWNlaXZlQWRkcmVzcz5iYzFwdXhhMmN3dmZ3NDd2YWFkdDRsZnh3Z2w0emxuOWVwcXRmc3EzeTdjbDd2cjV3dmZheXo2czIybW13cAABASAS5QAAAAAAABepFPByZp/+KB/SFKU1l3eyzbLuXImlhwEEFgAUY9wnP/vXXCJ9d3tDwzvzOL1cNV8ACfwCbWUEbWZlZQQAAAAACfwCbWUEdGZlZQQAAABkEPwCbWULaXRlbUZlZVRpcHMFMTAwLDAQ/AJtZQtydW5lT3JkZXJJZCQ1YjM1NDQxYS0yOTdiLTQ1YjQtODI3Ny1hMzA0ODMwOTgzN2IAAAA=',
hex: '70736274ff0100a902000000012758e5eab6f2a1356ed7136e575f28235569a04d101a3a118a6f54aad505a1aa0100000000ffffffff03232a000000000000225120d6935b39da6ca920e474270afae37b2d469a8efb4c42a1652daba0acc23672f49207000000000000225120d6935b39da6ca920e474270afae37b2d469a8efb4c42a1652daba0acc23672f455b200000000000017a914f072669ffe281fd214a5359777b2cdb2ee5c89a5870000000008fc026d6503736967407a5b0710153ef6ccdc8f7be7f4990db3ab99dc3edca8d49348316d8798c7056a007748605c6d7d76c71d6e65307539d4a36b15afa1d0e14e7d51d5a521d42ff809fc026d650470666565033538300bfc026d6506736967657870084279583a9c05700014fc026d650f7472616e73616374696f6e54797065077262662d62757918fc026d651374616b65725061796d656e74416464726573732233506350394a3643395a4e517054524872747146426850334c6f426a536d4d6b5a7118fc026d651374616b657252656365697665416464726573733e6263317075786132637776667734377661616474346c667877676c347a6c6e3965707174667371337937636c3776723577766661797a367332326d6d77700001012012e500000000000017a914f072669ffe281fd214a5359777b2cdb2ee5c89a587010416001463dc273ffbd75c227d777b43c33bf338bd5c355f0009fc026d65046d666565040000000009fc026d650474666565040000006410fc026d650b6974656d46656554697073053130302c3010fc026d650b72756e654f7264657249642435623335343431612d323937622d343562342d383237372d613330343833303938333762000000',
inputs: [ 0 ]
}
}
```
#### confirmPSBT
Confirm and broadcast a signed PSBT.
```typescript
async confirmPSBT(params: ConfirmPSBTParams): Promise<ConfirmPSBTResponse>
```
**Parameters:**
- `orders` (RuneOrder[]): Array of orders (from fetchQuote)
- `address` (string): Bitcoin address
- `publicKey` (string): Bitcoin public key
- `paymentAddress` (string): Payment address
- `paymentPublicKey` (string): Payment public key
- `signedPsbtBase64` (string): Signed PSBT in base64 format
- `swapId` (string): Swap ID (from getPSBT)
- `themeID?` (string): Optional widget theme ID
- `sell?` (boolean): Optional flag for sell orders (default: false)
- `runeName?` (string): Optional rune name
- `marketplaces?` (string[]): Optional list of marketplaces to use
- `rbfProtection?` (boolean): Optional RBF protection flag (default: false)
- `signedRbfPsbtBase64?` (string): Optional signed RBF protection PSBT in base64 format
**Example:**
```typescript
const confirmation = await satsTerminal.confirmPSBT({
orders: quote.selectedOrders,
address: "bc1...",
publicKey: "...",
paymentAddress: "3Pc...",
paymentPublicKey: "...",
signedPsbtBase64: "cHNid...",
signedRbfPsbtBase64: "cHNid...", // Only if rbfProtection is true
swapId: psbtResponse.swapId,
runeName: "LOBO•THE•WOLF•PUP",
rbfProtection: true
});
```
**Example Response:**
```javascript
{
marketplace: 'magiceden',
txid: '4fb3ae350ae4366687a1e069b1d4de528f3dc9dc097382de041e979a8187312b',
rbfProtection: {
fundsPreparationTxId: '4fb3ae350ae4366687a1e069b1d4de528f3dc9dc097382de041e979a8187312b',
fulfillmentId: 'bd85a36b-7a54-458a-8585-e3af2b1a8fe1'
},
isRbfTxid: true
}
```
## Example Flow
Below is a complete example flow for buying runes with RBF protection, including waiting for user input for signed PSBTs:
```javascript
import { SatsTerminal } from 'satsterminal-sdk';
import readlineSync from 'readline-sync';
// Configuration
const CONFIG = {
apiKey: 'your_api_key_here',
baseURL: 'https://api.satsterminal.com/v1',
timeout: 30000 // 30 seconds timeout
};
// Trade Parameters
const TRADE_PARAMS = {
runeName: 'LOBO•THE•WOLF•PUP',
address: 'bc1p...',
publicKey: '...',
paymentAddress: '3Pc...',
paymentPublicKey: '...',
btcAmount: 0.0001,
rbfProtection: true,
marketplaces: ["MagicEden"],
fill: true
};
// Initialize the SDK
const satsTerminal = new SatsTerminal(CONFIG);
// Function to get user input for signed PSBT
const getSignedPSBT = () => {
console.log('\nPlease sign the PSBT and enter the signed PSBT base64 string:');
return readlineSync.question('Signed PSBT: ');
};
// Function to get user input for signed RBF PSBT
const getSignedRbfPSBT = () => {
console.log('\nPlease sign the RBF protection PSBT and enter the signed RBF PSBT base64 string:');
return readlineSync.question('Signed RBF PSBT: ');
};
// Main execution
(async () => {
try {
// 1. Get a quote for the trade
console.log('\n1. Fetching quote...');
const quote = await satsTerminal.fetchQuote({
btcAmount: TRADE_PARAMS.btcAmount,
runeName: TRADE_PARAMS.runeName,
address: TRADE_PARAMS.address,
marketplaces: TRADE_PARAMS.marketplaces,
rbfProtection: TRADE_PARAMS.rbfProtection,
fill: TRADE_PARAMS.fill
});
console.log('Best marketplace:', quote.bestMarketplace);
console.log('Quote:', quote.selectedOrders);
// 2. Create a PSBT
console.log('\n2. Creating PSBT...');
const psbtResponse = await satsTerminal.getPSBT({
orders: quote.selectedOrders,
address: TRADE_PARAMS.address,
publicKey: TRADE_PARAMS.publicKey,
paymentAddress: TRADE_PARAMS.paymentAddress,
paymentPublicKey: TRADE_PARAMS.paymentPublicKey,
utxos: [],
feeRate: 5,
runeName: TRADE_PARAMS.runeName,
themeID: null,
sell: false,
slippage: 9,
rbfProtection: TRADE_PARAMS.rbfProtection
});
console.log('PSBT created:', psbtResponse);
// Wait for user to input the signed PSBT
const signedPsbtBase64 = getSignedPSBT();
// Get signed RBF PSBT if RBF protection is enabled
let signedRbfPsbtBase64 = null;
if (TRADE_PARAMS.rbfProtection && psbtResponse.rbfProtected.base64) {
console.log('\nRBF Protection PSBT:', psbtResponse.rbfProtected.base64);
signedRbfPsbtBase64 = getSignedRbfPSBT();
}
// 3. Confirm the PSBT after signing
console.log('\n3. Confirming PSBT...');
const confirmation = await satsTerminal.confirmPSBT({
orders: quote.selectedOrders,
address: TRADE_PARAMS.address,
publicKey: TRADE_PARAMS.publicKey,
paymentAddress: TRADE_PARAMS.paymentAddress,
paymentPublicKey: TRADE_PARAMS.paymentPublicKey,
signedRbfPsbtBase64: signedRbfPsbtBase64,
swapId: psbtResponse.swapId,
runeName: TRADE_PARAMS.runeName,
rbfProtection: TRADE_PARAMS.rbfProtection,
signedPsbtBase64: signedPsbtBase64
});
console.log('Confirmation:', confirmation);
} catch (error) {
console.error('Error:', error.message);
// Provide more helpful information for network errors
if (error.code === 'ECONNREFUSED') {
console.error('Connection refused. Make sure the API server is running and accessible.');
} else if (error.code === 'ECONNRESET' || error.message.includes('socket hang up')) {
console.error('Connection was reset or timed out. This could be due to:');
console.error('- Network connectivity issues');
console.error('- Server overload or maintenance');
console.error('- Invalid API key or authentication');
console.error('- Request timeout (the operation might take longer than expected)');
}
if (process.env.NODE_ENV === 'development') {
console.error('Full error:', error);
}
}
})();
```
## Error Handling
The SDK throws errors with descriptive messages. Always wrap API calls in try-catch blocks:
```typescript
try {
const result = await satsTerminal.search({ rune_name: 'PEPE' });
} catch (error) {
if (error.message.includes('API key')) {
console.error('Invalid API key');
} else if (error.code === 'ECONNRESET' || error.message.includes('socket hang up')) {
console.error('Network connection issue. Please try again.');
} else {
console.error('An error occurred:', error);
}
}
```
## Common Issues
### Invalid API Key
Ensure your API key is valid and properly configured. Only approved partners can use the SDK.
### Network Errors
- Check your internet connection
- Verify the API endpoint is accessible
- Check if your firewall is blocking requests
- Consider increasing the timeout value in the configuration
### PSBT Errors
- Ensure all required parameters are provided
- Ensure the PSBT is valid and signed
- Verify the format of public keys and addresses
- Check if UTXOs are valid and confirmed
### RBF Protection Issues
- Make sure both the main PSBT and RBF protection PSBT are properly signed
- Verify that the RBF protection flag is set to true in all relevant method calls
- Ensure the signed RBF PSBT is passed to the confirmPSBT method
For additional support, contact our support team.