├── src ├── types │ ├── options.ts │ ├── mempool_types.ts │ ├── satscanner_types.ts │ ├── satextractor_types.ts │ ├── index.ts │ ├── runes_types.ts │ └── tokenpay_types.ts ├── inscription │ ├── error.ts │ └── index.ts ├── satextractor │ ├── index.ts │ └── client.ts ├── mempool │ ├── index.ts │ └── client.ts ├── satscanner │ ├── index.ts │ └── client.ts ├── index.ts ├── tokenpay │ ├── client.ts │ └── index.ts ├── marketplaceClient.ts └── launchpad │ └── client.ts ├── .npmignore ├── .github └── workflows │ ├── test.yml │ └── npm-publish.yml ├── .gitignore ├── tsconfig.json ├── test ├── marketplace │ ├── confirmDeList.js │ ├── confirmReList.js │ ├── reListing.js │ ├── confirmListing.js │ ├── getListing.js │ ├── l402.js │ ├── deList.js │ └── transferOrdinals.js ├── launchpad │ ├── submitLaunchpadOffer.js │ ├── saveLaunchpad.js │ ├── getLaunchpadPSBT.js │ ├── createMarketPlace.js │ ├── confirmPaddingOutputs.js │ ├── getAllocation.js │ ├── setupPaddingOutputs.js │ ├── l402.js │ ├── createLaunchpadOffer.js │ ├── createLaunchpad.js │ └── getLaunchpadListing.js ├── inscription │ ├── createSpecialSats.js │ ├── getAllocation.js │ ├── l402.js │ └── createParentChildPSBT.js ├── tokenpay │ ├── createPaymentPSBT.js │ ├── order.js │ └── accountWithdraw.js ├── runes.js ├── mempool.js ├── satextractor.js ├── marketplace.js └── satscanner.js ├── package.json ├── examples ├── l402.ts └── example.ts ├── CHANGELOG └── README.md /src/types/options.ts: -------------------------------------------------------------------------------- 1 | import { Store, Wallet } from "l402"; 2 | 3 | // Define the options interface 4 | export interface ClientOptions { 5 | useL402?: boolean; 6 | l402Config?: { 7 | wallet: Wallet; 8 | tokenStore: Store; 9 | }; 10 | } -------------------------------------------------------------------------------- /src/types/mempool_types.ts: -------------------------------------------------------------------------------- 1 | export interface MempoolAddressUtxoResponse { 2 | txid: string; 3 | vout: number; 4 | status: { 5 | confirmed: boolean; 6 | block_height: number; 7 | block_hash: string; 8 | block_time: number; 9 | }; 10 | value: number; 11 | } 12 | 13 | export interface RecommendedFees { 14 | fastestFee: number; 15 | halfHourFee: number; 16 | hourFee: number; 17 | economyFee: number; 18 | minimumFee: number; 19 | } 20 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.log 2 | npm-debug.log* 3 | 4 | # Coverage directory used by tools like istanbul 5 | coverage 6 | .nyc_output 7 | 8 | # Dependency directories 9 | node_modules 10 | 11 | # package lock 12 | package-lock.json 13 | pnpm-lock.json 14 | yarn.lock 15 | 16 | # project files 17 | test 18 | examples 19 | CHANGELOG.md 20 | .babelrc 21 | .DS_Store 22 | .editorconf 23 | .editorconfig 24 | .eslintignore 25 | .eslintrc 26 | .gitignore 27 | .mocharc.json 28 | .travis.yml 29 | tsconfig.json -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test, Build 2 | 3 | on: 4 | push: 5 | branches: [ "main", "master", "dev" ] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Setup Node.js environment 17 | uses: actions/setup-node@v2 18 | with: 19 | node-version: 18 20 | 21 | - name: Install dependencies 22 | run: npm i 23 | 24 | - name: Test 25 | run: npm test -------------------------------------------------------------------------------- /src/inscription/error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom error class for inscription errors. 3 | */ 4 | export class InscriptionError extends Error { 5 | /** 6 | * Creates an instance of InscriptionError. 7 | * @param {string | undefined} message The error message. 8 | * @param {string} [name='unknown'] The error name. 9 | * @param {number} [status] The HTTP status code associated with the error. 10 | */ 11 | constructor( 12 | message: string | undefined, 13 | public name: string = "unknown", 14 | public status?: number 15 | ) { 16 | super(message); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | .nyc_output 17 | 18 | # Compiled binary addons (http://nodejs.org/api/addons.html) 19 | build/Release 20 | 21 | # Dependency directories 22 | node_modules 23 | jspm_packages 24 | 25 | # Optional npm cache directory 26 | .npm 27 | 28 | # Optional REPL history 29 | .node_repl_history 30 | 31 | # Editors 32 | .idea 33 | 34 | others 35 | .DS_Store 36 | 37 | # build dir 38 | dist 39 | 40 | # other lockfiles 41 | yarn.lock 42 | package-lock.json 43 | .vscode/settings.json 44 | 45 | *.env 46 | .vscode/ 47 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "moduleResolution": "node", 5 | "target": "ES2020", 6 | "strictNullChecks": true, 7 | "strictFunctionTypes": true, 8 | "outDir": "dist", 9 | "rootDir": "src", 10 | "sourceMap": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "pretty": false, 14 | "removeComments": true, 15 | "noImplicitReturns": true, 16 | "noImplicitAny": true, 17 | "allowJs": false, 18 | "alwaysStrict": true, 19 | "resolveJsonModule": true, 20 | "esModuleInterop": true 21 | }, 22 | "files": [ 23 | "src/index.ts", 24 | ], 25 | "include": [ 26 | "src/" 27 | ], 28 | "exclude": [ 29 | "node_modules", 30 | "**/node_modules/*" 31 | ] 32 | } -------------------------------------------------------------------------------- /src/types/satscanner_types.ts: -------------------------------------------------------------------------------- 1 | export interface SatscannerSpecialRangesUtxoRequest { 2 | utxos: string[]; 3 | } 4 | 5 | export interface SatscannerSpecialRangesRequest { 6 | address: string; 7 | } 8 | 9 | export interface SatscannerSpecialRangesResponse { 10 | inscriptions: InscriptionEntry[]; 11 | ranges: Range[]; 12 | specialRanges: SpecialRange[]; 13 | } 14 | 15 | export interface InscriptionEntry { 16 | output: string; 17 | inscriptions: string[]; 18 | } 19 | 20 | export interface Range { 21 | output: string; 22 | start: number; 23 | end: number; 24 | size: number; 25 | offset: number; 26 | rarity: string; 27 | } 28 | 29 | export interface SpecialRange { 30 | start: number; 31 | output: string; 32 | size: number; 33 | offset: number; 34 | satributes: string[]; 35 | } 36 | -------------------------------------------------------------------------------- /src/types/satextractor_types.ts: -------------------------------------------------------------------------------- 1 | import { SpecialRange } from "./satscanner_types"; 2 | 3 | export interface SatextractorExtractRequest { 4 | // address to scan for special sats 5 | scanAddress: string; 6 | 7 | // address to send special sats after extraction (i.e. ordinals address) 8 | addressToSendSpecialSats: string; 9 | 10 | // address to send common sats after extraction (i.e. payment address) 11 | addressToSendCommonSats: string; 12 | 13 | // fee per byte in satoshis to use for extraction transaction 14 | feePerByte: number; 15 | 16 | // Array of strings, if supplied we will only detect the selected satributes; Everything else will be sent to the common sats address. 17 | filterSatributes: string[]; 18 | } 19 | 20 | export interface SatextractorExtractResponse { 21 | specialRanges: SpecialRange[]; 22 | 23 | // unsigned transaction to be signed by the scanAddress wallet 24 | tx: string; 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: Package & Publish 2 | 3 | on: 4 | push: 5 | branches: [ "main", "master" ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | 14 | - name: Setup Node.js environment 15 | uses: actions/setup-node@v2 16 | with: 17 | node-version: 18 18 | 19 | - name: Install dependencies 20 | run: npm i 21 | 22 | - name: Test 23 | run: npm test 24 | 25 | publish-npm: 26 | needs: build 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v3 30 | - uses: actions/setup-node@v3 31 | with: 32 | node-version: 18 33 | registry-url: https://registry.npmjs.org/ 34 | - uses: pnpm/action-setup@v2 35 | with: 36 | version: 8 37 | - run: npm i 38 | - run: npm publish 39 | env: 40 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * as v1 from "./v1"; 2 | 3 | /** 4 | * Represents the different environment options available. 5 | */ 6 | export type InscriptionEnv = "live" | "dev" | "mainnet" | "signet" | "testnet"; 7 | 8 | /** 9 | * Export ClientOptions from the options module. 10 | * @module options 11 | */ 12 | export { ClientOptions } from "./options"; 13 | 14 | /** 15 | * Enum for mapping InscriptionEnv values to their respective network names. 16 | * @enum {string} 17 | */ 18 | export enum InscriptionEnvNetwork { 19 | dev = "testnet", 20 | testnet = "testnet", 21 | live = "mainnet", 22 | mainnet = "mainnet", 23 | signet = "signet", 24 | } 25 | 26 | /** 27 | * Enum for mapping InscriptionEnv values to their respective network explorer URLs. 28 | * @enum {string} 29 | */ 30 | export enum EnvNetworkExplorer { 31 | dev = "https://testnet-api.ordinalsbot.com", 32 | live = "https://api.ordinalsbot.com", 33 | testnet = "https://testnet-api.ordinalsbot.com", 34 | mainnet = "https://api.ordinalsbot.com", 35 | signet = "https://signet-api.ordinalsbot.com", 36 | } 37 | -------------------------------------------------------------------------------- /test/marketplace/confirmDeList.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { MarketPlace } = require('../../dist') 4 | const { MarketPlaceClient } = require('../../dist/marketplaceClient') 5 | 6 | const sandbox = sinon.createSandbox() 7 | describe('Marketplace Confirm DeListing', function () { 8 | afterEach(() => { 9 | sandbox.restore() 10 | }) 11 | 12 | it('should return success message', async () => { 13 | const confirmDeListingRequest = { 14 | ordinalId: 'someOrdinalId', 15 | sellerPaymentAddress: 'someSellerPaymentAddress', 16 | } 17 | const mockResponse = { 18 | message: 'Ordinal successfully delisted', 19 | } 20 | 21 | sinon 22 | .stub(MarketPlaceClient.prototype, 'confirmDeListing') 23 | .callsFake(() => mockResponse) 24 | 25 | const marketPlace = new MarketPlace('someApiKey', 'testnet') 26 | const response = await marketPlace.confirmDeListing(confirmDeListingRequest) 27 | 28 | expect(response).to.equal(mockResponse) 29 | expect(response.message).to.be.a('string') 30 | expect(MarketPlaceClient.prototype.confirmDeListing.calledOnce).to.be.true 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /test/marketplace/confirmReList.js: -------------------------------------------------------------------------------- 1 | const { assert, expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { MarketPlace } = require('../../dist') 4 | const { MarketPlaceClient } = require('../../dist/marketplaceClient') 5 | 6 | const sandbox = sinon.createSandbox() 7 | describe('Marketplace Confirm ReListing', function () { 8 | afterEach(() => { 9 | sandbox.restore() 10 | }) 11 | 12 | it('should return success message', async () => { 13 | const confirmReListingRequest = { 14 | ordinalId: 'someOrdinalId', 15 | signedListingPSBT: 'someSignedPSBTString', 16 | } 17 | const mockResponse = { 18 | message: 'Signed PSBT is updated successfully', 19 | } 20 | 21 | sinon 22 | .stub(MarketPlaceClient.prototype, 'confirmReListing') 23 | .callsFake(() => mockResponse) 24 | 25 | const marketPlace = new MarketPlace('someApiKey', 'testnet') 26 | const response = await marketPlace.confirmReListing(confirmReListingRequest) 27 | 28 | expect(response).to.equal(mockResponse) 29 | expect(response.message).to.be.a('string') 30 | expect(MarketPlaceClient.prototype.confirmReListing.calledOnce).to.be.true 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /test/marketplace/reListing.js: -------------------------------------------------------------------------------- 1 | const { assert, expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { MarketPlace } = require('../../dist') 4 | const { WALLET_PROVIDER } = require('../../dist/types/marketplace_types') 5 | 6 | const sandbox = sinon.createSandbox() 7 | describe('ReList ordinal', function () { 8 | it('should handle the relisting process', async () => { 9 | const marketPlace = new MarketPlace('someApiKey', 'testnet') 10 | const createListingStub = sandbox.stub(marketPlace, 'reListing').resolves({ 11 | psbt: 'test_psbt', 12 | }) 13 | 14 | // Constructing a mock request based on MarketplaceReListingRequest type 15 | const mockListingRequest = { 16 | ordinalId: 'existingOrdinalId', 17 | price: 1234, 18 | sellerPaymentAddress: 'somePaymentAddress', 19 | sellerOrdinalPublicKey: 'someSellerOrdinalPublicKey', 20 | sellerOrdinalAddress: 'someSellerOrdinalAddress', 21 | walletProvider: WALLET_PROVIDER.xverse, 22 | } 23 | 24 | const response = await marketPlace.reListing(mockListingRequest) 25 | expect(response).to.have.property('psbt').that.equals('test_psbt') 26 | sinon.assert.calledWith(createListingStub, sinon.match(mockListingRequest)) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /test/marketplace/confirmListing.js: -------------------------------------------------------------------------------- 1 | const { assert, expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { MarketPlace } = require('../../dist') 4 | const { MarketPlaceClient } = require('../../dist/marketplaceClient') 5 | 6 | const sandbox = sinon.createSandbox() 7 | describe('Marketplace Confirm Listing', function () { 8 | afterEach(() => { 9 | sandbox.restore() 10 | }) 11 | 12 | it('should return success message', async () => { 13 | 14 | const sellerOrdinals = ['someOrdinalId1', 'someOrdinalId2'] 15 | const confirmListingRequest = { 16 | sellerOrdinals: sellerOrdinals, 17 | signedListingPSBT: 'someSignedPSBTString', 18 | } 19 | const mockResponse = { 20 | message: 'Signed PSBT is updated successfully', 21 | } 22 | 23 | sinon 24 | .stub(MarketPlaceClient.prototype, 'confirmListing') 25 | .callsFake(() => mockResponse) 26 | 27 | const marketPlace = new MarketPlace('someApiKey', 'testnet') 28 | const response = await marketPlace.confirmListing(confirmListingRequest) 29 | 30 | expect(response).to.equal(mockResponse) 31 | expect(response.message).to.be.a('string') 32 | expect(MarketPlaceClient.prototype.confirmListing.calledOnce).to.be.true 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /test/launchpad/submitLaunchpadOffer.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { Launchpad } = require('../../dist/launchpad/index') 4 | const { LaunchpadClient } = require('../../dist/launchpad/client') 5 | 6 | describe('Submit Launchpad Offer', function () { 7 | afterEach(() => { 8 | sinon.restore() 9 | }) 10 | 11 | it('should return broadcast transaction txId', async () => { 12 | // construct request payload SubmitLaunchpadOfferRequest 13 | const submitLaunchpadOfferRequest = { 14 | ordinalId: 'someLaunchpadId', 15 | launchpadPhase: 'someLaunchpadPhase', 16 | signedBuyerPSBTBase64: 'someSignedPSBTString', 17 | } 18 | 19 | // construct request response SubmitLaunchpadOfferResponse 20 | sinon.stub(LaunchpadClient.prototype, 'submitLaunchpadOffer').resolves({ 21 | txId: 'someTransactionString', 22 | }) 23 | 24 | const launchpad = new Launchpad('someApiKey', 'testnet') 25 | const response = await launchpad.submitLaunchpadOffer( 26 | submitLaunchpadOfferRequest 27 | ) 28 | 29 | expect(response).to.be.a('object') 30 | expect(response.txId).to.be.a('string') 31 | expect(LaunchpadClient.prototype.submitLaunchpadOffer.calledOnce).to.be.true 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/launchpad/saveLaunchpad.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { Launchpad } = require('../../dist/launchpad/index') 4 | const { LaunchpadClient } = require('../../dist/launchpad/client') 5 | 6 | describe('Save Launchpad', function () { 7 | afterEach(() => { 8 | sinon.restore() 9 | }) 10 | 11 | it('should return success after successfully update the psbt on the launchpad', async () => { 12 | // construct request payload SaveLaunchpadRequest 13 | const saveLaunchpadRequest = { 14 | launchpadId: 'someLaunchpadId', 15 | updateLaunchData: { 16 | /** signed psbt by the seller to updated */ 17 | signedListingPSBT: 'someSignedPSBTString', 18 | }, 19 | } 20 | 21 | // construct request response SaveLaunchpadResponse 22 | sinon.stub(LaunchpadClient.prototype, 'saveLaunchpad').resolves({ 23 | message: 'Launchpad listing is updated successfully', 24 | }) 25 | 26 | const launchpad = new Launchpad('someApiKey', 'testnet') 27 | const response = await launchpad.saveLaunchpad(saveLaunchpadRequest) 28 | 29 | expect(response).to.be.a('object') 30 | expect(response.message).to.be.a('string') 31 | expect(LaunchpadClient.prototype.saveLaunchpad.calledOnce).to.be.true 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/inscription/createSpecialSats.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { Inscription, InscriptionClient } = require('../../dist') 4 | 5 | const sandbox = sinon.createSandbox() 6 | describe('Create Special Sats PSBT', function () { 7 | afterEach(() => { 8 | sandbox.restore() 9 | }) 10 | 11 | it('should return psbt for special sats', async () => { 12 | const createSpecialSatsPSBTRequest = { 13 | chargeAmount: 2000, 14 | fundingAddress: 'someFundingAddress', 15 | specialSatsOutput: 'someSpecialSatsOutput', 16 | userAddress: 'someUserPaymentAddress', 17 | userPublicKey: 'someUserPaymentPublicKey', 18 | feeRate: 100, 19 | } 20 | 21 | const mockResponse = { 22 | psbt: 'somePSBTString', 23 | } 24 | 25 | sinon 26 | .stub(InscriptionClient.prototype, 'createSpecialSatsPSBT') 27 | .callsFake(() => mockResponse) 28 | 29 | const inscription = new Inscription('someApiKey', 'testnet') 30 | const response = await inscription.createSpecialSatsPSBT( 31 | createSpecialSatsPSBTRequest 32 | ) 33 | 34 | expect(response).to.equal(mockResponse) 35 | expect(response.psbt).to.be.a('string') 36 | expect(InscriptionClient.prototype.createSpecialSatsPSBT.calledOnce).to.be.true 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /test/launchpad/getLaunchpadPSBT.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { Launchpad } = require('../../dist/launchpad/index') 4 | const { LaunchpadClient } = require('../../dist/launchpad/client') 5 | 6 | describe('Get Launchpad Status', function () { 7 | 8 | afterEach(() => { 9 | sinon.restore() 10 | }) 11 | 12 | it('should return status with the psbt ready to sign', async () => { 13 | const clock = sinon.useFakeTimers() 14 | 15 | const getLaunchpadPSBTRequest = { 16 | launchpadId: 'someNamestring', 17 | status: 'pending', 18 | } 19 | 20 | const mockResponse = { 21 | psbt: 'encodedPSBTString', 22 | status: 'Pending Buyer Confirmation', 23 | } 24 | 25 | sinon 26 | .stub(LaunchpadClient.prototype, 'getLaunchpadStatus') 27 | .resolves(mockResponse) 28 | 29 | const launchpad = new Launchpad('someApiKey', 'testnet') 30 | const response = await launchpad.getLaunchpadPSBT(getLaunchpadPSBTRequest) 31 | clock.tick(5 * 60 * 1000) 32 | expect(response).to.be.a('object') 33 | expect(response.psbt).to.be.a('string') 34 | expect(response.status).to.be.a('string') 35 | expect(LaunchpadClient.prototype.getLaunchpadStatus.calledOnce).to.be.true 36 | clock.restore() 37 | }) 38 | 39 | afterEach(() => { 40 | sinon.restore() 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ordinalsbot", 3 | "version": "0.2.16", 4 | "description": "Node.js library for OrdinalsBot API", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "pnpm build && nyc mocha 'test/**/*.js'", 8 | "build": "rm -rf dist/ && tsc", 9 | "prepack": "pnpm build", 10 | "examples": "npx ts-node examples/example.ts" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/ordinalsbot/ordinalsbot-node.git" 15 | }, 16 | "author": "OrdinalsBot", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/ordinalsbot/ordinalsbot-node/issues" 20 | }, 21 | "homepage": "https://github.com/ordinalsbot/ordinalsbot-node#readme", 22 | "dependencies": { 23 | "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", 24 | "axios": "^0.28.0", 25 | "bitcoinjs-lib": "^6.1.5", 26 | "dotenv": "^16.4.5", 27 | "l402": "^0.5.1", 28 | "sats-connect": "^1.1.2", 29 | "uuid": "^9.0.1" 30 | }, 31 | "devDependencies": { 32 | "@types/node": "^18.15.3", 33 | "chai": "^4.3.6", 34 | "mocha": "^9.2.2", 35 | "nock": "^13.5.4", 36 | "nyc": "^15.1.0", 37 | "pnpm": "^8.6.12", 38 | "qs": "^6.11.2", 39 | "sinon": "^17.0.1", 40 | "typescript": "^5.4.5" 41 | }, 42 | "keywords": [ 43 | "bitcoin", 44 | "lightning network", 45 | "ordinals", 46 | "api", 47 | "ordinalsbot", 48 | "inscriptions", 49 | "buy", 50 | "sell" 51 | ] 52 | } -------------------------------------------------------------------------------- /test/launchpad/createMarketPlace.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { Launchpad } = require('../../dist/launchpad/index') 4 | const { LaunchpadClient } = require('../../dist/launchpad/client') 5 | 6 | describe('Launchpad Create Marketplace', function () { 7 | afterEach(() => { 8 | sinon.restore() 9 | }) 10 | 11 | it('should return marketplace id and api key', async () => { 12 | // construct request payload LaunchpadMarketplaceCreateRequest 13 | const createMarketPlaceRequest = { 14 | name: 'someNamestring', 15 | launchpadSellerFee: 500, 16 | launchpadBuyerFee: 501, 17 | launchpadBtcFeePayoutAddress: 'someBtcFeeAddres', 18 | url: 'someUrlString', 19 | description: 'someTextForMarketPlace', 20 | } 21 | 22 | // construct request response LaunchpadMarketplaceCreateResponse 23 | const mockResponse = { 24 | marketPlaceId: 'someMarketPlaceId', 25 | apiKey: 'someApiKey', 26 | } 27 | 28 | sinon 29 | .stub(LaunchpadClient.prototype, 'createMarketPlace') 30 | .callsFake(() => mockResponse) 31 | 32 | const launchpad = new Launchpad('someApiKey', 'testnet') 33 | const response = await launchpad.createMarketPlace(createMarketPlaceRequest) 34 | 35 | expect(response).to.equal(mockResponse) 36 | expect(response.marketPlaceId).to.be.a('string') 37 | expect(response.apiKey).to.be.a('string') 38 | expect(LaunchpadClient.prototype.createMarketPlace.calledOnce).to.be.true 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /test/marketplace/getListing.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { MarketPlace } = require('../../dist') 4 | const { LISTING_STATUS } = require('../../dist/types/marketplace_types') 5 | 6 | const sandbox = sinon.createSandbox() 7 | describe('Marketplace getListing Ordinal', function () { 8 | afterEach(() => { 9 | sandbox.restore() 10 | }) 11 | 12 | it('should return the listing response', async () => { 13 | const marketPlace = new MarketPlace('someApiKey', 'testnet') 14 | const mockResponse = { 15 | results: [ 16 | { 17 | ordinalId: 'someOrdinalId', 18 | price: 1200, 19 | }, 20 | ], 21 | count: 1, 22 | currentPage: 1, 23 | totalPages: 1, 24 | totalItems: 1, 25 | } 26 | const listingStub = sandbox 27 | .stub(marketPlace, 'getListing') 28 | .resolves(mockResponse) 29 | 30 | // Constructing a mock request based on MarketplaceGetListingResponse type 31 | const mockRequest = { 32 | filter: { status: LISTING_STATUS.active }, 33 | page: 1, 34 | itemsPerPage: 100, 35 | sort: 'time', 36 | } 37 | 38 | const response = await marketPlace.getListing(mockRequest) 39 | 40 | expect(response).to.have.property('results').that.an('array') 41 | 42 | expect(response).to.have.property('count').that.a('number') 43 | 44 | expect(response).to.have.property('currentPage').that.a('number') 45 | 46 | expect(response).to.have.property('totalPages').that.a('number') 47 | 48 | expect(response).to.have.property('totalItems').that.a('number') 49 | 50 | sinon.assert.calledWith(listingStub, sinon.match(mockRequest)) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /src/satextractor/index.ts: -------------------------------------------------------------------------------- 1 | import { SatextractorClient } from "./client"; 2 | import { ClientOptions, InscriptionEnv, InscriptionEnvNetwork } from "../types"; 3 | import { 4 | SatextractorExtractRequest, 5 | SatextractorExtractResponse, 6 | } from "../types/satextractor_types"; 7 | 8 | /** 9 | * A interface for interacting with the Satextractor API. 10 | */ 11 | export class Satextractor { 12 | /** 13 | * The underlying SatextractorClient instance. 14 | */ 15 | private satextractorInstance!: SatextractorClient; 16 | 17 | /** 18 | * Creates a new Satextractor instance. 19 | * @param {string} [key=''] - The API key for authentication. 20 | * @param {InscriptionEnv} [environment='mainnet'] - The environment (e.g., "testnet" , "mainnet", "signet") (optional, defaults to mainnet). 21 | * @param {ClientOptions} [options] - Options for enabling L402 support. 22 | */ 23 | constructor(key: string = "", environment: InscriptionEnv = InscriptionEnvNetwork.mainnet, options?: ClientOptions) { 24 | if (this.satextractorInstance !== undefined) { 25 | console.error("satextractor.setCredentials was called multiple times"); 26 | return; 27 | } 28 | environment = InscriptionEnvNetwork[environment]??InscriptionEnvNetwork.mainnet; 29 | this.satextractorInstance = new SatextractorClient(key, environment, options); 30 | } 31 | 32 | /** 33 | * Extracts data using the Satextractor API. 34 | * @param {SatextractorExtractRequest} extractRequest - The request object for data extraction. 35 | * @returns {Promise} A promise that resolves to the extraction response. 36 | */ 37 | extract( 38 | extractRequest: SatextractorExtractRequest 39 | ): Promise { 40 | return this.satextractorInstance.extract(extractRequest); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/l402.ts: -------------------------------------------------------------------------------- 1 | import { AlbyWallet, MemoryTokenStore } from "l402"; 2 | const { Client } = require('@getalby/sdk'); 3 | import { ClientOptions, Satscanner } from "../src"; 4 | require('dotenv').config(); 5 | 6 | // Load the Alby token from an environment variable 7 | const albyToken = process.env.ALBY_BEARER_TOKEN; 8 | if (!albyToken) { 9 | console.error('Missing ALBY_BEARER_TOKEN environment variable.'); 10 | process.exit(1); 11 | } 12 | 13 | const albyClient = new Client(albyToken) 14 | 15 | // Initialize the AlbyWallet using the bearer token 16 | const albyWallet = new AlbyWallet(albyClient); 17 | 18 | // Initialize the MemoryTokenStore 19 | const store = new MemoryTokenStore({ 20 | keyMode: 'hostname-only' // this is IMPORTANT, since all endpoints are monetized 21 | // using the same hostname-level package ie. the same 22 | // L402 token can be used for all endpoints. 23 | }); 24 | 25 | // Create Options 26 | const options: ClientOptions = { 27 | useL402: true, 28 | l402Config: { 29 | wallet: albyWallet, 30 | tokenStore: store 31 | } 32 | }; 33 | 34 | /** 35 | * Create a new Satscanner instance 36 | * Setup your API Key and environment 37 | * Allowed environments are ('testnet', 'mainnet', 'signet') 38 | * default environment is 'mainnet'. 39 | */ 40 | const satscanner = new Satscanner("", "mainnet", options); 41 | 42 | /** 43 | * use satscanner to get information about utxos owned by an address 44 | */ 45 | 46 | /** 47 | * Using promises 48 | */ 49 | (async () => { 50 | try { 51 | const response = await satscanner.findSpecialRanges({ address: "bc1pjqmzr4ad437ltvfyn8pslcy8quls9ujfkrudpz6qxdh2j75qrncq44mp47" }); 52 | console.log(response); 53 | } catch (error) { 54 | console.error(`${error.status} | ${error.message} | ${error.data}`); 55 | } 56 | })(); -------------------------------------------------------------------------------- /src/mempool/index.ts: -------------------------------------------------------------------------------- 1 | import { MempoolClient } from "./client"; 2 | import { ClientOptions, InscriptionEnv, InscriptionEnvNetwork } from "../types"; 3 | import { 4 | MempoolAddressUtxoResponse, 5 | RecommendedFees, 6 | } from "../types/mempool_types"; 7 | 8 | /** 9 | * A interface for interacting with the Mempool API. 10 | */ 11 | export class Mempool { 12 | /** 13 | * The MempoolClient instance. 14 | */ 15 | private mempoolInstance!: MempoolClient; 16 | 17 | /** 18 | * Creates a new Mempool instance. 19 | * @param key The API key (optional). 20 | * @param {InscriptionEnv} [environment='mainnet'] - The environment (e.g., "testnet" , "mainnet", "signet") (optional, defaults to mainnet). 21 | * @param {ClientOptions} [options] - Options for enabling L402 support. 22 | */ 23 | constructor(key: string = "", environment: InscriptionEnv = InscriptionEnvNetwork.mainnet, options?: ClientOptions) { 24 | if (this.mempoolInstance !== undefined) { 25 | console.error("mempool.setCredentials was called multiple times"); 26 | return; 27 | } 28 | environment = InscriptionEnvNetwork[environment]??InscriptionEnvNetwork.mainnet; 29 | this.mempoolInstance = new MempoolClient(key, environment, options); 30 | } 31 | 32 | /** 33 | * Gets the recommended fee estimation from the Mempool API. 34 | * @returns A promise that resolves to the recommended fees. 35 | */ 36 | getFeeEstimation(): Promise { 37 | return this.mempoolInstance.getFeeEstimation(); 38 | } 39 | 40 | /** 41 | * Gets the UTXO (unspent transaction outputs) for a given address from the Mempool API. 42 | * @param address The address for which to fetch UTXO. 43 | * @returns A promise that resolves to an array of UTXO responses. 44 | */ 45 | getAddressUtxo(address: string): Promise { 46 | return this.mempoolInstance.getAddressUtxo(address); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/launchpad/confirmPaddingOutputs.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { Launchpad } = require('../../dist/launchpad/index') 4 | const { LaunchpadClient } = require('../../dist/launchpad/client') 5 | 6 | describe('Confirm Padding Outputs ', function () { 7 | afterEach(() => { 8 | sinon.restore() 9 | }) 10 | 11 | it('Should return true if the buyer has padding Outputs', async () => { 12 | // construct request payload confirmPaddingOutputsRequest 13 | const confirmPaddingOutputsRequest = { 14 | address: 'paymentAddress', 15 | } 16 | 17 | const mockResponse = { 18 | paddingOutputsExist: true, 19 | } 20 | // construct request response confirmPaddingOutputsResponse 21 | sinon 22 | .stub(LaunchpadClient.prototype, 'confirmPaddingOutputs') 23 | .resolves(mockResponse) 24 | 25 | const launchpad = new Launchpad('someApiKey', 'testnet') 26 | const response = await launchpad.confirmPaddingOutputs( 27 | confirmPaddingOutputsRequest 28 | ) 29 | 30 | expect(response).to.equal(mockResponse) 31 | expect(response.paddingOutputsExist).to.be.a('boolean') 32 | expect(LaunchpadClient.prototype.confirmPaddingOutputs.calledOnce).to.be 33 | .true 34 | sinon.restore() 35 | }) 36 | 37 | it('Should return false if the buyer has padding Outputs', async () => { 38 | // construct request payload confirmPaddingOutputsRequest 39 | const confirmPaddingOutputsRequest = { 40 | address: 'paymentAddress', 41 | } 42 | 43 | const mockResponse = { 44 | paddingOutputsExist: false, 45 | } 46 | // construct request response confirmPaddingOutputsResponse 47 | sinon 48 | .stub(LaunchpadClient.prototype, 'confirmPaddingOutputs') 49 | .resolves(mockResponse) 50 | 51 | const launchpad = new Launchpad('someApiKey', 'testnet') 52 | const response = await launchpad.confirmPaddingOutputs( 53 | confirmPaddingOutputsRequest 54 | ) 55 | 56 | expect(response).to.equal(mockResponse) 57 | expect(response.paddingOutputsExist).to.be.a('boolean') 58 | expect(LaunchpadClient.prototype.confirmPaddingOutputs.calledOnce).to.be.true 59 | sinon.restore() 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /test/launchpad/getAllocation.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { Launchpad } = require('../../dist/launchpad/index') 4 | const { LaunchpadClient } = require('../../dist/launchpad/client') 5 | 6 | describe('Get Buyer Allocation', function () { 7 | 8 | afterEach(() => { 9 | sinon.restore() 10 | }) 11 | 12 | it('Should return the launchpad phases for the given buyer ordinal address', async () => { 13 | // construct request payload GetAllocationRequest 14 | const getAllocationRequest = { 15 | launchpadId: 'someLaunchpadId', 16 | buyerOrdinalAddress: 'buyerOrdinalAddress', 17 | } 18 | 19 | const mockResponse = { 20 | phases: [ 21 | { 22 | id: 'launchpadPhaseId1', 23 | phases: false, 24 | allocation: '5', 25 | inscriptionsClaimed: 0, 26 | }, 27 | { 28 | id: 'launchpadPhaseId2', 29 | phases: true, 30 | }, 31 | ], 32 | } 33 | // construct request response GetAllocationResponse 34 | sinon 35 | .stub(LaunchpadClient.prototype, 'getAllocation') 36 | .resolves(mockResponse) 37 | 38 | const launchpad = new Launchpad('someApiKey', 'testnet') 39 | const response = await launchpad.getAllocation(getAllocationRequest) 40 | 41 | expect(response).to.equal(mockResponse) 42 | expect(response.phases).to.be.a('array') 43 | expect(LaunchpadClient.prototype.getAllocation.calledOnce).to.be.true 44 | sinon.restore() 45 | }) 46 | 47 | it('should return empty phases if no launchpad phase found for buyer ordinal addres', async () => { 48 | // construct request payload GetAllocationRequest 49 | const getAllocationRequest = { 50 | launchpadId: 'someLaunchpadId', 51 | buyerOrdinalAddress: 'buyerOrdinalAddress', 52 | } 53 | 54 | const mockResponse = { 55 | phases: [], 56 | } 57 | // construct request response GetAllocationResponse 58 | sinon 59 | .stub(LaunchpadClient.prototype, 'getAllocation') 60 | .resolves(mockResponse) 61 | 62 | const launchpad = new Launchpad('someApiKey', 'testnet') 63 | const response = await launchpad.getAllocation(getAllocationRequest) 64 | 65 | expect(response).to.equal(mockResponse) 66 | expect(LaunchpadClient.prototype.getAllocation.calledOnce).to.be.true 67 | sinon.restore() 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /test/inscription/getAllocation.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const sinon = require("sinon"); 3 | const { Inscription, InscriptionClient } = require("../../dist"); 4 | 5 | describe("Get Allocation", function () { 6 | afterEach(() => { 7 | sinon.restore(); 8 | }); 9 | 10 | it("should return phases for receiveAddress with allocation and inscribedCount", async () => { 11 | const getAllocationRequest = { 12 | id: "someCollectionId", 13 | receiveAddress: "someReceiveAddress", 14 | }; 15 | 16 | const mockResponse = { 17 | phases: [ 18 | { 19 | phaseId: "0b553157-dba9-40b0-badd-d4274eda7b45", 20 | public: 0, 21 | allocation: 1, 22 | inscribedCount: 0, 23 | paidItemCount: 0 24 | }, 25 | { 26 | phaseId: "13a5b8b1-ba0d-4ef6-b2c8-0e6348f5dfc4", 27 | public: 0, 28 | allocation: 4, 29 | inscribedCount: 0, 30 | paidItemCount: 0 31 | }, 32 | { 33 | phaseId: "d859e70a-a4e5-4073-809f-686c0a9b69ef", 34 | public: 0, 35 | allocation: 4, 36 | inscribedCount: 0, 37 | paidItemCount: 0 38 | }, 39 | ], 40 | }; 41 | 42 | sinon 43 | .stub(InscriptionClient.prototype, "getAllocation") 44 | .callsFake(() => mockResponse); 45 | 46 | const inscription = new Inscription("someApiKey", "testnet"); 47 | const response = await inscription.getAllocation(getAllocationRequest); 48 | 49 | expect(response).to.equal(mockResponse); 50 | expect(response.phases).to.be.a("array"); 51 | expect(InscriptionClient.prototype.getAllocation.calledOnce).to.be.true; 52 | sinon.restore(); 53 | }); 54 | 55 | it("should return error for bad request", async () => { 56 | sinon.stub(InscriptionClient.prototype, "getAllocation").rejects({ 57 | status: 400, 58 | message: "Bad Request", 59 | }); 60 | const getAllocationRequest = { 61 | id: "someOtherCollectionId", 62 | receiveAddress: "someInvalidReceiveAddress", 63 | }; 64 | try { 65 | const inscription = new Inscription("someApiKey", "testnet"); 66 | await inscription.getAllocation(getAllocationRequest); 67 | } catch (error) { 68 | expect(error.status).to.equal(400); 69 | expect(error.message).to.equal("Bad Request"); 70 | } 71 | sinon.restore(); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/launchpad/setupPaddingOutputs.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { Launchpad } = require('../../dist/launchpad/index') 4 | const { LaunchpadClient } = require('../../dist/launchpad/client') 5 | 6 | describe('Setup Padding Outputs', function () { 7 | afterEach(() => { 8 | sinon.restore() 9 | }) 10 | 11 | it('should return psbt and buyerInputIndices without wallet provider', async () => { 12 | // construct request response setupPaddingOutputsRequest 13 | const inputRequest = getValidTestInput() 14 | 15 | sinon.stub(LaunchpadClient.prototype, 'setupPaddingOutputs').resolves({ 16 | psbt: 'somePSBTString', 17 | buyerInputIndices: [1], 18 | }) 19 | const launchpad = new Launchpad('someApiKey', 'testnet') 20 | const response = await launchpad.setupPaddingOutputs(inputRequest) 21 | 22 | expect(response).to.be.a('object') 23 | expect(response.psbt).to.be.a('string') 24 | expect(response.buyerInputIndices).to.be.a('array') 25 | expect(LaunchpadClient.prototype.setupPaddingOutputs.calledOnce).to.be.true 26 | sinon.restore() 27 | }) 28 | 29 | it('should return psbtBase64 and txId wallet provider', async () => { 30 | // construct request response setupPaddingOutputsRequest 31 | const inputRequest = getValidTestInput() 32 | 33 | inputRequest.walletProvider = 'Xverse' 34 | 35 | sinon.stub(LaunchpadClient.prototype, 'setupPaddingOutputs').resolves({ 36 | psbt: 'somePSBTString', 37 | buyerInputIndices: [1], 38 | }) 39 | 40 | sinon.stub(Launchpad.prototype, 'satsConnectWrapper').resolves({ 41 | success: true, 42 | message: 'Transaction successfull', 43 | psbtBase64: 'someString', 44 | txId: 'someTransactionID', 45 | }) 46 | 47 | const launchpad = new Launchpad('someApiKey', 'testnet') 48 | const response = await launchpad.setupPaddingOutputs(inputRequest) 49 | 50 | expect(response).to.be.a('object') 51 | expect(response.txId).to.be.a('string') 52 | expect(response.psbtBase64).to.be.a('string') 53 | expect(LaunchpadClient.prototype.setupPaddingOutputs.calledOnce).to.be.true 54 | expect(Launchpad.prototype.satsConnectWrapper.calledOnce).to.be.true 55 | sinon.restore() 56 | }) 57 | }) 58 | 59 | function getValidTestInput() { 60 | // construct request payload SetupPaddingOutputsRequest 61 | return { 62 | address: 'buyerPaymentAddress', 63 | publicKey: 'buyerPaymentPublic', 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/types/runes_types.ts: -------------------------------------------------------------------------------- 1 | import { Delegate, InscriptionCharge, InscriptionFile, InscriptionOrderState, OrderType } from "./v1"; 2 | 3 | export interface RunesEtchOrderRequest { 4 | files?: InscriptionFile[]; 5 | delegates?: Delegate[]; 6 | 7 | rune: string; 8 | 9 | supply: number; 10 | 11 | symbol: string; 12 | 13 | divisibility: number; 14 | 15 | fee: number; 16 | 17 | receiveAddress: string; 18 | 19 | terms?: RuneTerms; 20 | 21 | turbo: boolean; 22 | 23 | premine?: number; 24 | 25 | additionalFee?: number; 26 | 27 | referral?: string; 28 | } 29 | 30 | export interface RuneTerms { 31 | offset?: RuneTermsOffset; 32 | height?: RuneTermsHeight; 33 | cap?: number; 34 | amount?: number; 35 | } 36 | 37 | export interface RuneTermsOffset { 38 | start: number; 39 | end: number; 40 | } 41 | 42 | export interface RuneTermsHeight { 43 | start: number; 44 | end: number; 45 | } 46 | 47 | export interface RunesEtchOrderResponse { 48 | runeProperties: RuneProperties; 49 | fee: number; 50 | files: InscriptionFile[]; 51 | charge: InscriptionCharge; 52 | chainFee: number; 53 | serviceFee: number; 54 | receiveAddress: string; 55 | baseFee: number; 56 | rareSatsFee: number; 57 | postage: number; 58 | id: string; 59 | orderType: OrderType; 60 | state: InscriptionOrderState; 61 | createdAt: string; 62 | referral?: string; 63 | additionalFee?: number; 64 | } 65 | 66 | export interface RuneProperties { 67 | turbo: boolean; 68 | rune: string; 69 | supply: number; 70 | symbol: string; 71 | divisibility: number; 72 | premine: number; 73 | cap: number; 74 | amount: number; 75 | offset: RuneTermsOffset; 76 | height: RuneTermsHeight; 77 | }; 78 | 79 | export interface RunesMintOrderRequest { 80 | rune: string; 81 | receiveAddress: string; 82 | numberOfMints: number; 83 | fee: number; 84 | additionalFee?: number; 85 | referral?: string; 86 | } 87 | 88 | export interface RunesMintOrderResponse { 89 | charge: InscriptionCharge; 90 | rune: string; 91 | receiveAddress: string; 92 | fee: number; 93 | chainFee: number; 94 | serviceFee: number; 95 | baseFee: number; 96 | rareSatsFee: number; 97 | postage: number; 98 | numberOfMints: number; 99 | id: string; 100 | orderType: OrderType; 101 | state: InscriptionOrderState; 102 | createdAt: string; 103 | executedMints: number; 104 | mintingTxs: string[]; 105 | additionalFee?: number; 106 | referral?: string; 107 | } -------------------------------------------------------------------------------- /test/inscription/l402.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const nock = require("nock"); 3 | const { Inscription } = require("../../dist"); 4 | const { PaymentResult, MemoryTokenStore } = require('l402'); 5 | 6 | // Mock Wallet as it would be used within the Satscanner 7 | class MockWallet { 8 | payInvoice(invoice) { 9 | // Assuming payment is always successful for testing 10 | return Promise.resolve(new PaymentResult('mock-preimage', true)); 11 | } 12 | } 13 | 14 | describe("Inscription with L402 Handling", () => { 15 | let inscription; 16 | let wallet; 17 | let store; 18 | const request = { 19 | size: 0, 20 | fee: 0, 21 | count: 0, 22 | rareSats: "", 23 | postage: 10000, 24 | direct: false, 25 | additionalFee: 0, 26 | baseFee: 0 27 | }; 28 | 29 | beforeEach(() => { 30 | wallet = new MockWallet(); 31 | store = new MemoryTokenStore(); 32 | 33 | // Initialize Mempool with L402 enabled 34 | inscription = new Inscription("", "testnet", { 35 | useL402: true, 36 | l402Config: { 37 | wallet: wallet, 38 | tokenStore: store 39 | } 40 | }); 41 | 42 | // Setup nock to clean all interceptors 43 | nock.cleanAll(); 44 | }); 45 | 46 | it('should handle L402 Payment Required response by retrying the request', async () => { 47 | const resourceUrl = "https://ordinalsbot.ln.sulu.sh/price"; 48 | const invoice = 'mockinvoice'; 49 | 50 | // Simulate a 402 response with invoice details 51 | nock('https://ordinalsbot.ln.sulu.sh') 52 | .get('/price') 53 | .query(true) 54 | .reply(402, '', { 55 | 'WWW-Authenticate': `L402 invoice="${invoice}", macaroon="mockmacaroon"` 56 | }); 57 | 58 | // Simulate successful access after payment 59 | nock('https://ordinalsbot.ln.sulu.sh') 60 | .get('/price') 61 | .query(true) 62 | .reply(200, {data: 'data after L402 handled'}); 63 | 64 | const response = await inscription.getPrice(request); 65 | expect(response).to.equal('data after L402 handled'); 66 | expect(store.get(resourceUrl)).to.include('mock-preimage'); 67 | }); 68 | 69 | it('should store and reuse tokens for subsequent requests', async () => { 70 | const resourceUrl = 'https://ordinalsbot.ln.sulu.sh/price'; 71 | store.put(resourceUrl, 'L402 mocktoken'); 72 | 73 | nock('https://ordinalsbot.ln.sulu.sh') 74 | .get('/price') 75 | .query(true) 76 | .matchHeader('Authorization', 'L402 mocktoken') 77 | .reply(200, {data: "data"}); 78 | 79 | const response = await inscription.getPrice(request); 80 | expect(response).to.equal('data'); 81 | }); 82 | }); -------------------------------------------------------------------------------- /src/satscanner/index.ts: -------------------------------------------------------------------------------- 1 | import { SatscannerClient } from "./client"; 2 | import { ClientOptions, InscriptionEnv, InscriptionEnvNetwork } from "../types"; 3 | import { 4 | SatscannerSpecialRangesRequest, 5 | SatscannerSpecialRangesResponse, 6 | SatscannerSpecialRangesUtxoRequest, 7 | } from "../types/satscanner_types"; 8 | 9 | /** 10 | * A higher-level interface for interacting with the Satscanner API. 11 | */ 12 | export class Satscanner { 13 | /** 14 | * The underlying SatscannerClient instance. 15 | */ 16 | private satscannerInstance!: SatscannerClient; 17 | 18 | /** 19 | * Creates a new Satscanner instance. 20 | * @param {string} [key=''] - The API key for authentication. 21 | * @param {InscriptionEnv} [environment='mainnet'] - The environment (e.g., "testnet" , "mainnet", "signet") (optional, defaults to mainnet). 22 | * @param {ClientOptions} [options] - Options for enabling L402 support. 23 | */ 24 | constructor(key: string = "", environment: InscriptionEnv = InscriptionEnvNetwork.mainnet, options?: ClientOptions) { 25 | if (this.satscannerInstance !== undefined) { 26 | console.error("satscanner.setCredentials was called multiple times"); 27 | return; 28 | } 29 | environment = InscriptionEnvNetwork[environment]??InscriptionEnvNetwork.mainnet; 30 | this.satscannerInstance = new SatscannerClient(key, environment, options); 31 | } 32 | 33 | /** 34 | * Retrieves the supported Satributes. 35 | * @returns {Promise} A promise that resolves to an array of supported Satributes. 36 | */ 37 | getSupportedSatributes(): Promise { 38 | return this.satscannerInstance.getSupportedSatributes(); 39 | } 40 | 41 | /** 42 | * Finds special ranges using the Satscanner API. 43 | * @param {SatscannerSpecialRangesRequest} specialRangesRequest - The request object for finding special ranges. 44 | * @returns {Promise} A promise that resolves to the special ranges response. 45 | */ 46 | findSpecialRanges( 47 | specialRangesRequest: SatscannerSpecialRangesRequest 48 | ): Promise { 49 | return this.satscannerInstance.findSpecialRanges(specialRangesRequest); 50 | } 51 | 52 | /** 53 | * Finds special ranges UTXO using the Satscanner API. 54 | * @param {SatscannerSpecialRangesUtxoRequest} specialRangesRequestUtxo - The request object for finding special ranges UTXO. 55 | * @returns {Promise} A promise that resolves to the special ranges UTXO response. 56 | */ 57 | findSpecialRangesUtxo( 58 | specialRangesRequestUtxo: SatscannerSpecialRangesUtxoRequest 59 | ): Promise { 60 | return this.satscannerInstance.findSpecialRangesUtxo( 61 | specialRangesRequestUtxo 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/launchpad/l402.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const nock = require("nock"); 3 | const { Launchpad } = require('../../dist/launchpad/index') 4 | const { PaymentResult, MemoryTokenStore } = require('l402'); 5 | 6 | // Mock Wallet as it would be used within the Satscanner 7 | class MockWallet { 8 | payInvoice(invoice) { 9 | // Assuming payment is always successful for testing 10 | return Promise.resolve(new PaymentResult('mock-preimage', true)); 11 | } 12 | } 13 | 14 | describe("Launchpad with L402 Handling", () => { 15 | let launchpad; 16 | let wallet; 17 | let store; 18 | const request = { 19 | phases: [], 20 | metaData: {}, 21 | sellerPaymentAddress: "", 22 | sellerOrdinalPublicKey: "", 23 | sellerOrdinalAddress: "", 24 | walletProvider: "" 25 | }; 26 | 27 | beforeEach(() => { 28 | wallet = new MockWallet(); 29 | store = new MemoryTokenStore(); 30 | 31 | // Initialize Mempool with L402 enabled 32 | launchpad = new Launchpad("", "testnet", { 33 | useL402: true, 34 | l402Config: { 35 | wallet: wallet, 36 | tokenStore: store 37 | } 38 | }); 39 | 40 | // Setup nock to clean all interceptors 41 | nock.cleanAll(); 42 | }); 43 | 44 | it('should handle L402 Payment Required response by retrying the request', async () => { 45 | const resourceUrl = "https://ordinalsbot.ln.sulu.sh/launchpad/create-launch"; 46 | const invoice = 'mockinvoice'; 47 | 48 | // Simulate a 402 response with invoice details 49 | nock('https://ordinalsbot.ln.sulu.sh') 50 | .post('/launchpad/create-launch') 51 | .reply(402, '', { 52 | 'WWW-Authenticate': `L402 invoice="${invoice}", macaroon="mockmacaroon"` 53 | }); 54 | 55 | // Simulate successful access after payment 56 | nock('https://ordinalsbot.ln.sulu.sh') 57 | .post('/launchpad/create-launch') 58 | .reply(200, {data: 'data after L402 handled'}); 59 | 60 | const response = await launchpad.createLaunchpad(request); 61 | expect(response).to.equal('data after L402 handled'); 62 | expect(store.get(resourceUrl, 'POST')).to.include('mock-preimage'); 63 | }); 64 | 65 | it('should store and reuse tokens for subsequent requests', async () => { 66 | const resourceUrl = 'https://ordinalsbot.ln.sulu.sh/launchpad/create-launch'; 67 | store.put(resourceUrl, 'L402 mocktoken', 'POST'); 68 | 69 | nock('https://ordinalsbot.ln.sulu.sh') 70 | .post('/launchpad/create-launch') 71 | .matchHeader('Authorization', 'L402 mocktoken') 72 | .reply(200, {data: "data"}); 73 | 74 | const response = await launchpad.createLaunchpad(request); 75 | expect(response).to.equal('data'); 76 | }); 77 | }); -------------------------------------------------------------------------------- /test/marketplace/l402.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const nock = require("nock"); 3 | const { MarketPlace } = require("../../dist"); 4 | const { PaymentResult, MemoryTokenStore } = require('l402'); 5 | 6 | // Mock Wallet as it would be used within the Satscanner 7 | class MockWallet { 8 | payInvoice(invoice) { 9 | // Assuming payment is always successful for testing 10 | return Promise.resolve(new PaymentResult('mock-preimage', true)); 11 | } 12 | } 13 | 14 | describe("Marketplace with L402 Handling", () => { 15 | let marketplace; 16 | let wallet; 17 | let store; 18 | const request = { 19 | name: "My Marketplace", 20 | sellerFee: 1000, 21 | buyerFee: 500, 22 | btcFeePayoutAddress: "1ABCxyz", 23 | url: "https://example.com/marketplace", 24 | description: "This is a marketplace for buying and selling ordinals." 25 | }; 26 | 27 | beforeEach(() => { 28 | wallet = new MockWallet(); 29 | store = new MemoryTokenStore(); 30 | 31 | // Initialize Mempool with L402 enabled 32 | marketplace = new MarketPlace("", "testnet", { 33 | useL402: true, 34 | l402Config: { 35 | wallet: wallet, 36 | tokenStore: store 37 | } 38 | }); 39 | 40 | // Setup nock to clean all interceptors 41 | nock.cleanAll(); 42 | }); 43 | 44 | it('should handle L402 Payment Required response by retrying the request', async () => { 45 | const resourceUrl = "https://ordinalsbot.ln.sulu.sh/marketplace/create-marketplace"; 46 | const invoice = 'mockinvoice'; 47 | 48 | // Simulate a 402 response with invoice details 49 | nock('https://ordinalsbot.ln.sulu.sh') 50 | .post('/marketplace/create-marketplace') 51 | .reply(402, '', { 52 | 'WWW-Authenticate': `L402 invoice="${invoice}", macaroon="mockmacaroon"` 53 | }); 54 | 55 | // Simulate successful access after payment 56 | nock('https://ordinalsbot.ln.sulu.sh') 57 | .post('/marketplace/create-marketplace') 58 | .reply(200, {data: 'data after L402 handled'}); 59 | 60 | const response = await marketplace.createMarketplace(request); 61 | expect(response).to.equal('data after L402 handled'); 62 | expect(store.get(resourceUrl, 'POST')).to.include('mock-preimage'); 63 | }); 64 | 65 | it('should store and reuse tokens for subsequent requests', async () => { 66 | const resourceUrl = 'https://ordinalsbot.ln.sulu.sh/marketplace/create-marketplace'; 67 | store.put(resourceUrl, 'L402 mocktoken', 'POST'); 68 | 69 | nock('https://ordinalsbot.ln.sulu.sh') 70 | .post('/marketplace/create-marketplace') 71 | .matchHeader('Authorization', 'L402 mocktoken') 72 | .reply(200, {data: "data"}); 73 | 74 | const response = await marketplace.createMarketplace(request); 75 | expect(response).to.equal('data'); 76 | }); 77 | }); -------------------------------------------------------------------------------- /test/marketplace/deList.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { MarketPlace } = require('../../dist') 4 | const { WALLET_PROVIDER } = require('../../dist/types/marketplace_types') 5 | 6 | const sandbox = sinon.createSandbox() 7 | describe('Marketplace DeList Ordinal', function () { 8 | afterEach(() => { 9 | sandbox.restore() 10 | }) 11 | 12 | it('should handle the DeList and transfer process to back to seller ordinal address without wallet provider', async () => { 13 | const marketPlace = new MarketPlace('someApiKey', 'testnet') 14 | const transferStub = sandbox.stub(marketPlace, 'deList').resolves({ 15 | psbtBase64: 'somePSBTString', 16 | senderOrdinalInputs: [0], 17 | senderPaymentInputs: [1], 18 | }) 19 | 20 | // Constructing a mock request based on MarketplaceDeListRequest type 21 | const mockTransferRequest = { 22 | ordinalId: 'someOrdinalId', 23 | senderPaymentAddress: 'someSenderPaymentAddress', 24 | senderPaymentPublicKey: 'someSenderPaymentPublicKey', 25 | senderOrdinalPublicKey: 'someSenderOrdinalPublicKey', 26 | senderOrdinalAddress: 'someSenderOrdinalAddress' 27 | } 28 | 29 | const response = await marketPlace.deList(mockTransferRequest) 30 | 31 | expect(response) 32 | .to.have.property('psbtBase64') 33 | .that.equals('somePSBTString') 34 | 35 | expect(response) 36 | .to.have.property('senderOrdinalInputs') 37 | .that.is.an('array') 38 | .that.includes(0) 39 | 40 | expect(response) 41 | .to.have.property('senderPaymentInputs') 42 | .that.is.an('array') 43 | .that.includes(1) 44 | 45 | sinon.assert.calledWith(transferStub, sinon.match(mockTransferRequest)) 46 | }) 47 | 48 | it('should handle the DeList and transfer process to back to seller ordinal address with wallet provider', async () => { 49 | const marketPlace = new MarketPlace('someApiKey', 'testnet') 50 | const transferStub = sandbox.stub(marketPlace, 'deList').resolves({ 51 | message: 'Ordinal successfully delisted', 52 | txId: 'someTransactionString' 53 | }) 54 | 55 | // Constructing a mock request based on MarketplaceDeListRequest type 56 | const mockTransferRequest = { 57 | ordinalId: 'someOrdinalId', 58 | senderPaymentAddress: 'someSenderPaymentAddress', 59 | senderPaymentPublicKey: 'someSenderPaymentPublicKey', 60 | senderOrdinalPublicKey: 'someSenderOrdinalPublicKey', 61 | senderOrdinalAddress: 'someSenderOrdinalAddress', 62 | walletProvider: WALLET_PROVIDER.xverse, 63 | } 64 | 65 | const response = await marketPlace.deList(mockTransferRequest) 66 | 67 | expect(response) 68 | .to.have.property('message') 69 | .that.equals('Ordinal successfully delisted') 70 | 71 | expect(response) 72 | .to.have.property('txId') 73 | .that.equals('someTransactionString') 74 | 75 | 76 | sinon.assert.calledWith(transferStub, sinon.match(mockTransferRequest)) 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /test/tokenpay/createPaymentPSBT.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const sinon = require("sinon"); 3 | const { TokenPay } = require("../../dist/tokenpay/index"); 4 | const { TokenPayClient } = require("../../dist/tokenpay/client"); 5 | 6 | describe("Create Payment PSBT", function () { 7 | afterEach(() => { 8 | sinon.restore(); 9 | }); 10 | 11 | it("should create a PSBT for order without wallet provider", async () => { 12 | const inputRequest = { 13 | orderId: "someOrderID", 14 | paymentAddress: "somePaymentAddress", 15 | paymentPublicKey: "somePaymentPublicKey", 16 | ordinalAddress: "someordinalAddress", 17 | feeRate: 28, 18 | }; 19 | 20 | const mockResponse = { 21 | psbtBase64: "somePsbtBase64String", 22 | runeInput: { 23 | index: 0, 24 | txid: "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", 25 | vout: 0, 26 | }, 27 | paymentInputs: { 28 | indices: [1, 2], 29 | address: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", 30 | }, 31 | psbtHex: "70736274ff0100fd5f0102000000c11d65eeb3b956159...", 32 | }; 33 | 34 | sinon 35 | .stub(TokenPayClient.prototype, "createPaymentPSBT") 36 | .resolves(mockResponse); 37 | 38 | const tokenPay = new TokenPay("someApiKey", "testnet", "someTokenPayApiKey"); 39 | const response = await tokenPay.createPaymentPSBT(inputRequest); 40 | 41 | expect(response).to.be.a("object"); 42 | expect(response).to.deep.equal(mockResponse); 43 | expect(TokenPayClient.prototype.createPaymentPSBT.calledOnce).to.be.true; 44 | sinon.restore(); 45 | }); 46 | 47 | it("should return PSBT for order with wallet provider", async () => { 48 | 49 | const inputRequest = { 50 | orderId: "someOrderID", 51 | paymentAddress: "somePaymentAddress", 52 | paymentPublicKey: "somePaymentPublicKey", 53 | ordinalAddress: "someordinalAddress", 54 | feeRate: 28, 55 | walletProvider: "Xverse", 56 | }; 57 | 58 | const mockResponse = { 59 | psbtBase64: "somePsbtBase64String", 60 | runeInputs: { 61 | indices: [1, 2], 62 | address: "bc1pdtkxdpunmu9rfj7tcglt4kcg2qya8w4y4cxhqcy9fscqnwdk8c7q6ec2w3", 63 | }, 64 | paymentInputs: { 65 | indices: [3], 66 | address: "3LbrEVwQ9QQ8Xh6d7tNBCdL96uhu4xzCLr", 67 | }, 68 | psbtHex: "70736274ff0100fd5f0102000000c11d65eeb3b956159...", 69 | }; 70 | 71 | sinon 72 | .stub(TokenPayClient.prototype, "createPaymentPSBT") 73 | .resolves(mockResponse); 74 | 75 | sinon.stub(TokenPay.prototype, "satsConnectWrapper").resolves({ 76 | success: true, 77 | message: "Transaction successfull", 78 | psbtBase64: "someString", 79 | txId: "someTransactionID", 80 | }); 81 | 82 | const tokenPay = new TokenPay("someApiKey", "testnet", "someTokenPayApiKey"); 83 | const response = await tokenPay.createPaymentPSBT(inputRequest); 84 | 85 | expect(response.txId).to.be.a("string"); 86 | expect(response.psbtBase64).to.be.a("string"); 87 | expect(TokenPayClient.prototype.createPaymentPSBT.calledOnce).to.be.true; 88 | expect(TokenPay.prototype.satsConnectWrapper.calledOnce).to.be.true; 89 | sinon.restore(); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/launchpad/createLaunchpadOffer.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { Launchpad } = require('../../dist/launchpad/index') 4 | const { LaunchpadClient } = require('../../dist/launchpad/client') 5 | describe('Create Launchpad offer', function () { 6 | 7 | afterEach(() => { 8 | sinon.restore() 9 | }) 10 | 11 | it('should return psbt and buyerInputIndices without wallet provider', async () => { 12 | // construct request response createLaunchpadOfferRequest 13 | const inputRequest = getValidTestInput() 14 | const mockResponse = getValidResponse() 15 | sinon 16 | .stub(LaunchpadClient.prototype, 'createLaunchpadOffer') 17 | .resolves(mockResponse) 18 | 19 | const launchpad = new Launchpad('someApiKey', 'testnet') 20 | const response = await launchpad.createLaunchpadOffer(inputRequest) 21 | 22 | expect(response).to.be.a('object') 23 | expect(response.phase).to.be.a('object') 24 | expect(response.ordinalId).to.be.a('string') 25 | expect(response.launchpadPhase).to.be.a('string') 26 | expect(response.buyerInputIndices).to.be.a('array') 27 | expect(response.psbt).to.be.a('string') 28 | expect(LaunchpadClient.prototype.createLaunchpadOffer.calledOnce).to.be.true 29 | sinon.restore() 30 | }) 31 | 32 | it('should return signedBuyerPSBTBase64 with wallet provider', async () => { 33 | // construct request response createLaunchpadOfferRequest 34 | const inputRequest = getValidTestInput() 35 | inputRequest.walletProvider = 'Xverse' 36 | 37 | const mockResponse = getValidResponse() 38 | sinon 39 | .stub(LaunchpadClient.prototype, 'createLaunchpadOffer') 40 | .resolves(mockResponse) 41 | 42 | sinon.stub(Launchpad.prototype, 'satsConnectWrapper').resolves({ 43 | success: true, 44 | message: 'Transaction successfull', 45 | psbtBase64: 'someString', 46 | txId: 'someTransactionID', 47 | }) 48 | 49 | const launchpad = new Launchpad('someApiKey', 'testnet') 50 | const response = await launchpad.createLaunchpadOffer(inputRequest) 51 | 52 | expect(response).to.be.a('object') 53 | expect(response.ordinalId).to.be.a('string') 54 | expect(response.launchpadPhase).to.be.a('string') 55 | expect(response.signedBuyerPSBTBase64).to.be.a('string') 56 | expect(LaunchpadClient.prototype.createLaunchpadOffer.calledOnce).to.be.true 57 | expect(Launchpad.prototype.satsConnectWrapper.calledOnce).to.be.true 58 | sinon.restore() 59 | }) 60 | }) 61 | 62 | function getValidTestInput() { 63 | // construct request payload createLaunchpadOfferRequest 64 | return { 65 | launchpadId: 'someLaunchpadId', 66 | launchpadPhaseId: 'someLaunchpadPhaseId', 67 | buyerPaymentAddress: 'someBuyerPaymentAddress', 68 | buyerOrdinalAddress: 'someBuyerOrdinalAddress', 69 | buyerPaymentPublicKey: 'someBuyerPaymentPublicKey', 70 | } 71 | } 72 | function getValidResponse() { 73 | // construct request payload CreateLaunchpadOfferResponse 74 | return { 75 | phase: { 76 | _id: 'someLaunchpadPhaseId', 77 | isPublic: false, 78 | price: 1500, 79 | startDate: '2024-01-08T00:00:00.000Z', 80 | endDate: '2024-01-14T00:00:00.000Z', 81 | }, 82 | ordinalId: 'someOrdinalId', 83 | launchpadPhase: 'someLaunchpadPhaseId', 84 | buyerInputIndices: [0, 1, 3], 85 | psbt: 'somePSBT', 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /test/inscription/createParentChildPSBT.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { Inscription } = require('../../dist') 4 | const { WALLET_PROVIDER } = require('../../dist/types/marketplace_types') 5 | 6 | const sandbox = sinon.createSandbox() 7 | describe('Create Parent Child PSBT', function () { 8 | afterEach(() => { 9 | sandbox.restore() 10 | }) 11 | 12 | it('should handle the create parent child psbt without wallet provider', async () => { 13 | const inscription = new Inscription('someApiKey', 'testnet') 14 | const createParentChildPsbtStub = sandbox.stub(inscription, 'createParentChildPsbt').resolves({ 15 | psbtBase64: 'somePSBTString', 16 | ordinalInputIndices: [0], 17 | paymentInputIndices: [1], 18 | psbtHex: 'someHexString' 19 | }) 20 | 21 | // Constructing a mock request based on createParentChildPsbt type 22 | const mockRequest = { 23 | orderId: 'someOrderId', 24 | userAddress: 'someUserPaymentAddress', 25 | userPublicKey: 'someUserPaymentPublicKey', 26 | userOrdinalPublicKey: 'someUserOrdinalPublicKey', 27 | userOrdinalsAddress: 'someUserOrdinalAddress', 28 | feeRate:28 29 | } 30 | 31 | const response = await inscription.createParentChildPsbt(mockRequest) 32 | 33 | expect(response) 34 | .to.have.property('psbtBase64') 35 | .that.equals('somePSBTString') 36 | 37 | expect(response) 38 | .to.have.property('ordinalInputIndices') 39 | .that.is.an('array') 40 | .that.includes(0) 41 | 42 | expect(response) 43 | .to.have.property('paymentInputIndices') 44 | .that.is.an('array') 45 | .that.includes(1) 46 | 47 | expect(response) 48 | .to.have.property('psbtHex') 49 | .that.equals('someHexString') 50 | 51 | sinon.assert.calledWith(createParentChildPsbtStub, sinon.match(mockRequest)) 52 | }) 53 | 54 | it('should handle the create parent child psbt wallet provider', async () => { 55 | const inscription = new Inscription('someApiKey', 'testnet') 56 | const createParentChildPsbtStub = sandbox.stub(inscription, 'createParentChildPsbt').resolves({ 57 | psbtBase64: 'somePSBTString', 58 | ordinalInputIndices: [0], 59 | paymentInputIndices: [1], 60 | psbtHex: 'someHexString' 61 | }) 62 | 63 | // Constructing a mock request based on createParentChildPsbt type 64 | const mockRequest = { 65 | orderId: 'someOrderId', 66 | userAddress: 'someUserPaymentAddress', 67 | userPublicKey: 'someUserPaymentPublicKey', 68 | userOrdinalPublicKey: 'someUserOrdinalPublicKey', 69 | userOrdinalsAddress: 'someUserOrdinalAddress', 70 | feeRate:28, 71 | walletProvider: WALLET_PROVIDER.xverse, 72 | } 73 | 74 | const response = await inscription.createParentChildPsbt(mockRequest) 75 | 76 | expect(response) 77 | .to.have.property('psbtBase64') 78 | .that.equals('somePSBTString') 79 | 80 | expect(response) 81 | .to.have.property('ordinalInputIndices') 82 | .that.is.an('array') 83 | .that.includes(0) 84 | 85 | expect(response) 86 | .to.have.property('paymentInputIndices') 87 | .that.is.an('array') 88 | .that.includes(1) 89 | 90 | expect(response) 91 | .to.have.property('psbtHex') 92 | .that.equals('someHexString') 93 | 94 | sinon.assert.calledWith(createParentChildPsbtStub, sinon.match(mockRequest)) 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /test/marketplace/transferOrdinals.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { MarketPlace } = require('../../dist') 4 | const { WALLET_PROVIDER } = require('../../dist/types/marketplace_types') 5 | 6 | const sandbox = sinon.createSandbox() 7 | describe('Marketplace Transfer Ordinals', function () { 8 | afterEach(() => { 9 | sandbox.restore() 10 | }) 11 | 12 | it('should handle the ordinal transfer process without wallet provider', async () => { 13 | const marketPlace = new MarketPlace('someApiKey', 'testnet') 14 | const transferStub = sandbox.stub(marketPlace, 'transfer').resolves({ 15 | psbtBase64: 'somePSBTString', 16 | senderOrdinalInputs: [0], 17 | senderPaymentInputs: [1], 18 | }) 19 | 20 | // Constructing a mock request based on MarketplaceTransferRequest type 21 | const mockTransferRequest = { 22 | transfer: [ 23 | { 24 | ordinalId: 'someOrdinalId1', 25 | receiverOrdinalAddress: 'someReceiverOrdinalAddress1', 26 | }, 27 | { 28 | ordinalId: 'someOrdinalId2', 29 | receiverOrdinalAddress: 'someReceiverOrdinalAddress2', 30 | }, 31 | ], 32 | senderPaymentAddress: 'someSenderPaymentAddress', 33 | senderPaymentPublicKey: 'someSenderPaymentPublicKey', 34 | senderOrdinalPublicKey: 'someSenderOrdinalPublicKey', 35 | senderOrdinalAddress: 'someSenderOrdinalAddress', 36 | } 37 | 38 | const response = await marketPlace.transfer(mockTransferRequest) 39 | 40 | expect(response) 41 | .to.have.property('psbtBase64') 42 | .that.equals('somePSBTString') 43 | 44 | expect(response) 45 | .to.have.property('senderOrdinalInputs') 46 | .that.is.an('array') 47 | .that.includes(0) 48 | 49 | expect(response) 50 | .to.have.property('senderPaymentInputs') 51 | .that.is.an('array') 52 | .that.includes(1) 53 | 54 | sinon.assert.calledWith(transferStub, sinon.match(mockTransferRequest)) 55 | }) 56 | 57 | it('should handle the ordinal transfer process with wallet provider', async () => { 58 | const marketPlace = new MarketPlace('someApiKey', 'testnet') 59 | const transferStub = sandbox.stub(marketPlace, 'transfer').resolves({ 60 | psbtBase64: 'somePSBTString', 61 | txId: 'someTransactionString' 62 | }) 63 | 64 | // Constructing a mock request based on MarketplaceTransferRequest type 65 | const mockTransferRequest = { 66 | transfer: [ 67 | { 68 | ordinalId: 'someOrdinalId1', 69 | receiverOrdinalAddress: 'someReceiverOrdinalAddress1', 70 | }, 71 | { 72 | ordinalId: 'someOrdinalId2', 73 | receiverOrdinalAddress: 'someReceiverOrdinalAddress2', 74 | }, 75 | ], 76 | senderPaymentAddress: 'someSenderPaymentAddress', 77 | senderPaymentPublicKey: 'someSenderPaymentPublicKey', 78 | senderOrdinalPublicKey: 'someSenderOrdinalPublicKey', 79 | senderOrdinalAddress: 'someSenderOrdinalAddress', 80 | walletProvider: WALLET_PROVIDER.xverse, 81 | } 82 | 83 | const response = await marketPlace.transfer(mockTransferRequest) 84 | 85 | expect(response) 86 | .to.have.property('psbtBase64') 87 | .that.equals('somePSBTString') 88 | 89 | expect(response) 90 | .to.have.property('txId') 91 | .that.equals('someTransactionString') 92 | 93 | sinon.assert.calledWith(transferStub, sinon.match(mockTransferRequest)) 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /src/satextractor/client.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from "axios"; 2 | import { InscriptionError } from "../inscription/error"; 3 | import { 4 | SatextractorExtractRequest, 5 | SatextractorExtractResponse, 6 | } from "../types/satextractor_types"; 7 | import { ClientOptions, EnvNetworkExplorer, InscriptionEnv, InscriptionEnvNetwork } from "../types"; 8 | import { setupL402Interceptor } from "l402"; 9 | 10 | /** 11 | * A client for interacting with the Satextractor API. 12 | */ 13 | export class SatextractorClient { 14 | /** 15 | * The environment for the Satextractor API (e.g., "testnet" , "mainnet", "signet"). 16 | */ 17 | public env: InscriptionEnv; 18 | 19 | /** 20 | * The API key used for authentication. 21 | */ 22 | private api_key: string; 23 | 24 | /** 25 | * The Axios instance for making HTTP requests. 26 | */ 27 | private instanceV1: AxiosInstance; 28 | 29 | /** 30 | * Creates a new Satextractor instance. 31 | * @param {string} [key=''] - The API key for authentication. 32 | * @param {InscriptionEnv} [environment='mainnet'] - The environment (e.g., "testnet" , "mainnet", "signet") (optional, defaults to mainnet). 33 | * @param {ClientOptions} [options] - Options for enabling L402 support. 34 | */ 35 | constructor(key: string = "", environment: InscriptionEnv = InscriptionEnvNetwork.mainnet, options?: ClientOptions) { 36 | this.api_key = key; 37 | environment = InscriptionEnvNetwork[environment]??InscriptionEnvNetwork.mainnet; 38 | this.env = environment; 39 | 40 | /** 41 | * Creates a new Axios instance with appropriate headers. 42 | * @returns {AxiosInstance} The new Axios instance. 43 | */ 44 | const createInstance = (): AxiosInstance => { 45 | const headers: Record = { 46 | "Content-Type": "application/json", 47 | }; 48 | 49 | // Add the API key header only if this.api_key has a value 50 | if (this.api_key) { 51 | headers["x-api-key"] = this.api_key; 52 | } 53 | 54 | // Choose the base URL based on whether L402 is used or not 55 | const baseURL = options?.useL402 56 | ? "https://ordinalsbot.ln.sulu.sh/satextractor/" 57 | : `${EnvNetworkExplorer[this.env] || EnvNetworkExplorer.mainnet}/satextractor/`; 58 | 59 | // Create the Axios client with the appropriate base URL 60 | const client = axios.create({ 61 | baseURL, 62 | headers: headers, 63 | }); 64 | 65 | client.interceptors.response.use( 66 | ({ data }) => ("data" in data ? data.data : data), 67 | (err) => { 68 | if (axios.isAxiosError(err) && err.response?.status !== 402) { // avoid modifying L402 errors. 69 | throw new InscriptionError( 70 | err.message, 71 | err.response?.statusText, 72 | err.response?.status 73 | ); 74 | } 75 | 76 | if (err instanceof Error) throw err; 77 | 78 | return err; 79 | } 80 | ); 81 | 82 | // If L402 is enabled and configuration is provided, set up the L402 interceptor 83 | if (options?.useL402 && options.l402Config) { 84 | setupL402Interceptor(client as any, options.l402Config.wallet, options.l402Config.tokenStore); 85 | }; 86 | 87 | return client; 88 | }; 89 | 90 | // Create the Axios instance 91 | this.instanceV1 = createInstance(); 92 | } 93 | 94 | /** 95 | * Extracts data using the Satextractor API. 96 | * @param {SatextractorExtractRequest} extractRequest - The request object for data extraction. 97 | * @returns {Promise} A promise that resolves to the extraction response. 98 | */ 99 | async extract( 100 | extractRequest: SatextractorExtractRequest 101 | ): Promise { 102 | return this.instanceV1.post(`/extract`, { 103 | ...extractRequest, 104 | }); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /test/runes.js: -------------------------------------------------------------------------------- 1 | const { assert, expect } = require("chai"); 2 | const sinon = require("sinon"); 3 | const { Inscription } = require("../dist"); 4 | const { v4: uuidv4 } = require("uuid"); 5 | 6 | describe("Runes SDK Tests", function () { 7 | let sandbox; 8 | let inscription; 9 | let axiosStub; 10 | const sampleOrderId1 = uuidv4(); 11 | const sampleOrderId2 = uuidv4(); 12 | const sampleTestNetAddress = "tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6"; 13 | 14 | beforeEach(function () { 15 | sandbox = sinon.createSandbox(); 16 | inscription = new Inscription("", "testnet"); 17 | axiosStub = { 18 | get: sandbox.stub(inscription.instance.axiosInstance, 'get'), 19 | post: sandbox.stub(inscription.instance.axiosInstance, 'post') 20 | }; 21 | }); 22 | 23 | afterEach(function () { 24 | sandbox.restore(); 25 | }); 26 | 27 | it("should return a rune etching order object with status 'ok' and verify payload", async () => { 28 | const orderPayload = { 29 | files: [ 30 | { 31 | size: 10, 32 | type: "plain/text", 33 | name: "my-runes-file.txt", 34 | dataURL: "data:plain/text;base64,dGVzdCBvcmRlcg==", 35 | } 36 | ], 37 | turbo: true, 38 | rune: 'THIRTEENCHARS', 39 | supply: 10000, 40 | symbol: 'D', 41 | premine: 0, 42 | divisibility: 10, 43 | fee: 110, 44 | receiveAddress: 'tb1p4mn7h5nsdtuhkkhlvg30hyfglz30whtgfs8qwr2efdjvw0yqm4cquzd8m7', 45 | terms: { 46 | amount: 1, 47 | cap: 10000, 48 | height: { 49 | start: 8000010, 50 | end: 9000010, 51 | }, 52 | }, 53 | }; 54 | axiosStub.post.resolves({ data: { status: "ok" } }); 55 | 56 | const orderResponse = await inscription.createRunesEtchOrder(orderPayload); 57 | 58 | sinon.assert.calledWithMatch(axiosStub.post, '/runes/etch', orderPayload); 59 | assert.deepEqual(orderResponse.data, { status: "ok" }); 60 | }); 61 | 62 | it("should throw a (400) Bad Request when creating a rune etching order with invalid parameters", async () => { 63 | axiosStub.post.rejects({ 64 | response: { 65 | status: 400, 66 | data: 'Bad Request' 67 | } 68 | }); 69 | 70 | let error; 71 | 72 | try { 73 | await inscription.createRunesEtchOrder({ description: "hello world" }); 74 | } catch (e) { 75 | error = e; 76 | } 77 | 78 | assert.isDefined(error); 79 | assert.strictEqual(error.response.status, 400); 80 | assert.strictEqual(error.response.data, 'Bad Request'); 81 | sinon.assert.calledOnce(axiosStub.post); 82 | }); 83 | 84 | it("should return a rune mint order object with status 'ok' and verify payload", async () => { 85 | const orderPayload = { 86 | rune: 'THIRTEENCHARS', 87 | numberOfMints: 5, 88 | fee: 110, 89 | receiveAddress: 'tb1p4mn7h5nsdtuhkkhlvg30hyfglz30whtgfs8qwr2efdjvw0yqm4cquzd8m7', 90 | }; 91 | axiosStub.post.resolves({ data: { status: "ok" } }); 92 | 93 | const orderResponse = await inscription.createRunesMintOrder(orderPayload); 94 | 95 | sinon.assert.calledWithMatch(axiosStub.post, '/runes/mint', orderPayload); 96 | assert.deepEqual(orderResponse.data, { status: "ok" }); 97 | }); 98 | 99 | it("should throw a (400) Bad Request when creating a rune etching order with invalid parameters", async () => { 100 | axiosStub.post.rejects({ 101 | response: { 102 | status: 400, 103 | data: 'Bad Request' 104 | } 105 | }); 106 | 107 | let error; 108 | 109 | try { 110 | await inscription.createRunesMintOrder({ description: "hello world" }); 111 | } catch (e) { 112 | error = e; 113 | } 114 | 115 | assert.isDefined(error); 116 | assert.strictEqual(error.response.status, 400); 117 | assert.strictEqual(error.response.data, 'Bad Request'); 118 | sinon.assert.calledOnce(axiosStub.post); 119 | }); 120 | }); -------------------------------------------------------------------------------- /test/launchpad/createLaunchpad.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { Launchpad } = require('../../dist/launchpad/index') 4 | const { LaunchpadClient } = require('../../dist/launchpad/client') 5 | 6 | describe('Create Launchpad', function () { 7 | afterEach(() => { 8 | sinon.restore() 9 | }) 10 | 11 | it('should return launchpadId and status without wallet provider', async () => { 12 | // construct request response CreateLaunchpadRequest 13 | const inputRequest = getValidTestInput() 14 | 15 | sinon.stub(LaunchpadClient.prototype, 'createLaunchpad').resolves({ 16 | launchpadId: 'someLaunchpadId', 17 | status: 'pending', 18 | }) 19 | const launchpad = new Launchpad('someApiKey', 'testnet') 20 | const response = await launchpad.createLaunchpad(inputRequest) 21 | 22 | expect(response).to.be.a('object') 23 | expect(response.launchpadId).to.be.a('string') 24 | expect(response.status).to.be.a('string') 25 | expect(LaunchpadClient.prototype.createLaunchpad.calledOnce).to.be.true 26 | sinon.restore() 27 | }) 28 | 29 | it('should return update message with wallet provider', async () => { 30 | // construct request response CreateLaunchpadRequest 31 | const inputRequest = getValidTestInput() 32 | 33 | inputRequest.walletProvider = 'Xverse' 34 | 35 | sinon.stub(LaunchpadClient.prototype, 'createLaunchpad').resolves({ 36 | launchpadId: 'someLaunchpadId', 37 | status: 'pending', 38 | }) 39 | 40 | sinon.stub(Launchpad.prototype, 'getLaunchpadPSBT').resolves({ 41 | psbt: 'encodedPSBTString', 42 | status: 'Pending Buyer Confirmation', 43 | }) 44 | 45 | sinon.stub(Launchpad.prototype, 'satsConnectWrapper').resolves({ 46 | success: true, 47 | message: 'Transaction successfull', 48 | psbtBase64: 'someString', 49 | txId: 'someTransactionID', 50 | }) 51 | 52 | sinon.stub(Launchpad.prototype, 'saveLaunchpad').resolves({ 53 | message: 'Launchpad listing is updated successfully', 54 | }) 55 | 56 | const launchpad = new Launchpad('someApiKey', 'testnet') 57 | const response = await launchpad.createLaunchpad(inputRequest) 58 | 59 | expect(response).to.be.a('object') 60 | expect(response.message).to.be.a('string') 61 | expect(LaunchpadClient.prototype.createLaunchpad.calledOnce).to.be.true 62 | expect(Launchpad.prototype.getLaunchpadPSBT.calledOnce).to.be.true 63 | expect(Launchpad.prototype.satsConnectWrapper.calledOnce).to.be.true 64 | expect(Launchpad.prototype.saveLaunchpad.calledOnce).to.be.true 65 | sinon.restore() 66 | }) 67 | }) 68 | 69 | function getValidTestInput() { 70 | // construct request payload CreateLaunchpadRequest 71 | return { 72 | phases: [ 73 | { 74 | ordinals: [ 75 | 'someOrdinalId1', 'someOrdinalId2', 'someOrdinalId3', 'someOrdinalId4', 'someOrdinalId-n' 76 | ], 77 | allowList: { 78 | someBuyerOrderAddres1: { 79 | allocation: 5, 80 | }, 81 | someBuyerOrderAddres2: { 82 | allocation: 5, 83 | }, 84 | someBuyerOrderAddres3: { 85 | allocation: 1, 86 | }, 87 | someBuyerOrderAddresN: { 88 | allocation: 1, 89 | }, 90 | }, 91 | isPublic: 0, 92 | price: 6000, 93 | startDate: '2023-12-14 18:12:00', 94 | endDate: '2023-12-20 18:12:00', 95 | }, 96 | { 97 | ordinals: [ 98 | 'someOrdinalId1', 99 | ], 100 | isPublic: 1, 101 | price: 8000, 102 | startDate: '2023-12-21 18:12:00', 103 | endDate: '2023-12-28 18:12:00', 104 | }, 105 | ], 106 | sellerPaymentAddress: 'somePaymentAddress', 107 | sellerOrdinalPublicKey: 'someOrdinalPublicKey', 108 | sellerOrdinalAddress: 'someOrdinalAddress', 109 | metaData: 110 | '{"title":"This is amazing","description":"This is amaazing Descriptions","imageURL":"This is amazing image url"}', 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /test/tokenpay/order.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const sinon = require("sinon"); 3 | const { TokenPay } = require("../../dist/tokenpay/index"); 4 | const { TokenPayClient } = require("../../dist/tokenpay/client"); 5 | 6 | describe("Order", function () { 7 | afterEach(() => { 8 | sinon.restore(); 9 | }); 10 | 11 | it("should create a new order", async () => { 12 | const inputRequest = { 13 | amount: 2000, 14 | token: "rune", 15 | accountId: "customer_123", 16 | }; 17 | 18 | const mockOrderResponse = { 19 | id: "someOrderID", 20 | createdAt: 1719554214939, 21 | accountId: "someAccountId", 22 | feeCharge: { 23 | amount: 1000, 24 | token: "usd", 25 | address: "abc", 26 | state: "pending_payment", 27 | tokenType: "btc", 28 | protocol: "bitcoin", 29 | txid: null, 30 | createdAt: 1721384941957, 31 | }, 32 | tokenCharge: { 33 | amount: 2000, 34 | token: "rune", 35 | address: "def", 36 | state: "pending_payment", 37 | tokenType: "rune", 38 | protocol: "bitcoin", 39 | txid: null, 40 | createdAt: 1721384941957, 41 | }, 42 | webhookUrl: null, 43 | type: "rune", 44 | state: "pending", 45 | }; 46 | 47 | sinon.stub(TokenPayClient.prototype, "createRuneOrder").resolves(mockOrderResponse); 48 | const tokenPay = new TokenPay("someApiKey", "testnet", "someTokenPayApiKey"); 49 | const response = await tokenPay.createRuneOrder(inputRequest); 50 | expect(response).to.be.a("object"); 51 | expect(response).to.deep.equal(mockOrderResponse); 52 | expect(TokenPayClient.prototype.createRuneOrder.calledOnce).to.be.true; 53 | sinon.restore(); 54 | }); 55 | 56 | it("should returns an order details with valid order id", async () => { 57 | const inputRequest = { 58 | orderId: 'someOrderId', 59 | }; 60 | 61 | const mockOrderResponse = { 62 | id: "someOrderID", 63 | createdAt: 1719554214939, 64 | accountId: "someAccountId", 65 | feeCharge: { 66 | amount: 1000, 67 | token: "usd", 68 | address: "abc", 69 | state: "pending_payment", 70 | tokenType: "btc", 71 | protocol: "bitcoin", 72 | txid: null, 73 | createdAt: 1721384941957, 74 | }, 75 | tokenCharge: { 76 | amount: 2000, 77 | token: "rune", 78 | address: "def", 79 | state: "pending_payment", 80 | tokenType: "rune", 81 | protocol: "bitcoin", 82 | txid: null, 83 | createdAt: 1721384941957, 84 | }, 85 | webhookUrl: null, 86 | type: "rune", 87 | state: "pending", 88 | }; 89 | 90 | sinon.stub(TokenPayClient.prototype, "getOrder").resolves(mockOrderResponse); 91 | const tokenPay = new TokenPay("someApiKey", "testnet", "someTokenPayApiKey"); 92 | const response = await tokenPay.getOrder(inputRequest); 93 | expect(response).to.be.a("object"); 94 | expect(response).to.deep.equal(mockOrderResponse); 95 | expect(TokenPayClient.prototype.getOrder.calledOnce).to.be.true; 96 | sinon.restore(); 97 | }); 98 | 99 | it("should return error for bad request", async () => { 100 | sinon.stub(TokenPayClient.prototype, "getOrder").rejects({ 101 | response: { 102 | status: 404, 103 | data: { 104 | error: [ 105 | { 106 | msg: "Order not found" 107 | } 108 | ] 109 | } 110 | } 111 | }); 112 | 113 | try { 114 | const inputRequest = { 115 | orderId: 'invalidOrderId', 116 | }; 117 | const tokenPay = new TokenPay("someApiKey", "testnet", "someTokenPayApiKey"); 118 | await tokenPay.getOrder(inputRequest); 119 | } catch (error) { 120 | // Assuming your error handling converts the 404 error to a 400 error 121 | expect(error.response.status).to.equal(404); 122 | expect(error.response.data.error[0].msg).to.equal("Order not found"); 123 | } 124 | sinon.restore(); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /src/mempool/client.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from "axios"; 2 | import { InscriptionError } from "../inscription/error"; 3 | import { 4 | MempoolAddressUtxoResponse, 5 | RecommendedFees, 6 | } from "../types/mempool_types"; 7 | import { ClientOptions, EnvNetworkExplorer, InscriptionEnv, InscriptionEnvNetwork } from "../types"; 8 | import { setupL402Interceptor } from "l402"; 9 | 10 | /** 11 | * A client for interacting with the Mempool API. 12 | */ 13 | export class MempoolClient { 14 | /** 15 | * The environment for the Mempool API (e.g., "testnet" , "mainnet", "signet"). 16 | */ 17 | public env: InscriptionEnv; 18 | 19 | /** 20 | * The API key used for authentication. 21 | */ 22 | private api_key: string; 23 | 24 | /** 25 | * The Axios instance for making HTTP requests. 26 | */ 27 | private instanceV1: AxiosInstance; 28 | 29 | /** 30 | * Creates a new Mempool instance. 31 | * @param {string} [key=''] - The API key for authentication. 32 | * @param {InscriptionEnv} [environment='mainnet'] - The environment (e.g., "testnet" , "mainnet", "signet") (optional, defaults to mainnet). 33 | * @param {ClientOptions} [options] - Options for enabling L402 support. 34 | */ 35 | constructor(key: string = "", environment: InscriptionEnv = InscriptionEnvNetwork.mainnet, options?: ClientOptions) { 36 | this.api_key = key; 37 | environment = InscriptionEnvNetwork[environment]??InscriptionEnvNetwork.mainnet; 38 | this.env = environment; 39 | 40 | /** 41 | * Creates a new Axios instance with appropriate headers. 42 | * @returns {AxiosInstance} The new Axios instance. 43 | */ 44 | const createInstance = (): AxiosInstance => { 45 | const headers: Record = { 46 | "Content-Type": "application/json", 47 | }; 48 | 49 | // Add the API key header only if this.api_key has a value 50 | if (this.api_key) { 51 | headers["x-api-key"] = this.api_key; 52 | } 53 | 54 | // Choose the base URL based on whether L402 is used or not 55 | const baseURL = options?.useL402 56 | ? "https://ordinalsbot.ln.sulu.sh/mempool/" 57 | : `${EnvNetworkExplorer[this.env] || EnvNetworkExplorer.mainnet}/mempool/`; 58 | 59 | // Create the Axios client with the appropriate base URL 60 | const client = axios.create({ 61 | baseURL, 62 | headers: headers, 63 | }); 64 | 65 | client.interceptors.response.use( 66 | ({ data }) => ("data" in data ? data.data : data), 67 | (err) => { 68 | if (axios.isAxiosError(err) && err.response?.status !== 402) { // avoid modifying L402 errors. 69 | throw new InscriptionError( 70 | err.message, 71 | err.response?.statusText, 72 | err.response?.status 73 | ); 74 | } 75 | 76 | if (err instanceof Error) throw err; 77 | 78 | return err; 79 | } 80 | ); 81 | 82 | // If L402 is enabled and configuration is provided, set up the L402 interceptor 83 | if (options?.useL402 && options.l402Config) { 84 | setupL402Interceptor(client as any, options.l402Config.wallet, options.l402Config.tokenStore); 85 | }; 86 | console.log({environment, key}); 87 | return client; 88 | }; 89 | 90 | // Create the Axios instance 91 | this.instanceV1 = createInstance(); 92 | } 93 | 94 | 95 | /** 96 | * Gets the recommended fee estimation from the Mempool API. 97 | * @returns A promise that resolves to the recommended fees. 98 | */ 99 | async getFeeEstimation(): Promise { 100 | return this.instanceV1.get("api/v1/fees/recommended"); 101 | } 102 | 103 | /** 104 | * Gets the UTXO (unspent transaction outputs) for a given address from the Mempool API. 105 | * @param address The address for which to fetch UTXO. 106 | * @returns A promise that resolves to an array of UTXO responses. 107 | */ 108 | async getAddressUtxo(address: string): Promise { 109 | return this.instanceV1.get(`api/address/${address}/utxo`); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/tokenpay/accountWithdraw.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const sinon = require("sinon"); 3 | const { TokenPay } = require("../../dist/tokenpay/index"); 4 | const { TokenPayClient } = require("../../dist/tokenpay/client"); 5 | 6 | describe("Account Withdraw", function () { 7 | afterEach(() => { 8 | sinon.restore(); 9 | }); 10 | 11 | it("should create user account withdraw request", async () => { 12 | const inputRequest = { 13 | protocol: "rune", 14 | token: "SHITCOIN", 15 | amount: 1000, 16 | address: "2N6ZePLQrKtix9bJBfznsykxKX1XtirnbKL", 17 | }; 18 | 19 | const mockWithdraw = { 20 | id: "673dc808-98e0-49bc-bb83-e535c611ce2b", 21 | createdAt: 1721892881114, 22 | accountId: "local-test-id", 23 | protocol: "rune", 24 | token: "SHITCOIN", 25 | amount: 1000, 26 | address: "2N6ZePLQrKtix9bJBfznsykxKX1XtirnbKL", 27 | state: "pending", 28 | txid: null, 29 | chainFee: null, 30 | tries: 0, 31 | processingAt: null, 32 | feePerByte: null, 33 | }; 34 | 35 | sinon.stub(TokenPayClient.prototype, "accountWithdraw").resolves(mockWithdraw); 36 | const tokenPay = new TokenPay("someApiKey", "testnet", "someTokenPayApiKey"); 37 | const response = await tokenPay.accountWithdraw(inputRequest); 38 | expect(response).to.be.a("object"); 39 | expect(response).to.deep.equal(mockWithdraw); 40 | expect(TokenPayClient.prototype.accountWithdraw.calledOnce).to.be.true; 41 | sinon.restore(); 42 | }); 43 | 44 | it("should return an error for insufficient balance when creating the user account withdrawal", async () => { 45 | const inputRequest = { 46 | protocol: "rune", 47 | token: "SHITCOIN", 48 | amount: 1000, 49 | address: "2N6ZePLQrKtix9bJBfznsykxKX1XtirnbKL", 50 | }; 51 | 52 | const mockWithdraw = { 53 | success: false, 54 | message: "user does not have a balance for this token", 55 | }; 56 | 57 | sinon.stub(TokenPayClient.prototype, "accountWithdraw").resolves(mockWithdraw); 58 | const tokenPay = new TokenPay("someApiKey", "testnet", "someTokenPayApiKey"); 59 | const response = await tokenPay.accountWithdraw(inputRequest); 60 | expect(response).to.be.a("object"); 61 | expect(response).to.deep.equal(mockWithdraw); 62 | expect(TokenPayClient.prototype.accountWithdraw.calledOnce).to.be.true; 63 | sinon.restore(); 64 | }); 65 | 66 | it("should return a account withdrawal details by withdrawalId", async () => { 67 | const inputRequest = { 68 | withdrawalId: "someWithdrawalId" 69 | }; 70 | 71 | const mockWithdraw = { 72 | id: "673dc808-98e0-49bc-bb83-e535c611ce2b", 73 | createdAt: 1721892881114, 74 | accountId: "local-test-id", 75 | protocol: "rune", 76 | token: "SHITCOIN", 77 | amount: 1000, 78 | address: "2N6ZePLQrKtix9bJBfznsykxKX1XtirnbKL", 79 | state: "pending", 80 | txid: null, 81 | chainFee: null, 82 | tries: 0, 83 | processingAt: null, 84 | feePerByte: null, 85 | }; 86 | 87 | sinon.stub(TokenPayClient.prototype, "getAccountWithdraw").resolves(mockWithdraw); 88 | const tokenPay = new TokenPay("someApiKey", "testnet", "someTokenPayApiKey"); 89 | const response = await tokenPay.getAccountWithdraw(inputRequest); 90 | expect(response).to.be.a("object"); 91 | expect(response).to.deep.equal(mockWithdraw); 92 | expect(TokenPayClient.prototype.getAccountWithdraw.calledOnce).to.be.true; 93 | sinon.restore(); 94 | }); 95 | 96 | it("should return an error for an invalid withdrawal request by ID.", async () => { 97 | sinon.stub(TokenPayClient.prototype, "getAccountWithdraw").rejects({ 98 | response: { 99 | status: 404, 100 | data: { 101 | error: [ 102 | { 103 | msg: "withdrawal not found", 104 | }, 105 | ], 106 | }, 107 | }, 108 | }); 109 | 110 | try { 111 | const inputRequest = { 112 | orderId: 'invalidOrderId', 113 | }; 114 | const tokenPay = new TokenPay("someApiKey", "testnet", "someTokenPayApiKey"); 115 | await tokenPay.getAccountWithdraw(inputRequest); 116 | } catch (error) { 117 | // Assuming your error handling converts the 404 error to a 400 error 118 | expect(error.response.status).to.equal(404); 119 | expect(error.response.data.error[0].msg).to.equal("withdrawal not found"); 120 | } 121 | sinon.restore(); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /test/mempool.js: -------------------------------------------------------------------------------- 1 | const { assert, expect } = require("chai"); 2 | const sinon = require("sinon"); 3 | const nock = require("nock"); 4 | const { Mempool } = require("../dist"); 5 | const { PaymentResult, MemoryTokenStore } = require('l402'); 6 | 7 | describe("Mempool SDK Tests", function () { 8 | let sandbox; 9 | let mempool; 10 | let axiosStub; 11 | const sampleTestNetAddress = "tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6"; 12 | 13 | beforeEach(function () { 14 | sandbox = sinon.createSandbox(); 15 | mempool = new Mempool("", "testnet"); 16 | axiosStub = { 17 | get: sandbox.stub(mempool.mempoolInstance.instanceV1, 'get'), 18 | post: sandbox.stub(mempool.mempoolInstance.instanceV1, 'post') 19 | }; 20 | }); 21 | 22 | afterEach(function () { 23 | sandbox.restore(); 24 | }); 25 | 26 | it("should return utxos for an address", async () => { 27 | const expectedResponse = [ 28 | { 29 | "txid": "f0d3d66c4e8afbef61ff050f9f2af5a53739e7b486c6f621176b042f5cf80b6e", 30 | "vout": 0, 31 | "status": { 32 | "confirmed": true, 33 | "block_height": 827418, 34 | "block_hash": "000000000000000000000a2ab7103ef9d12295b46afadb77c972ec74ca832b05", 35 | "block_time": 1706243682 36 | }, 37 | "value": 661306895 38 | }, 39 | { 40 | "txid": "398fc49bf87268183cb7c9ceef9e8af391b71017cad399fdeac7d05ee05016e1", 41 | "vout": 0, 42 | "status": { 43 | "confirmed": true, 44 | "block_height": 822985, 45 | "block_hash": "00000000000000000000a1b7e37b3e6ec25826a4c7367d32348f3ff47f1bbdaf", 46 | "block_time": 1703567325 47 | }, 48 | "value": 747974423 49 | } 50 | ]; 51 | axiosStub.get.resolves({ data: expectedResponse }); 52 | 53 | const utxoResponse = await mempool.getAddressUtxo(sampleTestNetAddress); 54 | 55 | sinon.assert.calledWithMatch(axiosStub.get, `api/address/${sampleTestNetAddress}/utxo`); 56 | assert.deepEqual(utxoResponse.data, expectedResponse); 57 | }); 58 | }); 59 | 60 | // Mock classes for Wallet and Store 61 | class MockWallet { 62 | payInvoice(invoice) { 63 | // Simulate successful payment for testing 64 | return Promise.resolve(new PaymentResult('mock-preimage', true)); 65 | } 66 | } 67 | 68 | describe("Mempool with L402 Handling", () => { 69 | let mempool; 70 | let wallet; 71 | let store; 72 | 73 | beforeEach(() => { 74 | wallet = new MockWallet(); 75 | store = new MemoryTokenStore(); 76 | 77 | // Initialize Mempool with L402 enabled 78 | mempool = new Mempool("", "testnet", { 79 | useL402: true, 80 | l402Config: { 81 | wallet: wallet, 82 | tokenStore: store 83 | } 84 | }); 85 | 86 | // Setup nock to clean all interceptors 87 | nock.cleanAll(); 88 | }); 89 | 90 | it('should handle L402 Payment Required response by retrying the request', async () => { 91 | const resourceUrl = "https://ordinalsbot.ln.sulu.sh/mempool/api/address/tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6/utxo"; 92 | const invoice = 'mockinvoice'; 93 | 94 | // Simulate a 402 response with invoice details 95 | nock('https://ordinalsbot.ln.sulu.sh') 96 | .get('/mempool/api/address/tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6/utxo') 97 | .reply(402, '', { 98 | 'WWW-Authenticate': `L402 invoice="${invoice}", macaroon="mockmacaroon"` 99 | }); 100 | 101 | // Simulate successful access after payment 102 | nock('https://ordinalsbot.ln.sulu.sh') 103 | .get('/mempool/api/address/tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6/utxo') 104 | .reply(200, {data: 'UTXO data after L402 handled'}); 105 | 106 | const response = await mempool.getAddressUtxo('tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6'); 107 | expect(response).to.equal('UTXO data after L402 handled'); 108 | expect(store.get(resourceUrl)).to.include('mock-preimage'); 109 | }); 110 | 111 | it('should store and reuse tokens for subsequent requests', async () => { 112 | const resourceUrl = 'https://ordinalsbot.ln.sulu.sh/mempool/api/address/tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6/utxo'; 113 | store.put(resourceUrl, 'L402 mocktoken'); 114 | 115 | nock('https://ordinalsbot.ln.sulu.sh') 116 | .get('/mempool/api/address/tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6/utxo') 117 | .matchHeader('Authorization', 'L402 mocktoken') 118 | .reply(200, {data: "UTXO data"}); 119 | 120 | const response = await mempool.getAddressUtxo('tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6'); 121 | expect(response).to.equal('UTXO data'); 122 | }); 123 | }); -------------------------------------------------------------------------------- /test/satextractor.js: -------------------------------------------------------------------------------- 1 | const { assert, expect } = require("chai"); 2 | const sinon = require("sinon"); 3 | const nock = require("nock"); 4 | const { PaymentResult, MemoryTokenStore } = require('l402'); 5 | const { Satextractor } = require("../dist"); 6 | 7 | describe("Satextractor SDK Tests", function () { 8 | let sandbox; 9 | let satextractor; 10 | let axiosStub; 11 | const sampleTestNetAddress = "tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6"; 12 | const sampleUtxo = "f8ed8828adf7c780366944c5b8bbe470cdf9212637d09ecd66868fc448bb8967:15" 13 | 14 | beforeEach(function () { 15 | sandbox = sinon.createSandbox(); 16 | satextractor = new Satextractor("", "testnet"); 17 | axiosStub = { 18 | get: sandbox.stub(satextractor.satextractorInstance.instanceV1, 'get'), 19 | post: sandbox.stub(satextractor.satextractorInstance.instanceV1, 'post') 20 | }; 21 | }); 22 | 23 | afterEach(function () { 24 | sandbox.restore(); 25 | }); 26 | 27 | it("should return sat extraction raw tx", async () => { 28 | const expectedParams = { 29 | "scanAddress": "bc1pshuvzr7x8y3fj362dl2excxx0n69xq42tguxsfrhvmvkre7404gs9cz40h", 30 | "addressToSendSpecialSats" : "bc1pgnwmg7wplc09cm9fctgmgalu7l4synjh7khwzre9qlcvg5xy0k5qz9mwe3", 31 | "addressToSendCommonSats": "bc1qq2ealrqzjf6da2l6czkwvtulmkh8m07280kq3q", 32 | "feePerByte": 30, 33 | "filterSatributes" : [] 34 | }; 35 | const expectedResponse = { 36 | "specialRanges": [ 37 | { 38 | "start": 280810779975733, 39 | "output": "826fe75c2e9d567baa6bee11160ae265b3007814ecca79299c5bd8338298b5d5:0", 40 | "size": 1, 41 | "offset": 0, 42 | "satributes": [ 43 | "pizza" 44 | ] 45 | } 46 | ], 47 | "tx": "0200000001d5b5988233d85b9c2979caec147800b365e20a1611ee6baa7b569d2e5ce76f820000000000fdffffff02220200000000000022512044ddb479c1fe1e5c6ca9c2d1b477fcf7eb024e57f5aee10f2507f0c450c47da85c1100000000000016001402b3df8c029274deabfac0ace62f9fddae7dbfca00000000" 48 | }; 49 | axiosStub.post.resolves({ data: expectedResponse }); 50 | 51 | const extractResponse = await satextractor.extract(expectedParams); 52 | 53 | sinon.assert.calledWithMatch(axiosStub.post, '/extract', { ...expectedParams }); 54 | assert.deepEqual(extractResponse.data, expectedResponse); 55 | }); 56 | }); 57 | 58 | // Mock classes for Wallet and Store similar to the Satscanner tests 59 | class MockWallet { 60 | payInvoice(invoice) { 61 | // Simulate successful payment for testing 62 | return Promise.resolve(new PaymentResult('mock-preimage', true)); 63 | } 64 | } 65 | 66 | describe("Satextractor with L402 Handling", () => { 67 | let satextractor; 68 | let wallet; 69 | let store; 70 | 71 | beforeEach(() => { 72 | wallet = new MockWallet(); 73 | store = new MemoryTokenStore(); 74 | 75 | // Initialize Satextractor with L402 enabled 76 | satextractor = new Satextractor("", "testnet", { 77 | useL402: true, 78 | l402Config: { 79 | wallet: wallet, 80 | tokenStore: store 81 | } 82 | }); 83 | 84 | // Clean all nocks before each test 85 | nock.cleanAll(); 86 | }); 87 | 88 | it('should handle L402 Payment Required response by retrying the request', async () => { 89 | const resourceUrl = "https://ordinalsbot.ln.sulu.sh/satextractor/extract"; 90 | const invoice = 'mockinvoice'; 91 | 92 | // Simulate a 402 response with invoice details 93 | nock('https://ordinalsbot.ln.sulu.sh') 94 | .post('/satextractor/extract') 95 | .reply(402, '', { 96 | 'WWW-Authenticate': `L402 invoice="${invoice}", macaroon="mockmacaroon"` 97 | }); 98 | 99 | // Simulate successful access after payment 100 | nock('https://ordinalsbot.ln.sulu.sh') 101 | .post('/satextractor/extract') 102 | .reply(200, {data: 'Extraction data after L402 handled'}); 103 | 104 | const response = await satextractor.extract({}); // Assuming extract method accepts an object parameter 105 | expect(response).to.equal('Extraction data after L402 handled'); 106 | expect(store.get(resourceUrl, "POST")).to.include('mock-preimage'); 107 | }); 108 | 109 | it('should store and reuse tokens for subsequent requests', async () => { 110 | const resourceUrl = 'https://ordinalsbot.ln.sulu.sh/satextractor/extract'; 111 | store.put(resourceUrl, 'L402 mocktoken', "POST"); 112 | 113 | nock('https://ordinalsbot.ln.sulu.sh') 114 | .post('/satextractor/extract') 115 | .matchHeader('Authorization', 'L402 mocktoken') 116 | .reply(200, {data: "Extracted data"}); 117 | 118 | const response = await satextractor.extract({}); 119 | expect(response).to.equal('Extracted data'); 120 | }); 121 | }); -------------------------------------------------------------------------------- /src/satscanner/client.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from "axios"; 2 | import { InscriptionError } from "../inscription/error"; 3 | import { SatscannerSpecialRangesRequest, SatscannerSpecialRangesResponse, SatscannerSpecialRangesUtxoRequest } from "../types/satscanner_types"; 4 | import { InscriptionEnv, ClientOptions, EnvNetworkExplorer, InscriptionEnvNetwork } from "../types"; 5 | import { setupL402Interceptor } from "l402"; 6 | 7 | 8 | 9 | /** 10 | * A client for interacting with the Satscanner API. 11 | */ 12 | export class SatscannerClient { 13 | /** 14 | * The environment for the Satscanner API (e.g., "testnet" , "mainnet", "signet"). 15 | */ 16 | public env: InscriptionEnv; 17 | 18 | /** 19 | * The API key used for authentication. 20 | */ 21 | private api_key: string; 22 | 23 | /** 24 | * The Axios instance for making HTTP requests. 25 | */ 26 | private instanceV1: AxiosInstance; 27 | 28 | /** 29 | * Creates a new SatscannerClient instance. 30 | * @param {string} [key=''] - The API key for authentication. 31 | * @param {InscriptionEnv} [environment='mainnet'] - The environment (e.g., "testnet" , "mainnet", "signet") (optional, defaults to mainnet). 32 | * @param {ClientOptions} [options] - Options for enabling L402 support. 33 | */ 34 | constructor(key: string = "", environment: InscriptionEnv = InscriptionEnvNetwork.mainnet, options?: ClientOptions) { 35 | this.api_key = key; 36 | environment = InscriptionEnvNetwork[environment]??InscriptionEnvNetwork.mainnet; 37 | this.env = environment; 38 | 39 | /** 40 | * Creates a new Axios instance with appropriate headers. 41 | * @returns {AxiosInstance} The new Axios instance. 42 | */ 43 | const createInstance = (): AxiosInstance => { 44 | const headers: Record = { 45 | "Content-Type": "application/json", 46 | }; 47 | 48 | // Add the API key header only if this.api_key has a value 49 | if (this.api_key) { 50 | headers["x-api-key"] = this.api_key; 51 | } 52 | 53 | // Choose the base URL based on whether L402 is used or not 54 | const baseURL = options?.useL402 55 | ? "https://ordinalsbot.ln.sulu.sh/satscanner/" 56 | : `${EnvNetworkExplorer[this.env] || EnvNetworkExplorer.mainnet}/satscanner/`; 57 | 58 | // Create the Axios client with the appropriate base URL 59 | const client = axios.create({ 60 | baseURL, 61 | headers: headers, 62 | }); 63 | 64 | client.interceptors.response.use( 65 | ({ data }) => ("data" in data ? data.data : data), 66 | (err) => { 67 | if (axios.isAxiosError(err) && err.response?.status !== 402) { // avoid modifying L402 errors. 68 | throw new InscriptionError( 69 | err.message, 70 | err.response?.statusText, 71 | err.response?.status 72 | ); 73 | } 74 | 75 | if (err instanceof Error) throw err; 76 | 77 | return err; 78 | } 79 | ); 80 | 81 | // If L402 is enabled and configuration is provided, set up the L402 interceptor 82 | if (options?.useL402 && options.l402Config) { 83 | setupL402Interceptor(client as any, options.l402Config.wallet, options.l402Config.tokenStore); 84 | }; 85 | 86 | return client; 87 | }; 88 | 89 | // Create the Axios instance 90 | this.instanceV1 = createInstance(); 91 | } 92 | 93 | /** 94 | * Retrieves the supported Satributes. 95 | * @returns {Promise} A promise that resolves to an array of supported Satributes. 96 | */ 97 | async getSupportedSatributes(): Promise { 98 | return this.instanceV1.get(`/supported-satributes`); 99 | } 100 | 101 | /** 102 | * Finds special ranges using the Satscanner API. 103 | * @param {SatscannerSpecialRangesRequest} findSpecialRangesRequest - The request object for finding special ranges. 104 | * @returns {Promise} A promise that resolves to the special ranges response. 105 | */ 106 | async findSpecialRanges( 107 | findSpecialRangesRequest: SatscannerSpecialRangesRequest 108 | ): Promise { 109 | return this.instanceV1.get(`/find-special-ranges`, { 110 | params: findSpecialRangesRequest, 111 | }); 112 | } 113 | 114 | /** 115 | * Finds special ranges UTXO using the Satscanner API. 116 | * @param {SatscannerSpecialRangesUtxoRequest} findSpecialRangesRequestUtxo - The request object for finding special ranges UTXO. 117 | * @returns {Promise} A promise that resolves to the special ranges UTXO response. 118 | */ 119 | async findSpecialRangesUtxo( 120 | findSpecialRangesRequestUtxo: SatscannerSpecialRangesUtxoRequest 121 | ): Promise { 122 | return this.instanceV1.post(`/find-special-ranges-utxo`, { 123 | ...findSpecialRangesRequestUtxo, 124 | }); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Inscription } from "./inscription/index"; 2 | import { Launchpad } from "./launchpad/index"; 3 | import { MarketPlace } from "./marketplace"; 4 | import { Mempool } from "./mempool/index"; 5 | import { Satextractor } from "./satextractor/index"; 6 | import { Satscanner } from "./satscanner/index"; 7 | import { TokenPay } from './tokenpay/index'; 8 | import { ClientOptions, InscriptionEnv, InscriptionEnvNetwork } from "./types"; 9 | 10 | export { InscriptionClient } from "./client"; 11 | export { InscriptionError } from "./inscription/error"; 12 | export * from "./types"; 13 | export { MarketPlace } from "./marketplace"; 14 | export { Inscription } from "./inscription/index"; 15 | export { Satscanner } from "./satscanner/index"; 16 | export { Satextractor } from "./satextractor/index"; 17 | export { Mempool } from "./mempool/index"; 18 | export { TokenPay } from "./tokenpay/index"; 19 | 20 | 21 | /** 22 | * Represents a bot for managing marketplaces and inscriptions. 23 | */ 24 | export class Ordinalsbot { 25 | /** 26 | * The marketplace instance. 27 | */ 28 | private marketPlaceObj!: MarketPlace; 29 | 30 | /** 31 | * The inscription instance. 32 | */ 33 | private inscriptionObj!: Inscription; 34 | 35 | /** 36 | * The launchpad instance. 37 | */ 38 | private launchpadObj!: Launchpad; 39 | 40 | /** 41 | * The Mempool instance. 42 | */ 43 | private mempoolObj!: Mempool; 44 | 45 | /** 46 | * The Satextractor instance. 47 | */ 48 | private satextractorObj!: Satextractor; 49 | 50 | /** 51 | * The satscanner instance. 52 | */ 53 | private satscannerObj!: Satscanner; 54 | 55 | /** 56 | * The tokenpay instance. 57 | */ 58 | private tokenpayObj!: TokenPay; 59 | 60 | /** 61 | * Creates an instance of Ordinalsbot. 62 | * @param {string} key - The API key for authentication. 63 | * @param {InscriptionEnv} [environment='mainnet'] - The environment (e.g., "testnet" , "mainnet", "signet") (optional, defaults to mainnet). 64 | * @param {string} tokenPayApiKey - The TokenPay API key for tokenpay backend api authentication (optional). 65 | * @param {ClientOptions} [options] - Options for enabling L402 support (optional). 66 | */ 67 | constructor(key: string = "", environment: InscriptionEnv = InscriptionEnvNetwork.mainnet, tokenPayApiKey?: string, options?: ClientOptions) { 68 | 69 | environment = InscriptionEnvNetwork[environment]??InscriptionEnvNetwork.mainnet; 70 | /** 71 | * initialising the marketplace instance 72 | */ 73 | if (this.marketPlaceObj === undefined) { 74 | this.marketPlaceObj = new MarketPlace(key, environment, options); 75 | } 76 | 77 | /** 78 | * initialising the inscription instance 79 | */ 80 | if (this.inscriptionObj === undefined) { 81 | this.inscriptionObj = new Inscription(key, environment, options); 82 | } 83 | 84 | /** 85 | * initialising the launchpad instance 86 | */ 87 | if (this.launchpadObj === undefined) { 88 | this.launchpadObj = new Launchpad(key, environment, options); 89 | } 90 | 91 | /** 92 | * initialising the mempool instance 93 | */ 94 | if (this.mempoolObj === undefined) { 95 | this.mempoolObj = new Mempool(key, environment, options); 96 | } 97 | 98 | /** 99 | * initialising the satextractor instance 100 | */ 101 | if (this.satextractorObj === undefined) { 102 | this.satextractorObj = new Satextractor(key, environment, options); 103 | } 104 | 105 | /** 106 | * initialising the satextractor instance 107 | */ 108 | if (this.satscannerObj === undefined) { 109 | this.satscannerObj = new Satscanner(key, environment, options); 110 | } 111 | 112 | /** 113 | * initialising the tokenpay instance 114 | */ 115 | if (this.tokenpayObj === undefined && tokenPayApiKey) { 116 | this.tokenpayObj = new TokenPay(key, environment, tokenPayApiKey, options); 117 | } 118 | } 119 | 120 | /** 121 | * Returns the marketplace instance. 122 | * @returns {MarketPlace} The marketplace instance. 123 | */ 124 | MarketPlace(): MarketPlace { 125 | return this.marketPlaceObj; 126 | } 127 | 128 | /** 129 | * Returns the inscription instance. 130 | * @returns {Inscription} The inscription instance. 131 | */ 132 | Inscription(): Inscription { 133 | return this.inscriptionObj; 134 | } 135 | 136 | /** 137 | * Returns the launchpad instance. 138 | * @returns {Launchpad} The launchpad instance. 139 | */ 140 | Launchpad(): Launchpad { 141 | return this.launchpadObj; 142 | } 143 | 144 | /** 145 | * Returns the mempool instance. 146 | * @returns {Mempool} The mempool instance. 147 | */ 148 | Mempool(): Mempool { 149 | return this.mempoolObj; 150 | } 151 | 152 | /** 153 | * Returns the satextractor instance. 154 | * @returns {Satextractor} The satextractor instance. 155 | */ 156 | Satextractor(): Satextractor { 157 | return this.satextractorObj; 158 | } 159 | 160 | /** 161 | * Returns the Satscanner instance. 162 | * @returns {Satscanner} The Satscanner instance. 163 | */ 164 | Satscanner(): Satscanner { 165 | return this.satscannerObj; 166 | } 167 | 168 | /** 169 | * Returns the TokenPay instance. 170 | * @returns {TokenPay} The TokenPay instance. 171 | */ 172 | TokenPay(): TokenPay { 173 | if (!this.tokenpayObj) { 174 | throw new Error("TokenPay has not been initialized") 175 | } 176 | return this.tokenpayObj; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /test/launchpad/getLaunchpadListing.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const sinon = require('sinon') 3 | const { Launchpad } = require('../../dist/launchpad/index') 4 | const { LaunchpadClient } = require('../../dist/launchpad/client') 5 | const { LAUNCHPAD_STATUS } = require('../../dist/types/launchpad_types') 6 | 7 | describe('Get Launchpad listing', function () { 8 | afterEach(() => { 9 | sinon.restore() 10 | }) 11 | 12 | it('should return launchpad listings', async () => { 13 | // construct request payload GetListingRequest 14 | const getLaunchpadListingRequest = { 15 | filter: { status: LAUNCHPAD_STATUS.active }, 16 | page: 1, 17 | itemsPerPage: 100, 18 | sort: 'time', 19 | } 20 | const mockResponse = { 21 | results: [ 22 | { 23 | _id: '65e852227146e99247bf4489', 24 | sellerPaymentAddress: '2N6ZePLQrKtix9bJBfznsykxKX1XtirnbKL', 25 | sellerOrdinalPublicKey: 26 | 'e581edf3a948470930171a3e676490a8f7953a3698044c14b4d75ffeabc88a26', 27 | marketPlaceId: '65e6f865fbdbaaa3d7f1bc9f', 28 | metaData: 29 | '{"title":"This is SDK","description":"description SDK","imageURL":"url sdk"}', 30 | createdAt: '2024-03-06T11:23:14.155Z', 31 | updatedAt: '2024-03-06T11:23:30.731Z', 32 | phases: [ 33 | { 34 | _id: '65e852227146e99247bf448c', 35 | ordinals: 5, 36 | available: 5, 37 | allowList: { 38 | '': { 39 | allocation: '', 40 | }, 41 | }, 42 | isPublic: true, 43 | price: 3600, 44 | startDate: '2024-03-05T18:30:00.000Z', 45 | endDate: '2024-03-07T18:30:00.000Z', 46 | }, 47 | ], 48 | totalOrdinals: 5, 49 | available: 5, 50 | }, 51 | { 52 | _id: '65e94e73bac282b5264954f3', 53 | sellerPaymentAddress: '2N6ZePLQrKtix9bJBfznsykxKX1XtirnbKL', 54 | sellerOrdinalPublicKey: 55 | 'e581edf3a948470930171a3e676490a8f7953a3698044c14b4d75ffeabc88a26', 56 | marketPlaceId: '65e6f865fbdbaaa3d7f1bc9f', 57 | metaData: 58 | '{"title":"Protected launcpad","description":"abc desc","imageURL":"img url"}', 59 | createdAt: '2024-03-07T05:19:47.071Z', 60 | updatedAt: '2024-03-07T05:20:01.137Z', 61 | phases: [ 62 | { 63 | _id: '65e94e73bac282b5264954f6', 64 | ordinals: 5, 65 | available: 4, 66 | allowList: { 67 | tb1p79l2gnn7u8uqxfepd7ddeeajzrmuv9nkl20wpf77t2u473a2h89s483yk3: 68 | { 69 | allocation: '1', 70 | inscriptionsClaimed: 1, 71 | }, 72 | tb1pmxq57zwa8uluf7dp86g9wj0adq7z9vuea02e9x0lsq9escnu4kusnuvau0: 73 | { 74 | allocation: '3', 75 | }, 76 | tb1pl8yv6f76eq7n83gfgh3w9ktdjm2ljtvhlmxeqhdjkq0zpwnryj3q0a9gk8: 77 | { 78 | allocation: '1', 79 | }, 80 | }, 81 | isPublic: false, 82 | price: 3620, 83 | startDate: '2024-03-07T05:17:00.000Z', 84 | endDate: '2024-03-08T05:19:00.000Z', 85 | }, 86 | ], 87 | totalOrdinals: 5, 88 | available: 4, 89 | }, 90 | { 91 | _id: '65f002ad9bb4d7453e8b4cf9', 92 | sellerPaymentAddress: '2N6ZePLQrKtix9bJBfznsykxKX1XtirnbKL', 93 | sellerOrdinalPublicKey: 94 | 'e581edf3a948470930171a3e676490a8f7953a3698044c14b4d75ffeabc88a26', 95 | marketPlaceId: '65e6f865fbdbaaa3d7f1bc9f', 96 | metaData: 97 | '{"title":"Abc titit","description":"her i\'m the ownder","imageURL":"her i\'m the ownder"}', 98 | createdAt: '2024-03-12T07:22:21.725Z', 99 | updatedAt: '2024-03-12T07:22:36.370Z', 100 | phases: [ 101 | { 102 | _id: '65f002ad9bb4d7453e8b4cfc', 103 | ordinals: 5, 104 | available: 5, 105 | allowList: { 106 | '': { 107 | allocation: '', 108 | }, 109 | }, 110 | isPublic: true, 111 | price: 3265, 112 | startDate: '2024-03-11T18:30:00.000Z', 113 | endDate: '2024-03-12T18:30:00.000Z', 114 | }, 115 | ], 116 | totalOrdinals: 5, 117 | available: 5, 118 | }, 119 | ], 120 | count: 3, 121 | currentPage: 1, 122 | totalPages: 1, 123 | totalItems: 3, 124 | } 125 | // construct request response getLaunchpadListingResponse 126 | sinon 127 | .stub(LaunchpadClient.prototype, 'getLaunchpadListing') 128 | .resolves(mockResponse) 129 | 130 | const launchpad = new Launchpad('someApiKey', 'testnet') 131 | const response = await launchpad.getLaunchpadListing( 132 | getLaunchpadListingRequest 133 | ) 134 | 135 | expect(response).to.equal(mockResponse) 136 | expect(response.results).to.be.a('array') 137 | expect(response.count).to.be.a('number') 138 | expect(response.currentPage).to.be.a('number') 139 | expect(response.totalPages).to.be.a('number') 140 | expect(response.totalItems).to.be.a('number') 141 | expect(LaunchpadClient.prototype.getLaunchpadListing.calledOnce).to.be.true 142 | }) 143 | }) 144 | -------------------------------------------------------------------------------- /test/marketplace.js: -------------------------------------------------------------------------------- 1 | const { assert, expect } = require("chai"); 2 | const sinon = require("sinon"); 3 | const { MarketPlace } = require("../dist"); 4 | const { WALLET_PROVIDER } = require("../dist/types/marketplace_types"); 5 | const authenticationErrorStatus = 401; 6 | const authenticationErrorMessage = "Request failed with status code 401"; 7 | 8 | const sandbox = sinon.createSandbox(); 9 | let marketPlace; 10 | 11 | const sellerOrdinal = { id: "test_id", price: 1000 }; 12 | const mockData = { 13 | ordinalId: "test_id", 14 | buyerPaymentAddress: "test_payment_address", 15 | buyerOrdinalAddress: "test_ordinal_address", 16 | signedPsbt: "test_signed_psbt", 17 | publicKey: "test_public_key", 18 | }; 19 | 20 | describe("marketplace", function () { 21 | before(() => { 22 | marketPlace = new MarketPlace("3b834bce-92d2-45fb-81de-bfcc89ea9d57","testnet"); 23 | }); 24 | 25 | afterEach(() => { 26 | sandbox.restore(); 27 | }); 28 | 29 | describe("create marketplace", function () { 30 | it("should return a marketplace", async () => { 31 | const createMarketplaceStub = sandbox.stub(marketPlace, 'createMarketplace').rejects({ 32 | status: authenticationErrorStatus, 33 | message: authenticationErrorMessage 34 | }); 35 | 36 | try { 37 | await marketPlace.createMarketplace({ name: "Marketplace" }); 38 | } catch (error) { 39 | expect(error.status).to.equal(authenticationErrorStatus); 40 | expect(error.message).to.equal(authenticationErrorMessage); 41 | } 42 | sinon.assert.calledOnce(createMarketplaceStub); 43 | }); 44 | }); 45 | 46 | describe("List ordinal for sale", function () { 47 | it("should return a base64 transaction to be signed", async () => { 48 | const createListingStub = sandbox.stub(marketPlace, 'createListing').rejects({ 49 | status: authenticationErrorStatus, 50 | message: authenticationErrorMessage 51 | }); 52 | 53 | try { 54 | await marketPlace.createListing({ sellerOrdinals: [sellerOrdinal] }); 55 | } catch (error) { 56 | expect(error.status).to.equal(authenticationErrorStatus); 57 | expect(error.message).to.equal(authenticationErrorMessage); 58 | } 59 | sinon.assert.calledOnce(createListingStub); 60 | }); 61 | }); 62 | 63 | describe("Create Buy Offer", function () { 64 | it("should return create buy offer response", async () => { 65 | const createOfferStub = sandbox.stub(marketPlace, 'createOffer').rejects({ 66 | status: authenticationErrorStatus, 67 | message: authenticationErrorMessage 68 | }); 69 | 70 | try { 71 | await marketPlace.createOffer({ 72 | ordinalId: mockData.ordinalId, 73 | buyerPaymentAddress: mockData.buyerPaymentAddress, 74 | buyerOrdinalAddress: mockData.buyerOrdinalAddress 75 | }); 76 | } catch (error) { 77 | expect(error.status).to.equal(authenticationErrorStatus); 78 | expect(error.message).to.equal(authenticationErrorMessage); 79 | } 80 | sinon.assert.calledOnce(createOfferStub); 81 | }); 82 | }); 83 | 84 | describe("Submit Buy Offer", function () { 85 | it("should return txid", async () => { 86 | const submitOfferStub = sandbox.stub(marketPlace, 'submitOffer').rejects({ 87 | status: authenticationErrorStatus, 88 | message: authenticationErrorMessage 89 | }); 90 | 91 | try { 92 | await marketPlace.submitOffer({ 93 | ordinalId: mockData.ordinalId, 94 | signedBuyerPSBTBase64: mockData.signedPsbt 95 | }); 96 | } catch (error) { 97 | expect(error.status).to.equal(authenticationErrorStatus); 98 | expect(error.message).to.equal(authenticationErrorMessage); 99 | } 100 | sinon.assert.calledOnce(submitOfferStub); 101 | }); 102 | }); 103 | 104 | describe("Confirm padding output", function () { 105 | it("should check if padding output exists or not", async () => { 106 | const confirmPaddingOutputsStub = sandbox.stub(marketPlace, 'confirmPaddingOutputs').rejects({ 107 | status: authenticationErrorStatus, 108 | message: authenticationErrorMessage 109 | }); 110 | 111 | try { 112 | await marketPlace.confirmPaddingOutputs({ address: mockData.buyerPaymentAddress }); 113 | } catch (error) { 114 | expect(error.status).to.equal(authenticationErrorStatus); 115 | expect(error.message).to.equal(authenticationErrorMessage); 116 | } 117 | sinon.assert.calledOnce(confirmPaddingOutputsStub); 118 | }); 119 | }); 120 | 121 | describe("Setup padding output", function () { 122 | it("should return base64 transaction to be signed", async () => { 123 | const setupPaddingOutputsStub = sandbox.stub(marketPlace, 'setupPaddingOutputs').rejects({ 124 | status: authenticationErrorStatus, 125 | message: authenticationErrorMessage 126 | }); 127 | 128 | try { 129 | await marketPlace.setupPaddingOutputs({ 130 | address: mockData.buyerPaymentAddress, 131 | publicKey: mockData.publicKey 132 | }); 133 | } catch (error) { 134 | expect(error.status).to.equal(authenticationErrorStatus); 135 | expect(error.message).to.equal(authenticationErrorMessage); 136 | } 137 | sinon.assert.calledOnce(setupPaddingOutputsStub); 138 | }); 139 | }); 140 | 141 | describe("Update ordinal listing", function () { 142 | it("should return signed psbt", async () => { 143 | const saveListingStub = sandbox.stub(marketPlace, 'saveListing').rejects({ 144 | status: authenticationErrorStatus, 145 | message: authenticationErrorMessage 146 | }); 147 | 148 | try { 149 | await marketPlace.saveListing({ 150 | ordinalId: mockData.ordinalId, 151 | updateListingData: { signedListingPSBT: mockData.signedPsbt } 152 | }); 153 | } catch (error) { 154 | expect(error.status).to.equal(authenticationErrorStatus); 155 | expect(error.message).to.equal(authenticationErrorMessage); 156 | } 157 | sinon.assert.calledOnce(saveListingStub); 158 | }); 159 | }); 160 | 161 | describe("List ordinal for sale with walletProvider", function () { 162 | it("should handle the listing process with a walletProvider", async () => { 163 | const createListingStub = sandbox.stub(marketPlace, 'createListing').resolves({ 164 | psbt: "test_psbt" 165 | }); 166 | 167 | // Constructing a mock request based on MarketplaceCreateListingRequest type 168 | const mockListingRequest = { 169 | sellerOrdinals: [{ 170 | id: "0c9ac6fb5d4516aade728882e230b0d78337732ea71915c7fbc0cdabe5d29f3ci0", 171 | price: "1234" 172 | }], 173 | sellerPaymentAddress: "2NAurbuXjBK5dztb416bh98ibDS7MKxV75C", 174 | sellerOrdinalPublicKey: "594a4aaf5da5b144d0fa6b47987d966029d892fbc4aebb23214853e8b053702e", 175 | sellerOrdinalAddress: "tb1p79l2gnn7u8uqxfepd7ddeeajzrmuv9nkl20wpf77t2u473a2h89s483yk3", 176 | walletProvider: WALLET_PROVIDER.xverse 177 | }; 178 | 179 | try { 180 | const response = await marketPlace.createListing(mockListingRequest); 181 | console.log({response, data: response.data}); 182 | expect(response).to.have.property('psbt').that.equals("test_psbt"); 183 | sinon.assert.calledWith(createListingStub, sinon.match(mockListingRequest)); 184 | } catch (error) { 185 | console.log(error); 186 | assert.fail("Should not have thrown an error"); 187 | } 188 | }); 189 | }); 190 | }); 191 | -------------------------------------------------------------------------------- /test/satscanner.js: -------------------------------------------------------------------------------- 1 | const { assert, expect } = require("chai"); 2 | const sinon = require("sinon"); 3 | const { Satscanner } = require("../dist"); 4 | const nock = require("nock"); 5 | const {PaymentResult, MemoryTokenStore} = require('l402'); 6 | 7 | describe("Satscanner SDK Tests", function () { 8 | let sandbox; 9 | let satscanner; 10 | let axiosStub; 11 | const sampleTestNetAddress = "tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6"; 12 | const sampleUtxo = "f8ed8828adf7c780366944c5b8bbe470cdf9212637d09ecd66868fc448bb8967:15" 13 | 14 | beforeEach(function () { 15 | sandbox = sinon.createSandbox(); 16 | satscanner = new Satscanner("", "testnet"); 17 | axiosStub = { 18 | get: sandbox.stub(satscanner.satscannerInstance.instanceV1, 'get'), 19 | post: sandbox.stub(satscanner.satscannerInstance.instanceV1, 'post') 20 | }; 21 | }); 22 | 23 | afterEach(function () { 24 | sandbox.restore(); 25 | }); 26 | 27 | it("should return supported satributes", async () => { 28 | const expectedResponse = { 29 | "result": [ 30 | "uncommon", 31 | "rare", 32 | "epic", 33 | "legendary", 34 | "mythic", 35 | "block-9", 36 | "block-78", 37 | "block-666", 38 | "block-999", 39 | "vintage", 40 | "number-palindrome", 41 | "pizza", 42 | "first-transaction", 43 | "nakamoto", 44 | "black" 45 | ] 46 | }; 47 | axiosStub.get.resolves({ data: expectedResponse }); 48 | 49 | const specialRangesUtxoResponse = await satscanner.getSupportedSatributes(); 50 | 51 | sinon.assert.calledWithMatch(axiosStub.get, '/supported-satributes'); 52 | assert.deepEqual(specialRangesUtxoResponse.data, expectedResponse); 53 | }); 54 | 55 | it("should return special ranges for an address", async () => { 56 | const expectedParams = { address: sampleTestNetAddress }; 57 | const expectedResponse = { 58 | "result": { 59 | "inscriptions": [ 60 | { 61 | "output": "8622ae5e1d3ea03c3859ea48f8524962abb3512a9342f49d45c3562ed90d418c:0", 62 | "inscriptions": [ 63 | "8622ae5e1d3ea03c3859ea48f8524962abb3512a9342f49d45c3562ed90d418ci0" 64 | ] 65 | } 66 | ], 67 | "ranges": [ 68 | { 69 | "output": "8622ae5e1d3ea03c3859ea48f8524962abb3512a9342f49d45c3562ed90d418c:0", 70 | "start": 1923523562791240, 71 | "end": 1923523562791786, 72 | "size": 546, 73 | "offset": 0, 74 | "rarity": "common" 75 | } 76 | ], 77 | "specialRanges": [ 78 | { 79 | "start": 1853786874999999, 80 | "output": "c0cdb5de99ae10fbe0f8f23afc0937796efef384b81c406072baf42cd671fed4:1", 81 | "size": 1, 82 | "offset": 0, 83 | "satributes": [ 84 | "black" 85 | ] 86 | } 87 | ] 88 | } 89 | }; 90 | axiosStub.get.resolves({ data: expectedResponse }); 91 | 92 | const specialRangesResponse = await satscanner.findSpecialRanges(expectedParams); 93 | 94 | sinon.assert.calledWithMatch(axiosStub.get, '/find-special-ranges', { params: expectedParams }); 95 | assert.deepEqual(specialRangesResponse.data, expectedResponse); 96 | }); 97 | 98 | it("should return special ranges for a utxo", async () => { 99 | const expectedParams = { utxos: sampleUtxo }; 100 | const expectedResponse = { 101 | "result": { 102 | "inscriptions": [ 103 | { 104 | "output": "8622ae5e1d3ea03c3859ea48f8524962abb3512a9342f49d45c3562ed90d418c:0", 105 | "inscriptions": [ 106 | "8622ae5e1d3ea03c3859ea48f8524962abb3512a9342f49d45c3562ed90d418ci0" 107 | ] 108 | } 109 | ], 110 | "ranges": [ 111 | { 112 | "output": "8622ae5e1d3ea03c3859ea48f8524962abb3512a9342f49d45c3562ed90d418c:0", 113 | "start": 1923523562791240, 114 | "end": 1923523562791786, 115 | "size": 546, 116 | "offset": 0, 117 | "rarity": "common" 118 | } 119 | ], 120 | "specialRanges": [ 121 | { 122 | "start": 1853786874999999, 123 | "output": "c0cdb5de99ae10fbe0f8f23afc0937796efef384b81c406072baf42cd671fed4:1", 124 | "size": 1, 125 | "offset": 0, 126 | "satributes": [ 127 | "black" 128 | ] 129 | } 130 | ] 131 | } 132 | }; 133 | axiosStub.post.resolves({ data: expectedResponse }); 134 | 135 | const specialRangesUtxoResponse = await satscanner.findSpecialRangesUtxo(expectedParams); 136 | 137 | sinon.assert.calledWithMatch(axiosStub.post, '/find-special-ranges-utxo', { ...expectedParams }); 138 | assert.deepEqual(specialRangesUtxoResponse.data, expectedResponse); 139 | }); 140 | }); 141 | 142 | // Mock Wallet as it would be used within the Satscanner 143 | class MockWallet { 144 | payInvoice(invoice) { 145 | // Assuming payment is always successful for testing 146 | return Promise.resolve(new PaymentResult('mock-preimage', true)); 147 | } 148 | } 149 | 150 | describe('Satscanner with L402 Handling', () => { 151 | let satscanner; 152 | let wallet; 153 | let store; 154 | 155 | beforeEach(() => { 156 | wallet = new MockWallet(); 157 | store = new MemoryTokenStore(); 158 | 159 | // Initialize Satscanner with L402 enabled 160 | satscanner = new Satscanner("", "testnet", { 161 | useL402: true, 162 | l402Config: { 163 | wallet: wallet, 164 | tokenStore: store 165 | } 166 | }); 167 | 168 | nock.cleanAll(); 169 | }); 170 | 171 | it('should handle L402 Payment Required response by retrying the request', async () => { 172 | const resourceUrl = "https://ordinalsbot.ln.sulu.sh/satscanner/supported-satributes"; 173 | const invoice = 'mockinvoice'; 174 | 175 | // First call returns 402 with an invoice challenge 176 | nock('https://ordinalsbot.ln.sulu.sh') 177 | .get('/satscanner/supported-satributes') 178 | .reply(402, '', { 179 | 'WWW-Authenticate': `L402 invoice="${invoice}", macaroon="mockmacaroon"` 180 | }); 181 | 182 | // Second call simulates successful access after payment 183 | nock('https://ordinalsbot.ln.sulu.sh') 184 | .get('/satscanner/supported-satributes') 185 | .reply(200, {data: 'Resource data after L402 handled'}); 186 | 187 | const response = await satscanner.getSupportedSatributes(); 188 | expect(response).to.equal('Resource data after L402 handled'); 189 | expect(store.get(resourceUrl)).to.include('mock-preimage'); 190 | }); 191 | 192 | it('should store and reuse tokens for subsequent requests', async () => { 193 | const resourceUrl = 'https://ordinalsbot.ln.sulu.sh/satscanner/supported-satributes'; 194 | store.put(resourceUrl, 'L402 mocktoken'); 195 | 196 | nock('https://ordinalsbot.ln.sulu.sh') 197 | .get('/satscanner/supported-satributes') 198 | .matchHeader('Authorization', 'L402 mocktoken') 199 | .reply(200, {"data": "Resource data"}); 200 | 201 | const response = await satscanner.getSupportedSatributes(); 202 | expect(response).to.equal('Resource data'); 203 | }); 204 | }); -------------------------------------------------------------------------------- /src/tokenpay/client.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from "axios"; 2 | import { ClientOptions, EnvNetworkExplorer, InscriptionEnv, InscriptionEnvNetwork } from "../types"; 3 | import { 4 | AccountBalanceResponse, 5 | AccountWithdrawRequest, 6 | AccountWithdrawResponse, 7 | CheckTransactionAsTxidRequest, 8 | CheckTransactionAsTxidResponse, 9 | CreatePaymentPSBTRequest, 10 | CreatePaymentPSBTResponse, 11 | CreateRuneOrderRequest, 12 | GetAccountWithdrawRequest, 13 | GetOrderRequest, 14 | GetOrderResponse, 15 | RuneOrderResponse, 16 | } from "../types/tokenpay_types"; 17 | import { InscriptionError } from "../inscription/error"; 18 | import { setupL402Interceptor } from "l402"; 19 | 20 | /** 21 | * A client for interacting with the TokenPay API. 22 | */ 23 | export class TokenPayClient { 24 | /** 25 | * The environment for the API (e.g., "testnet" , "mainnet", "signet"). 26 | */ 27 | public env: InscriptionEnv; 28 | 29 | /** 30 | * The API key used for authentication. 31 | */ 32 | private api_key: string; 33 | 34 | /** 35 | * The Axios instance for making API requests. 36 | */ 37 | private instanceV1: AxiosInstance; 38 | 39 | /** 40 | * The API key used for tokenpay backend api authentication. 41 | */ 42 | private tokenpay_api_key: string; 43 | 44 | /** 45 | * Creates a new TokenPay instance. 46 | * @param {string} [key=''] - The API key for authentication. 47 | * @param {InscriptionEnv} [environment='mainnet'] - The environment (e.g., "testnet" , "mainnet", "signet") (optional, defaults to mainnet). 48 | * @param tokenPayApiKey The Token Pay API key (optional). 49 | * @param {ClientOptions} [options] - Options for enabling L402 support. 50 | */ 51 | constructor(key: string = "", environment: InscriptionEnv = InscriptionEnvNetwork.mainnet, tokenPayApiKey?: string, options?: ClientOptions) { 52 | this.api_key = key; 53 | environment = InscriptionEnvNetwork[environment]??InscriptionEnvNetwork.mainnet; 54 | this.env = environment; 55 | if (!tokenPayApiKey || (typeof tokenPayApiKey !== 'string')) { 56 | throw new Error("tokenpay api key is not defined"); 57 | } 58 | this.tokenpay_api_key = tokenPayApiKey; 59 | 60 | /** 61 | * Creates a new Axios instance with appropriate headers. 62 | * @returns {AxiosInstance} The new Axios instance. 63 | */ 64 | const createInstance = (): AxiosInstance => { 65 | const headers: Record = { 66 | "Content-Type": "application/json", 67 | }; 68 | 69 | // Add the API key header only if this.tokenpay_api_key has a value 70 | if (this.tokenpay_api_key) { 71 | headers["x-api-key"] = this.tokenpay_api_key; 72 | } 73 | 74 | // Choose the base URL based on whether L402 is used or not 75 | const baseURL = options?.useL402 76 | ? "https://ordinalsbot.ln.sulu.sh/tokenpay/" 77 | : `${EnvNetworkExplorer[this.env] || EnvNetworkExplorer.mainnet}/tokenpay/`; 78 | 79 | 80 | // Create the Axios client with the appropriate base URL 81 | const client = axios.create({ 82 | baseURL, 83 | headers: headers, 84 | }); 85 | 86 | client.interceptors.response.use( 87 | ({ data }) => ("data" in data ? data.data : data), 88 | (err) => { 89 | if (axios.isAxiosError(err) && err.response?.status !== 402) { // avoid modifying L402 errors. 90 | throw new InscriptionError( 91 | err.message, 92 | err.response?.statusText, 93 | err.response?.status 94 | ); 95 | } 96 | 97 | if (err instanceof Error) throw err; 98 | 99 | return err; 100 | } 101 | ); 102 | 103 | // If L402 is enabled and configuration is provided, set up the L402 interceptor 104 | if (options?.useL402 && options.l402Config) { 105 | setupL402Interceptor(client as any, options.l402Config.wallet, options.l402Config.tokenStore); 106 | }; 107 | 108 | return client; 109 | }; 110 | 111 | // Create the Axios instance 112 | this.instanceV1 = createInstance(); 113 | } 114 | 115 | /** 116 | * Creates a new order. 117 | * @param {CreateRuneOrderRequest} createRuneOrderRequest The request body for creating a order. 118 | * @returns {Promise} A promise that resolves to the response from the API. 119 | */ 120 | async createRuneOrder( 121 | createRuneOrderRequest: CreateRuneOrderRequest 122 | ): Promise { 123 | return this.instanceV1.post(`/user/order/rune`, { 124 | ...createRuneOrderRequest, 125 | }); 126 | } 127 | 128 | /** 129 | * Creates a Payment Partially Signed Bitcoin Transaction (PSBT) for the given request. 130 | * 131 | * @param {CreatePaymentPSBTRequest} createPaymentRequest - The request object containing the details needed to create the payment PSBT. 132 | * @returns {Promise} A promise that resolves to the response of the created payment PSBT. 133 | */ 134 | async createPaymentPSBT( 135 | createPaymentRequest: CreatePaymentPSBTRequest 136 | ): Promise { 137 | return this.instanceV1.post(`/create-payment-psbt`, { 138 | ...createPaymentRequest, 139 | }); 140 | } 141 | 142 | /** 143 | * Checks the transaction status using the provided transaction ID (txid). 144 | * 145 | * @param {CheckTransactionAsTxidRequest} checkTransactionAsTxidRequest - The request object containing the transaction ID to be checked. 146 | * @returns {Promise} A promise that resolves to the response containing the transaction status. 147 | */ 148 | async checkTransactionAsTxid( 149 | checkTransactionAsTxidRequest: CheckTransactionAsTxidRequest 150 | ): Promise { 151 | return this.instanceV1.post(`/utils/checktx`, { 152 | ...checkTransactionAsTxidRequest, 153 | }); 154 | } 155 | 156 | /** 157 | * Retrieves the details of an order. 158 | * 159 | * @param {GetOrderRequest} getOrderRequest - The request object containing the order ID. 160 | * @returns {Promise} A promise that resolves to the order response. 161 | */ 162 | async getOrder( 163 | getOrderRequest: GetOrderRequest 164 | ): Promise { 165 | return this.instanceV1.get( 166 | `/order/${getOrderRequest.orderId}` 167 | ); 168 | } 169 | 170 | /** 171 | * Initiates an account withdrawal request. 172 | * 173 | * @param {AccountWithdrawRequest} accountWithdrawRequest - The request object containing the withdrawal details. 174 | * @returns {Promise} A promise that resolves to the account withdrawal response. 175 | */ 176 | async accountWithdraw( 177 | accountWithdrawRequest: AccountWithdrawRequest 178 | ): Promise { 179 | return this.instanceV1.post(`/user/account/withdraw`, { 180 | ...accountWithdrawRequest, 181 | }); 182 | } 183 | 184 | /** 185 | * Retrieves the account withdrawal details. 186 | * 187 | * @param {GetAccountWithdrawRequest} getAccountWithdrawRequest - The request object containing the withdrawal ID. 188 | * @returns {Promise} A promise that resolves to the account withdrawal response. 189 | */ 190 | async getAccountWithdraw( 191 | getAccountWithdrawRequest: GetAccountWithdrawRequest 192 | ): Promise { 193 | return this.instanceV1.get( 194 | `/user/account/withdraw/${getAccountWithdrawRequest.withdrawalId}` 195 | ); 196 | } 197 | 198 | /** 199 | * Retrieves the account balance. 200 | * 201 | * @returns {Promise} A promise that resolves to the account balance response. 202 | */ 203 | async getAccountBalance(): Promise { 204 | return this.instanceV1.get( 205 | `/user/account/balance` 206 | ); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /examples/example.ts: -------------------------------------------------------------------------------- 1 | import { Inscription } from "../src"; 2 | 3 | /** 4 | * Setup your API Key and environment 5 | * Allowed environments are ('testnet', 'mainnet', 'signet') 6 | * default environment is 'mainnet'. 7 | */ 8 | const inscription = new Inscription("", "testnet"); 9 | 10 | /** 11 | * 12 | * Fetch order information 13 | */ 14 | 15 | /** 16 | * Using promises 17 | */ 18 | 19 | inscription 20 | .getOrder("8a285e04-2973-40c3-a50b-4302c83f1d85") 21 | .then((order) => { 22 | console.log(order); 23 | }) 24 | .catch((error) => { 25 | console.error(`${error.status} | ${error.message}`); 26 | }); 27 | 28 | /** 29 | * 30 | * Using async/await 31 | */ 32 | (async () => { 33 | try { 34 | const data = await inscription.getOrder( 35 | "8a285e04-2973-40c3-a50b-4302c83f1d85" 36 | ); 37 | console.log(data); 38 | } catch (error) { 39 | console.error(`${error.status} | ${error.message}`); 40 | } 41 | })(); 42 | 43 | /** 44 | * 45 | * Creating a order 46 | */ 47 | 48 | const order = { 49 | files: [ 50 | { 51 | size: 10, 52 | type: "plain/text", 53 | name: "my-text-inscription-file.txt", 54 | dataURL: "data:plain/text;base64,dGVzdCBvcmRlcg==", 55 | } 56 | ], 57 | postage: 546, 58 | receiveAddress: "", 59 | fee: 11 60 | } 61 | 62 | /** 63 | * Using promises 64 | */ 65 | 66 | inscription 67 | .createOrder(order) 68 | .then((response) => { 69 | console.log(response); 70 | }) 71 | .catch((error) => { 72 | console.error(`${error.status} | ${error.message}`); 73 | }); 74 | 75 | /** 76 | * Using async/await 77 | */ 78 | 79 | (async () => { 80 | try { 81 | const response = await inscription.createOrder(order); 82 | console.log(response); 83 | } catch (error) { 84 | console.error(`${error.status} | ${error.message}`); 85 | } 86 | })(); 87 | 88 | /** 89 | * 90 | * Creating a text order 91 | */ 92 | 93 | const textOrder = { 94 | texts: ["This is an example text inscription."], 95 | postage: 330, 96 | receiveAddress: "", 97 | fee: 11 98 | } 99 | 100 | inscription 101 | .createTextOrder(textOrder) 102 | .then((response) => { 103 | console.log(response); 104 | }) 105 | .catch((error) => { 106 | console.error(`${error.status} | ${error.message}`); 107 | }); 108 | 109 | /** 110 | * Creating a runes etching order 111 | */ 112 | const runesEtchOrder = { 113 | files: [ 114 | { 115 | size: 10, 116 | type: "plain/text", 117 | name: "my-runes-file.txt", 118 | dataURL: "data:plain/text;base64,dGVzdCBvcmRlcg==", 119 | } 120 | ], 121 | turbo: true, 122 | rune: 'THIRTEENCHARS', 123 | supply: 10000, 124 | symbol: 'D', 125 | premine: 0, 126 | divisibility: 10, 127 | fee: 510, 128 | receiveAddress: 'tb1p4mn7h5nsdtuhkkhlvg30hyfglz30whtgfs8qwr2efdjvw0yqm4cquzd8m7', 129 | terms: { 130 | amount: 1, 131 | cap: 10000, 132 | height: { 133 | start: 8000010, 134 | end: 9000010, 135 | }, 136 | }, 137 | }; 138 | 139 | (async () => { 140 | try { 141 | const response = await inscription.createRunesEtchOrder(runesEtchOrder); 142 | console.log(response); 143 | } catch (error) { 144 | console.error(`${error.status} | ${error.message}`); 145 | } 146 | })(); 147 | 148 | /** 149 | * Creating a runes mint order 150 | */ 151 | const runesMintOrder = { 152 | rune: 'UNCOMMON.GOODS', 153 | numberOfMints: 2, 154 | fee: 510, 155 | receiveAddress: 'tb1p4mn7h5nsdtuhkkhlvg30hyfglz30whtgfs8qwr2efdjvw0yqm4cquzd8m7', 156 | }; 157 | 158 | (async () => { 159 | try { 160 | const response = await inscription.createRunesMintOrder(runesMintOrder); 161 | console.log(response); 162 | } catch (error) { 163 | console.error(`${error.status} | ${error.message}`); 164 | } 165 | })(); 166 | 167 | /** 168 | * Create a tokenpay order 169 | */ 170 | import { TokenPay } from "../src"; 171 | const tokenpayApiKey = ""; // this can not be blank 172 | const tokenpay = new TokenPay("", "testnet", tokenpayApiKey); 173 | 174 | (async () => { 175 | try { 176 | const data = await tokenpay.createRuneOrder({ 177 | amount: 100, 178 | token: 'TOKENPAY•TOKEN', 179 | description: 'This is a test order', 180 | }); 181 | console.log(data); 182 | } catch (error) { 183 | console.error(`${error.status} | ${error.message}`); 184 | } 185 | })(); 186 | 187 | /** Satscanner - API Key is required */ 188 | import { Satscanner } from "../src"; 189 | 190 | /** 191 | * Setup your API Key and environment 192 | * Allowed environments are ('testnet', 'mainnet', 'signet') 193 | * default environment is 'mainnet'. 194 | */ 195 | const satscanner = new Satscanner("", "testnet"); 196 | 197 | /** 198 | * use satscanner to get information about utxos owned by an address 199 | */ 200 | 201 | /** 202 | * Using promises 203 | */ 204 | 205 | satscanner 206 | .findSpecialRanges({ address: "bc1pjqmzr4ad437ltvfyn8pslcy8quls9ujfkrudpz6qxdh2j75qrncq44mp47" }) 207 | .then((response) => { 208 | console.log(response); 209 | }) 210 | .catch((error) => { 211 | console.error(`${error.status} | ${error.message} | ${error.data}`); 212 | }); 213 | 214 | /** 215 | * Using async/await 216 | */ 217 | 218 | (async () => { 219 | try { 220 | const response = await satscanner.findSpecialRanges({ address: "bc1pjqmzr4ad437ltvfyn8pslcy8quls9ujfkrudpz6qxdh2j75qrncq44mp47" }) 221 | console.log(response); 222 | } catch (error) { 223 | console.error(`${error.status} | ${error.message} | ${error.data}`); 224 | } 225 | })(); 226 | 227 | 228 | 229 | /** Satextractor - API Key is required */ 230 | import { Satextractor } from "../src"; 231 | 232 | /** 233 | * Setup your API Key and environment 234 | * Allowed environments are ('testnet', 'mainnet', 'signet') 235 | * default environment is 'mainnet'. 236 | */ 237 | const satextractor = new Satextractor("", "testnet"); 238 | 239 | /** 240 | * use satextractor to get a transaction that extracts special sats from an address's utxos 241 | */ 242 | 243 | /** 244 | * Using promises 245 | */ 246 | 247 | satextractor 248 | .extract({ 249 | "scanAddress": "bc1pshuvzr7x8y3fj362dl2excxx0n69xq42tguxsfrhvmvkre7404gs9cz40h", 250 | "addressToSendSpecialSats": "bc1pgnwmg7wplc09cm9fctgmgalu7l4synjh7khwzre9qlcvg5xy0k5qz9mwe3", 251 | "addressToSendCommonSats": "bc1qq2ealrqzjf6da2l6czkwvtulmkh8m07280kq3q", 252 | "feePerByte": 30, 253 | "filterSatributes": [] 254 | }) 255 | .then((response) => { 256 | console.log(response); 257 | }) 258 | .catch((error) => { 259 | console.error(`${error.status} | ${error.message} | ${error.data}`); 260 | }); 261 | 262 | /** 263 | * Using async/await 264 | */ 265 | 266 | (async () => { 267 | try { 268 | const response = await satextractor.extract({ 269 | "scanAddress": "bc1pshuvzr7x8y3fj362dl2excxx0n69xq42tguxsfrhvmvkre7404gs9cz40h", 270 | "addressToSendSpecialSats": "bc1pgnwmg7wplc09cm9fctgmgalu7l4synjh7khwzre9qlcvg5xy0k5qz9mwe3", 271 | "addressToSendCommonSats": "bc1qq2ealrqzjf6da2l6czkwvtulmkh8m07280kq3q", 272 | "feePerByte": 30, 273 | "filterSatributes": [] 274 | }) 275 | console.log(response); 276 | } catch (error) { 277 | console.error(`${error.status} | ${error.message} | ${error.data}`); 278 | } 279 | })(); 280 | 281 | 282 | 283 | 284 | /** Mempool - API Key is required */ 285 | import { Mempool } from "../src"; 286 | 287 | /** 288 | * Setup your API Key and environment 289 | * Allowed environments are ('testnet', 'mainnet', 'signet') 290 | * default environment is 'mainnet'. 291 | */ 292 | const mempool = new Mempool("", "testnet"); 293 | 294 | /** 295 | * use mempool to get information about the bitcoin blockchain 296 | */ 297 | 298 | /** 299 | * Using promises 300 | */ 301 | 302 | mempool 303 | .getAddressUtxo("2N4v1yyB5arLfQ3wHgAroRYmb49LVedkkYg") 304 | .then((response) => { 305 | console.log(response); 306 | }) 307 | .catch((error) => { 308 | console.error(`${error.status} | ${error.message} | ${error.data}`); 309 | }); 310 | 311 | /** 312 | * Using async/await 313 | */ 314 | 315 | (async () => { 316 | try { 317 | const response = await mempool.getAddressUtxo("2N4v1yyB5arLfQ3wHgAroRYmb49LVedkkYg") 318 | console.log(response); 319 | } catch (error) { 320 | console.error(`${error.status} | ${error.message} | ${error.data}`); 321 | } 322 | })(); -------------------------------------------------------------------------------- /src/tokenpay/index.ts: -------------------------------------------------------------------------------- 1 | import { ClientOptions, InscriptionEnv, InscriptionEnvNetwork } from "../types"; 2 | import { TokenPayClient } from "./client"; 3 | import { 4 | BitcoinNetworkType, 5 | SignTransactionPayload, 6 | SignTransactionResponse, 7 | signTransaction, 8 | } from "sats-connect"; 9 | import { 10 | AccountBalanceResponse, 11 | AccountWithdrawRequest, 12 | AccountWithdrawResponse, 13 | CheckTransactionAsTxidRequest, 14 | CheckTransactionAsTxidResponse, 15 | CreatePaymentPSBTRequest, 16 | CreatePaymentPSBTResponse, 17 | CreateRuneOrderRequest, 18 | GetAccountWithdrawRequest, 19 | GetOrderRequest, 20 | GetOrderResponse, 21 | RuneOrderResponse, 22 | SatsConnectWrapperResponse, 23 | } from "../types/tokenpay_types"; 24 | import { WALLET_PROVIDER } from "../types/marketplace_types"; 25 | 26 | /** 27 | * A class for interacting with the TokenPay API's. 28 | */ 29 | export class TokenPay { 30 | /** 31 | * The Bitcoin network type to use ('Mainnet', 'Testnet' or 'Signet'). 32 | */ 33 | private network: BitcoinNetworkType; 34 | /** 35 | * The instance of TokenPayClient used to make API requests. 36 | */ 37 | private tokenpayClientInstance!: TokenPayClient; 38 | 39 | /** 40 | * Creates an instance of TokenPay. 41 | * @param key The API key (optional). 42 | * @param {InscriptionEnv} [environment='mainnet'] - The environment (e.g., "testnet" , "mainnet", "signet") (optional, defaults to mainnet). 43 | * @param tokenPayApiKey The Token Pay API key (optional). 44 | * @param {ClientOptions} [options] - Options for enabling L402 support. 45 | */ 46 | constructor( 47 | key: string = "", 48 | environment: InscriptionEnv = InscriptionEnvNetwork.mainnet, 49 | tokenPayApiKey?: string, 50 | options?: ClientOptions, 51 | ) { 52 | if (this.tokenpayClientInstance !== undefined) { 53 | console.error("tokenpay constructor was called multiple times"); 54 | return; 55 | } 56 | 57 | if (!tokenPayApiKey || (typeof tokenPayApiKey !== 'string')) { 58 | throw new Error("tokenpay api key is not defined"); 59 | } 60 | 61 | environment = 62 | InscriptionEnvNetwork[environment] ?? InscriptionEnvNetwork.mainnet; 63 | switch (environment) { 64 | case InscriptionEnvNetwork.mainnet: 65 | this.network = BitcoinNetworkType.Mainnet; 66 | break; 67 | case InscriptionEnvNetwork.testnet: 68 | this.network = BitcoinNetworkType.Testnet; 69 | break; 70 | case InscriptionEnvNetwork.signet: 71 | // @ts-ignore 72 | this.network = BitcoinNetworkType.Signet; 73 | break; 74 | 75 | default: 76 | this.network = BitcoinNetworkType.Mainnet; 77 | break; 78 | } 79 | this.tokenpayClientInstance = new TokenPayClient(key, environment, tokenPayApiKey, options); 80 | } 81 | 82 | /** 83 | * Creates a new Rune order. 84 | * @param {CreateRuneOrderRequest} CreateRuneOrderRequest The request body for creating a order. 85 | * @returns {Promise} A promise that resolves to the response from the API. 86 | */ 87 | createRuneOrder(CreateRuneOrderRequest: CreateRuneOrderRequest): Promise { 88 | return this.tokenpayClientInstance.createRuneOrder(CreateRuneOrderRequest); 89 | } 90 | 91 | /** 92 | * Creates a payment PSBT (Partially Signed Bitcoin Transaction) based on the provided request. 93 | * If a wallet provider is specified, it signs the PSBT using the appropriate wallet. 94 | * 95 | * @param {CreatePaymentPSBTRequest} createPaymentPSBTRequest - The request object containing details to create the payment PSBT. 96 | * @returns {Promise} A promise that resolves to either a CreatePaymentPSBTResponse or SignTransactionResponse object. 97 | * @throws {Error} If the wallet provider is not supported. 98 | */ 99 | async createPaymentPSBT( 100 | createPaymentPSBTRequest: CreatePaymentPSBTRequest 101 | ): Promise { 102 | if (!createPaymentPSBTRequest.walletProvider) { 103 | return this.tokenpayClientInstance.createPaymentPSBT(createPaymentPSBTRequest); 104 | } else if (createPaymentPSBTRequest.walletProvider === WALLET_PROVIDER.xverse) { 105 | const paymentPSBTResponse: CreatePaymentPSBTResponse = await this.tokenpayClientInstance.createPaymentPSBT(createPaymentPSBTRequest); 106 | const inputsToSign = [ 107 | { 108 | address: createPaymentPSBTRequest.ordinalAddress, 109 | signingIndexes: paymentPSBTResponse.runeInputs.indices, 110 | }, 111 | { 112 | address: createPaymentPSBTRequest.paymentAddress, 113 | signingIndexes: paymentPSBTResponse.paymentInputs.indices, 114 | }, 115 | ]; 116 | 117 | const payload = { 118 | network: { 119 | type: this.network, 120 | }, 121 | message: "Sign Payment PSBT Transaction", 122 | psbtBase64: paymentPSBTResponse.psbtBase64, 123 | broadcast: true, 124 | inputsToSign, 125 | }; 126 | 127 | return new Promise(async (resolve, reject) => { 128 | const response: SatsConnectWrapperResponse = 129 | await this.satsConnectWrapper(payload); 130 | if (response && response.success && response.psbtBase64) { 131 | resolve({ psbtBase64: response.psbtBase64, txId: response.txId }); 132 | } else { 133 | console.log("Transaction canceled"); 134 | } 135 | }); 136 | } else { 137 | throw new Error("Wallet not supported"); 138 | } 139 | } 140 | 141 | /** 142 | * Wraps the signTransaction function to handle the transaction signing process. 143 | * 144 | * @param {SignTransactionPayload} payload - The payload containing the transaction details. 145 | * @returns {Promise} A promise that resolves to a SatsConnectWrapperResponse object. 146 | */ 147 | async satsConnectWrapper( 148 | payload: SignTransactionPayload 149 | ): Promise { 150 | return new Promise((resolve, reject) => { 151 | signTransaction({ 152 | payload, 153 | onFinish: async (response) => { 154 | resolve({ 155 | success: true, 156 | message: "Transaction successful", 157 | ...response, 158 | }); 159 | }, 160 | onCancel: () => { 161 | resolve({ success: false, message: "Transaction cancelled" }); 162 | }, 163 | }); 164 | }); 165 | } 166 | 167 | /** 168 | * Checks the transaction status using the provided transaction ID (txid). 169 | * 170 | * @param {CheckTransactionAsTxidRequest} checkTransactionAsTxidRequest - The request object containing the transaction ID to be checked. 171 | * @returns {Promise} A promise that resolves to the response containing the transaction status. 172 | */ 173 | checkTransactionAsTxid(CheckTransactionAsTxidRequest: CheckTransactionAsTxidRequest): Promise { 174 | return this.tokenpayClientInstance.checkTransactionAsTxid(CheckTransactionAsTxidRequest); 175 | } 176 | 177 | /** 178 | * Retrieves the details of an order. 179 | * 180 | * @param {GetOrderRequest} getOrderRequest - The request object containing the order ID. 181 | * @returns {Promise} A promise that resolves to the order response. 182 | */ 183 | getOrder(getOrderRequest: GetOrderRequest): Promise { 184 | return this.tokenpayClientInstance.getOrder(getOrderRequest); 185 | } 186 | 187 | /** 188 | * Initiates an account withdrawal request. 189 | * 190 | * @param {AccountWithdrawRequest} accountWithdrawRequest - The request object containing the withdrawal details. 191 | * @returns {Promise} A promise that resolves to the account withdrawal response. 192 | */ 193 | accountWithdraw(accountWithdrawRequest: AccountWithdrawRequest): Promise { 194 | return this.tokenpayClientInstance.accountWithdraw(accountWithdrawRequest); 195 | } 196 | 197 | /** 198 | * Retrieves the account withdrawal details. 199 | * 200 | * @param {GetAccountWithdrawRequest} getAccountWithdrawRequest - The request object containing the withdrawal ID. 201 | * @returns {Promise} A promise that resolves to the account withdrawal response. 202 | */ 203 | getAccountWithdraw(getAccountWithdrawRequest: GetAccountWithdrawRequest): Promise { 204 | return this.tokenpayClientInstance.getAccountWithdraw(getAccountWithdrawRequest); 205 | } 206 | 207 | /** 208 | * Retrieves the account balance. 209 | * 210 | * @returns {Promise} A promise that resolves to the account balance response. 211 | */ 212 | getAccountBalance(): Promise { 213 | return this.tokenpayClientInstance.getAccountBalance(); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | ## [0.2.16] 2 | - Remove `lowPostage` param on order/price methods in favor of a `postage` accepting min value of 330 and max value of 10,000 sats 3 | 4 | ## [0.2.15] 5 | - `TokenPay` 6 | - Updated tokenpay client so tokenpay api key is passed in header. 7 | - Added tokenpay sample code in examples 8 | 9 | ## [0.2.14] 10 | - `TokenPay` 11 | - Moved `options` parameter to the 4th position in the `Ordinalsbot` constructor. 12 | - Added `TOKENPAY_API_KEY` as an optional 3rd parameter in the `Ordinalsbot` constructor to support TokenPay functionality. 13 | 14 | ### Example 15 | ```js 16 | // Updated constructor usage 17 | const ordinalsbotObj = new Ordinalsbot(API_KEY, "testnet", TOKENPAY_API_KEY, options); 18 | 19 | // Usage with only the TOKENPAY_API_KEY 20 | const ordinalsbotObj = new Ordinalsbot(API_KEY, "testnet", TOKENPAY_API_KEY); 21 | 22 | // Usage with only the options parameter 23 | const ordinalsbotObj = new Ordinalsbot(API_KEY, "testnet", undefined, options); 24 | 25 | // Or without optional parameters 26 | const ordinalsbotObj = new Ordinalsbot(API_KEY, "testnet"); 27 | ``` 28 | 29 | ## [0.2.13] 30 | - `TokenPay` 31 | - Added `createRuneOrder` - method to create rune order. 32 | - Added `createPaymentPSBT` - method for create payment psbt for an order. 33 | - Added `checkTransactionAsTxid` - method to checks the transaction status using the provided transaction ID (txid). 34 | - Added `getOrder` - method to retrieve the details of an order. 35 | - Added `accountWithdraw` - method to initiates an account withdrawal request. 36 | - Added `getAccountWithdraw` - method to retrieves the account withdrawal details. 37 | - Added `getAccountBalance` - method to retrieves the account balance. 38 | 39 | ## [0.2.12] 40 | - `Inscription` 41 | - Updated `createSpecialSatsPSBT` - method request and response to create special sats psbt and xverse wallet support. 42 | 43 | ## [0.2.11] 44 | - `Inscription` 45 | - Updated `createDirectOrder` - method to create multi parent parent child inscription order. 46 | - Added `createParentChildPsbt` - method for create parent child psbt for an order. 47 | 48 | ## [0.2.10] 49 | - Fixed 50 | - Fixed the issue with the axios error when using sdk on frontend 51 | 52 | ## [0.2.9] 53 | - `Inscription` 54 | - createCollection - Added phases in the request and response object. 55 | - updateCollectionPhases - Added method for updating the phases of a collection. 56 | - getAllocation - Added a method to check allowed and claimed inscription in collection phases for an address 57 | 58 | ## [0.2.8] 59 | - Environment support added for all APIs except `marketplace` and `launchpad` 60 | - Environment can be `tesnet`, `signet`, `mainnet` 61 | - Default environment is `mainnet` 62 | - Backward compatibility maintained for `live` as mainnet environment and `dev` as testnet environment 63 | 64 | ## [0.2.5] 65 | - `Types` 66 | - Added `direct` inscription types 67 | 68 | - `Inscription` 69 | - Added `inscribe` endpoint to enable support for direct inscriptions 70 | 71 | ## [0.2.4] 72 | - `Marketplace` 73 | 74 | - deList - Added the confirmDelisting method after signTransaction. 75 | - confirmDeListing - Added confirmation method to delist the ordinal from the listing. 76 | 77 | ## [0.2.3] 78 | - `Types` 79 | - Added `delegates` support to inscriptions and rune etching 80 | - Added `metaprotocol` to files or delegates array element 81 | - Added `orderType`: managed and rune-launchpad-mint 82 | - Added `BatchModeType`: for batchMode price and order requests 83 | 84 | - `Inscription` 85 | - Updated `getPrice` with type and batchMode 86 | 87 | ## [0.2.2] 88 | - `Types` 89 | 90 | - Added OrderType enum 91 | - Added Runes Etching Types 92 | - Added 'sent' to InscriptionFile 93 | 94 | - `Inscription` 95 | - Added Rune Etching Order endpoint 96 | 97 | ## [0.2.1] 98 | - `Types` 99 | 100 | - Added InscriptionOrderState enum 101 | - Updated InscriptionTextOrderRequest so the examples script runs 102 | - Added testing section in README 103 | 104 | ## [0.2.0] 105 | - `Types` 106 | 107 | - type syncronization with OrdinalsBot API 108 | 109 | ## [0.1.5] 110 | 111 | #### APIs Added 112 | - `Marketplace` 113 | 114 | - getListing - Updated request and response object. 115 | 116 | - `Launchpad` 117 | 118 | - constructor - Added method constructor for initialization of launchpad instance 119 | - createMarketPlace - Added method for creating a launchpad marketplace 120 | - createLaunchpad - Added method for creating a launchpad listing 121 | - getLaunchpadPSBT - Added method for retrieving the launchpad PSBT to sign transaction 122 | - saveLaunchpad - Added method for updating the signed PSBT on the launchpad 123 | - getLaunchpadListing - Added method for retrieving the active launchpad listing 124 | - getAllocation - Added method for buyers to check allowed and claimed inscription for launchpad phases 125 | - confirmPaddingOutputs - Added method to check if the padding outputs are available in the buyer account 126 | - setupPaddingOutputs - Added method to set up if the padding outputs are not available in the buyer account 127 | - createLaunchpadOffer - Added method for buyers to request the ordinal from launchpad phase to buy 128 | - submitLaunchpadOffer - Added method to update the signed PSBT and broadcast the buyer transaction 129 | 130 | 131 | - `Mempool` 132 | 133 | - constructor - Added method constructor for initialization of Mempool instance 134 | - getFeeEstimation - Added method to gets the recommended fee estimation 135 | - getAddressUtxo - Added method to Gets the UTXO (unspent transaction outputs) for a given address from the Mempool API. 136 | 137 | - `Satextractor` 138 | 139 | - constructor - Added method constructor for initialization of satextractor instance 140 | - extract - Added method to extracts data using the Satextractor API 141 | 142 | 143 | - `Satscanner` 144 | 145 | - constructor - Added method constructor for initialization of Satscanner instance 146 | - getSupportedSatributes - Added method to Retrieves the supported Satributes 147 | - findSpecialRanges - Added method to finds special ranges using the Satscanner API 148 | - findSpecialRangesUtxo - Added method to finds special ranges UTXO using the Satscanner API 149 | 150 | - `Ordinalsbot` 151 | 152 | - Added `Launchpad` instance in the Ordinalsbot class 153 | - Added `Mempool` instance in the Ordinalsbot class 154 | - Added `Satextractor` instance in the Ordinalsbot class 155 | - Added `Satscanner` instance in the Ordinalsbot class 156 | - Added properties for the classes. 157 | 158 | ``` 159 | import { Ordinalsbot } from 'ordinalsbot' 160 | 161 | // If no parameter is given, the default environment is 'live' 162 | const ordinalsbotObj = new Ordinalsbot(API_KEY, 'dev') 163 | const marketPlace = ordinalsbotObj.MarketPlace() 164 | const inscription = ordinalsbotObj.Inscription() 165 | const launchpad = ordinalsbotObj.Launchpad() 166 | const mempool = ordinalsbotObj.Mempool() 167 | const satextractor = ordinalsbotObj.Satextractor() 168 | const satscanner = ordinalsbotObj.Satscanner() 169 | 170 | ``` 171 | 172 | ## [0.1.4] 173 | 174 | #### APIs Added 175 | 176 | - `Inscription` 177 | 178 | - createSpecialSats - Added method to create special sats. 179 | 180 | 181 | #### APIs Updated 182 | 183 | - `Inscription` 184 | 185 | - getPrice - Updated the request and response object. 186 | - createOrder - Updated the request and response object. 187 | - createCollection - Updated the request and response object. 188 | - createCollectionOrder - Updated the request and response object. 189 | - createTextOrder - Updated the request and response object. 190 | 191 | - Ordinalsbot 192 | 193 | - Updated Usage of Ordinalsbot. 194 | 195 | ``` 196 | import { Ordinalsbot } from 'ordinalsbot' 197 | 198 | // If no parameter is given, the default environment is 'live' 199 | const ordinalsbotObj = new Ordinalsbot(API_KEY, 'dev') 200 | const marketPlace = ordinalsbotObj.MarketPlace 201 | const inscription = ordinalsbotObj.Inscription 202 | 203 | ``` 204 | 205 | 206 | ## [0.1.3] 207 | 208 | #### APIs Updated 209 | 210 | - `Marketplace` 211 | 212 | - confirmListing - Added method to update the signedPSBT on a newly created listing. 213 | - reListing - Added method to update the price of existing listing ordinal. 214 | - confirmReListing - Added method to update the signedPSBT on a new relisting. 215 | - deList - Added method to remove the existing listing. 216 | - transfer - Updated the method to support transfer of multiple ordinals. 217 | 218 | ## [0.1.2] 219 | 220 | #### APIs Updated 221 | 222 | - `Inscription` 223 | 224 | - Added metadata support for insrciption orders. 225 | - Added brotli compression support for inscription orders. 226 | 227 | ## [0.1.1] 228 | 229 | #### APIs Updated 230 | 231 | - `Marketplace` 232 | 233 | - createListing - Added walletProvider parameter 234 | - createOffer - Added walletProvider parameter 235 | - setupPaddingOutputs - Added walletProvider parameter 236 | 237 | ## [0.1.0] 238 | 239 | #### APIs exposed 240 | 241 | - `Inscription` 242 | 243 | - constructor 244 | - getPrice 245 | - createOrder 246 | - getOrder 247 | - createCollection 248 | - createCollectionOrder 249 | - createTextOrder 250 | - getInventory 251 | - setReferralCode 252 | - getReferralStatus 253 | 254 | - `Marketplace` 255 | - constructor 256 | - createMarketPlace 257 | - createListing 258 | - saveListing 259 | - getListing 260 | - createOffer 261 | - submitOffer 262 | - confirmPaddingOutputs 263 | - setupPaddingOutputs 264 | -------------------------------------------------------------------------------- /src/marketplaceClient.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from "axios"; 2 | import { InscriptionError } from "./inscription/error"; 3 | import { 4 | MarketplaceConfirmPaddingOutputsRequest, 5 | MarketplaceConfirmPaddingOutputsResponse, 6 | MarketplaceCreateListingRequest, 7 | MarketplaceCreateListingResponse, 8 | MarketplaceCreateOfferRequest, 9 | MarketplaceCreateOfferResponse, 10 | MarketplaceSetupPaddingOutputsRequest, 11 | MarketplaceSetupPaddingOutputsResponse, 12 | MarketplaceCreateRequest, 13 | MarketplaceCreateResponse, 14 | MarketplaceGetListingResponse, 15 | MarketplaceSubmitOfferRequest, 16 | MarketplaceSubmitOfferResponse, 17 | MarketplaceGetListingRequest, 18 | MarketplaceSaveListingRequest, 19 | MarketplaceSaveListingResponse, 20 | MarketplaceTransferRequest, 21 | MarketplaceTransferAPIResponse, 22 | MarketplaceConfirmListingRequest, 23 | MarketplaceConfirmListingResponse, 24 | MarketplaceReListingRequest, 25 | MarketplaceReListingResponse, 26 | MarketplaceConfirmReListRequest, 27 | MarketplaceConfirmReListResponse, 28 | MarketplaceDeListRequest, 29 | MarketplaceDeListAPIResponse, 30 | MarketplaceConfirmDeListRequest, 31 | MarketplaceConfirmDeListResponse, 32 | } from "./types/marketplace_types"; 33 | import { ClientOptions, EnvNetworkExplorer, InscriptionEnv, InscriptionEnvNetwork } from "./types"; 34 | import { setupL402Interceptor } from "l402"; 35 | 36 | export class MarketPlaceClient { 37 | public env: InscriptionEnv; 38 | private api_key: string; 39 | private instanceV1: AxiosInstance; 40 | 41 | /** 42 | * Creates a new Marketplace instance. 43 | * @param {string} [key=''] - The API key for authentication. 44 | * @param {InscriptionEnv} [environment='mainnet'] - The environment (e.g., "testnet" , "mainnet", "signet") (optional, defaults to mainnet). 45 | * @param {ClientOptions} [options] - Options for enabling L402 support. 46 | */ 47 | constructor(key: string = "", environment: InscriptionEnv = InscriptionEnvNetwork.mainnet, options?: ClientOptions) { 48 | this.api_key = key; 49 | environment = InscriptionEnvNetwork[environment]??InscriptionEnvNetwork.mainnet; 50 | this.env = environment; 51 | 52 | /** 53 | * Creates a new Axios instance with appropriate headers. 54 | * @returns {AxiosInstance} The new Axios instance. 55 | */ 56 | const createInstance = (): AxiosInstance => { 57 | const headers: Record = { 58 | "Content-Type": "application/json" 59 | }; 60 | 61 | // Add the API key header only if this.api_key has a value 62 | if (this.api_key) { 63 | headers["x-api-key"] = this.api_key; 64 | } 65 | 66 | // Choose the base URL based on whether L402 is used or not 67 | const baseURL = options?.useL402 68 | ? "https://ordinalsbot.ln.sulu.sh/marketplace/" 69 | : `${EnvNetworkExplorer[this.env] || EnvNetworkExplorer.mainnet}/marketplace/` 70 | 71 | // Create the Axios client with the appropriate base URL 72 | const client = axios.create({ 73 | baseURL, 74 | headers: headers, 75 | }); 76 | 77 | client.interceptors.response.use( 78 | ({ data }) => ("data" in data ? data.data : data), 79 | (err) => { 80 | if (axios.isAxiosError(err) && err.response?.status !== 402) { // avoid modifying L402 errors. 81 | throw new InscriptionError( 82 | err.message, 83 | err.response?.statusText, 84 | err.response?.status 85 | ); 86 | } 87 | 88 | if (err instanceof Error) throw err; 89 | 90 | return err; 91 | } 92 | ); 93 | 94 | // If L402 is enabled and configuration is provided, set up the L402 interceptor 95 | if (options?.useL402 && options.l402Config) { 96 | setupL402Interceptor(client as any, options.l402Config.wallet, options.l402Config.tokenStore); 97 | }; 98 | 99 | return client; 100 | }; 101 | 102 | // Create the Axios instance 103 | this.instanceV1 = createInstance(); 104 | } 105 | 106 | async createMarketPlace( 107 | createMarketplaceRequest: MarketplaceCreateRequest 108 | ): Promise { 109 | return this.instanceV1.post(`/create-marketplace`, { 110 | ...createMarketplaceRequest, 111 | }); 112 | } 113 | 114 | async createListing( 115 | createListingRequest: MarketplaceCreateListingRequest 116 | ): Promise { 117 | return this.instanceV1.post(`/create-listing`, { 118 | ...createListingRequest, 119 | }); 120 | } 121 | 122 | async createOffer( 123 | createOfferRequest: MarketplaceCreateOfferRequest 124 | ): Promise { 125 | return this.instanceV1.post(`/create-offer`, { 126 | ...createOfferRequest, 127 | }); 128 | } 129 | 130 | async submitOffer( 131 | submitOfferRequest: MarketplaceSubmitOfferRequest 132 | ): Promise { 133 | return this.instanceV1.post(`/submit-offer`, { 134 | ...submitOfferRequest, 135 | }); 136 | } 137 | 138 | async confirmPaddingOutputs( 139 | confirmPaddingOutputsRequest: MarketplaceConfirmPaddingOutputsRequest 140 | ): Promise { 141 | return this.instanceV1.post(`/confirm-padding-outputs`, { 142 | ...confirmPaddingOutputsRequest, 143 | }); 144 | } 145 | 146 | async setupPaddingOutputs( 147 | setupPaddingOutputsRequest: MarketplaceSetupPaddingOutputsRequest 148 | ): Promise { 149 | return this.instanceV1.post(`/setup-padding-outputs`, { 150 | ...setupPaddingOutputsRequest, 151 | }); 152 | } 153 | 154 | async getListing( 155 | getListingRequest: MarketplaceGetListingRequest 156 | ): Promise { 157 | return this.instanceV1.post(`/get-listing`, { ...getListingRequest }); 158 | } 159 | 160 | async saveListing( 161 | saveListingRequest: MarketplaceSaveListingRequest 162 | ): Promise { 163 | return this.instanceV1.patch(`/save-listing/${saveListingRequest.ordinalId}`, { 164 | ...saveListingRequest, 165 | }); 166 | } 167 | 168 | /** 169 | * Confirms a listing in the marketplace. 170 | * @param {MarketplaceConfirmListingRequest} confirmListingRequest - The request object for confirming the listing. 171 | * @returns {Promise} A promise that resolves with the response from confirming the listing. 172 | */ 173 | async confirmListing( 174 | confirmListingRequest: MarketplaceConfirmListingRequest 175 | ): Promise { 176 | return this.instanceV1.post(`/confirm-listing`, { 177 | ...confirmListingRequest, 178 | }) 179 | } 180 | 181 | /** 182 | * Relisting an existing listing ordinal in the marketplace. 183 | * @param {MarketplaceReListingRequest} reListingRequest - The request object for reListing. 184 | * @returns {Promise} A promise that resolves with the response from relisting. 185 | */ 186 | async reListing( 187 | reListingRequest: MarketplaceReListingRequest 188 | ): Promise { 189 | return this.instanceV1.post(`/relist`, { 190 | ...reListingRequest, 191 | }); 192 | } 193 | 194 | /** 195 | * Confirms relisting in the marketplace. 196 | * @param {MarketplaceConfirmReListRequest} confirmReListRequest - The request object for confirming the listing. 197 | * @returns {Promise} A promise that resolves with the response from confirming the listing. 198 | */ 199 | async confirmReListing( 200 | confirmReListRequest: MarketplaceConfirmReListRequest 201 | ): Promise { 202 | return this.instanceV1.post(`/confirm-relist`, { 203 | ...confirmReListRequest, 204 | }) 205 | } 206 | 207 | /** 208 | * deListing the ordinal from marketplace and transfer back to the seller ordinal address. 209 | * @param {MarketplaceDeListRequest} deListRequest - The request object for deListing. 210 | * @returns {Promise} A promise that resolves with the response from deListing. 211 | */ 212 | async deList( 213 | deListRequest: MarketplaceDeListRequest 214 | ): Promise { 215 | return this.instanceV1.post(`/delist`, { 216 | ...deListRequest, 217 | }); 218 | } 219 | 220 | /** 221 | * Confirms delisting in the marketplace. 222 | * @param {MarketplaceConfirmDeListRequest} confirmDeListRequest - The request object for confirming the listing. 223 | * @returns {Promise} A promise that resolves with the response from confirming the listing. 224 | */ 225 | async confirmDeListing( 226 | confirmDeListRequest: MarketplaceConfirmDeListRequest 227 | ): Promise { 228 | return this.instanceV1.post(`/confirm-delist`, { 229 | ...confirmDeListRequest, 230 | }) 231 | } 232 | 233 | /** 234 | * transfer the ordinal to another ordinal address. 235 | * @param {MarketplaceTransferRequest} transferRequest - The request object for transfer. 236 | * @returns {Promise} A promise that resolves with the response from transfer. 237 | */ 238 | async transfer( 239 | transferRequest: MarketplaceTransferRequest 240 | ): Promise { 241 | return this.instanceV1.post(`/transfer-ordinal`, { 242 | ...transferRequest, 243 | }); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/launchpad/client.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from "axios"; 2 | import { ClientOptions, EnvNetworkExplorer, InscriptionEnv, InscriptionEnvNetwork } from "../types"; 3 | import { 4 | ConfirmPaddingOutputsRequest, 5 | ConfirmPaddingOutputsResponse, 6 | CreateLaunchpadOfferRequest, 7 | CreateLaunchpadOfferResponse, 8 | CreateLaunchpadRequest, 9 | CreateLaunchpadResponse, 10 | GetAllocationRequest, 11 | GetAllocationResponse, 12 | GetListingRequest, 13 | GetListingResponse, 14 | LaunchpadMarketplaceCreateRequest, 15 | LaunchpadMarketplaceCreateResponse, 16 | SetupPaddingOutputsRequest, 17 | SetupPaddingOutputsResponse, 18 | SubmitLaunchpadOfferRequest, 19 | SubmitLaunchpadOfferResponse, 20 | GetLaunchpadStatusRequest, 21 | GetLaunchpadStatusResponse, 22 | SaveLaunchpadRequest, 23 | SaveLaunchpadResponse, 24 | } from "../types/launchpad_types"; 25 | import { InscriptionError } from "../inscription/error"; 26 | import { setupL402Interceptor } from "l402"; 27 | 28 | /** 29 | * A client for interacting with the Launchpad marketplace API. 30 | */ 31 | export class LaunchpadClient { 32 | /** 33 | * The environment for the API (e.g., "testnet" , "mainnet", "signet"). 34 | */ 35 | public env: InscriptionEnv; 36 | 37 | /** 38 | * The API key used for authentication. 39 | */ 40 | private api_key: string; 41 | 42 | /** 43 | * The Axios instance for making API requests. 44 | */ 45 | private instanceV1: AxiosInstance; 46 | 47 | /** 48 | * Creates a new Launchpad instance. 49 | * @param {string} [key=''] - The API key for authentication. 50 | * @param {InscriptionEnv} [environment='mainnet'] - The environment (e.g., "testnet" , "mainnet", "signet") (optional, defaults to mainnet). 51 | * @param {ClientOptions} [options] - Options for enabling L402 support. 52 | */ 53 | constructor(key: string = "", environment: InscriptionEnv = InscriptionEnvNetwork.mainnet, options?: ClientOptions) { 54 | this.api_key = key; 55 | environment = InscriptionEnvNetwork[environment]??InscriptionEnvNetwork.mainnet; 56 | this.env = environment; 57 | 58 | /** 59 | * Creates a new Axios instance with appropriate headers. 60 | * @returns {AxiosInstance} The new Axios instance. 61 | */ 62 | const createInstance = (): AxiosInstance => { 63 | const headers: Record = { 64 | "Content-Type": "application/json", 65 | }; 66 | 67 | // Add the API key header only if this.api_key has a value 68 | if (this.api_key) { 69 | headers["x-api-key"] = this.api_key; 70 | } 71 | 72 | // Choose the base URL based on whether L402 is used or not 73 | const baseURL = options?.useL402 74 | ? "https://ordinalsbot.ln.sulu.sh/launchpad/" 75 | : `${EnvNetworkExplorer[this.env] || EnvNetworkExplorer.mainnet}/launchpad/`; 76 | 77 | // Create the Axios client with the appropriate base URL 78 | const client = axios.create({ 79 | baseURL, 80 | headers: headers, 81 | }); 82 | 83 | client.interceptors.response.use( 84 | ({ data }) => ("data" in data ? data.data : data), 85 | (err) => { 86 | if (axios.isAxiosError(err) && err.response?.status !== 402) { // avoid modifying L402 errors. 87 | throw new InscriptionError( 88 | err.message, 89 | err.response?.statusText, 90 | err.response?.status 91 | ); 92 | } 93 | 94 | if (err instanceof Error) throw err; 95 | 96 | return err; 97 | } 98 | ); 99 | 100 | // If L402 is enabled and configuration is provided, set up the L402 interceptor 101 | if (options?.useL402 && options.l402Config) { 102 | setupL402Interceptor(client as any, options.l402Config.wallet, options.l402Config.tokenStore); 103 | }; 104 | 105 | return client; 106 | }; 107 | 108 | // Create the Axios instance 109 | this.instanceV1 = createInstance(); 110 | } 111 | 112 | /** 113 | * Creates a new launchpad marketplace. 114 | * @param {LaunchpadMarketplaceCreateRequest} createMarketplaceRequest The request body for creating a new marketplace. 115 | * @returns {Promise} A promise that resolves to the response from the API. 116 | */ 117 | async createMarketPlace( 118 | createMarketplaceRequest: LaunchpadMarketplaceCreateRequest 119 | ): Promise { 120 | return this.instanceV1.post(`/create-marketplace`, { 121 | ...createMarketplaceRequest, 122 | }); 123 | } 124 | 125 | /** 126 | * Creates a new launchpad. 127 | * @param {CreateLaunchpadRequest} createLaunchpadRequest The request body for creating a new launchpad. 128 | * @returns {Promise} A promise that resolves to the response from the API. 129 | */ 130 | async createLaunchpad( 131 | createLaunchpadRequest: CreateLaunchpadRequest 132 | ): Promise { 133 | return this.instanceV1.post(`/create-launch`, { 134 | ...createLaunchpadRequest, 135 | }); 136 | } 137 | 138 | /** 139 | * gets launchpad status to sign traction. 140 | * @param {GetLaunchpadStatusRequest} getLaunchpadStatusRequest The request body for get launchpad status. 141 | * @returns {Promise} A promise that resolves to the response from the API. 142 | */ 143 | async getLaunchpadStatus( 144 | getLaunchpadStatusRequest: GetLaunchpadStatusRequest 145 | ): Promise { 146 | return this.instanceV1.get( 147 | `/get-launch-psbt/${getLaunchpadStatusRequest.launchpadId}` 148 | ); 149 | } 150 | 151 | /** 152 | * Updated the signed psbt by the seller on the launchpad 153 | * @param {SaveLaunchpadRequest} saveLaunchpadRequest - The request body to update the launchpad data. 154 | * @returns {Promise} A promise that resolves to the response from the API. 155 | */ 156 | async saveLaunchpad( 157 | saveLaunchpadRequest: SaveLaunchpadRequest 158 | ): Promise { 159 | return this.instanceV1.post(`/save-launch`, { 160 | ...saveLaunchpadRequest, 161 | }); 162 | } 163 | 164 | /** 165 | * Get all the launchpad listing 166 | * @param {GetListingRequest} getListingRequest - The request object for get all launchpad. 167 | * @returns {Promise} A promise that resolves to the response from the API. 168 | */ 169 | getLaunchpadListing( 170 | getListingRequest: GetListingRequest 171 | ): Promise { 172 | return this.instanceV1.post(`/get-listings`, { 173 | ...getListingRequest, 174 | }); 175 | } 176 | 177 | /** 178 | * Get buyer launhcpad allocation 179 | * @param {GetAllocationRequest} getAllocationRequest - The request object for buyer launhcpad allocations. 180 | * @returns {Promise} A promise that resolves to the response from the API. 181 | */ 182 | getAllocation( 183 | getAllocationRequest: GetAllocationRequest 184 | ): Promise { 185 | return new Promise(async (resolve) => { 186 | try { 187 | resolve( 188 | await this.instanceV1.post(`/get-allocation`, { 189 | ...getAllocationRequest, 190 | }) 191 | ); 192 | } catch (error) { 193 | resolve({ phases: [] }); 194 | } 195 | }); 196 | } 197 | 198 | /** 199 | * Confirms Padding Outputs 200 | * @param {ConfirmPaddingOutputsRequest} confirmPaddingOutputsRequest - The request object for confirms padding outputs 201 | * @returns {Promise} A promise that resolves to the response from the API. 202 | */ 203 | async confirmPaddingOutputs( 204 | confirmPaddingOutputsRequest: ConfirmPaddingOutputsRequest 205 | ): Promise { 206 | return this.instanceV1.post(`/confirm-padding-outputs`, { 207 | ...confirmPaddingOutputsRequest, 208 | }); 209 | } 210 | 211 | /** 212 | * Setup the padding output 213 | * @param {SetupPaddingOutputsRequest} setupPaddingOutputsRequest - The request object for buyer setup padding outputs. 214 | * @returns {Promise} A promise that resolves to the response from the API. 215 | */ 216 | async setupPaddingOutputs( 217 | setupPaddingOutputsRequest: SetupPaddingOutputsRequest 218 | ): Promise { 219 | return this.instanceV1.post(`/setup-padding-outputs`, { 220 | ...setupPaddingOutputsRequest, 221 | }); 222 | } 223 | 224 | /** 225 | * Creates the launchpad offer 226 | * @param {CreateLaunchpadOfferRequest} createLaunchpadOfferRequest - The request object for create Launchpad Offer. 227 | * @returns {Promise} A promise that resolves to the response from the API. 228 | */ 229 | async createLaunchpadOffer( 230 | createLaunchpadOfferRequest: CreateLaunchpadOfferRequest 231 | ): Promise { 232 | return this.instanceV1.post(`/create-launch-offer`, { 233 | ...createLaunchpadOfferRequest, 234 | }); 235 | } 236 | 237 | /** 238 | * submits the launchpad offer and gets the tansaction id 239 | * @param {SubmitLaunchpadOfferRequest} submitLaunchpadOfferRequest - The request object for create Launchpad Offer. 240 | * @returns {Promise} A promise that resolves to the response from the API. 241 | */ 242 | async submitLaunchpadOffer( 243 | submitLaunchpadOfferRequest: SubmitLaunchpadOfferRequest 244 | ): Promise { 245 | return this.instanceV1.post(`/submit-launch-offer`, { 246 | ...submitLaunchpadOfferRequest, 247 | }); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OrdinalsBot Node.js Library 2 | 3 | [![Version](https://img.shields.io/npm/v/ordinalsbot.svg)](https://www.npmjs.org/package/ordinalsbot) 4 | [![](https://badgen.net/npm/dt/ordinalsbot)](https://www.npmjs.com/package/ordinalsbot) 5 | [![Try ordinalsbot on RunKit](https://badge.runkitcdn.com/ordinalsbot.svg)](https://npm.runkit.com/ordinalsbot) 6 | 7 | The OrdinalsBot Node library provides convenient access to the OrdinalsBot API from 8 | applications written in JavaScript. 9 | 10 | ## Documentation 11 | 12 | You can find examples [here](examples/example.ts). For more information refer to our [API docs](https://docs.ordinalsbot.com). 13 | 14 | ## Installation 15 | 16 | Install the package with: 17 | 18 | npm install ordinalsbot --save 19 | or 20 | yarn add ordinalsbot 21 | 22 | ## Import and intialization 23 | 24 | The package needs to be configured with your account's API key which you can get by opening a ticket in our Discord for now. Our developer dashboard is coming soon... 25 | 26 | ```js 27 | import { Ordinalsbot } from "ordinalsbot"; 28 | 29 | /** 30 | * Creates a new instance of Ordinalsbot with the provided API key, network type, optional TokenPay API key, and options. 31 | * 32 | * @param {string} API_KEY - The API key to authenticate requests. 33 | * @param {string} network - The network type to be used. Allowed values are 'testnet', 'mainnet', 'signet'. Defaults to 'mainnet'. 34 | * @param {string} [TOKENPAY_API_KEY] - Optional API key for TokenPay. 35 | * @param {ClientOptions} [options] - Optional configuration options for the client. 36 | * @returns {Ordinalsbot} An instance of the Ordinalsbot class. 37 | */ 38 | const ordinalsbotObj = new Ordinalsbot(API_KEY, "testnet", TOKENPAY_API_KEY); 39 | const marketPlace = ordinalsbotObj.MarketPlace(); 40 | const inscription = ordinalsbotObj.Inscription(); 41 | const launchpad = ordinalsbotObj.Launchpad(); 42 | const mempool = ordinalsbotObj.Mempool(); 43 | const satextractor = ordinalsbotObj.Satextractor(); 44 | const satscanner = ordinalsbotObj.Satscanner(); 45 | // To use TokenPay, the `TOKENPAY_API_KEY` must be passed to the Ordinalsbot class 46 | const tokenPay = ordinalsbotObj.TokenPay(); 47 | ``` 48 | 49 | ## Usage 50 | 51 | ```js 52 | try { 53 | // create new order 54 | const order = await inscription.createOrder({ 55 | files: [ 56 | { 57 | size: 10, 58 | type: "plain/text", 59 | name: "my-text-inscription-file.txt", 60 | dataURL: "data:plain/text;base64,dGVzdCBvcmRlcg==", 61 | }, 62 | ], 63 | lowPostage: true, 64 | receiveAddress: "", 65 | fee: 11, 66 | }); 67 | console.log("Order: ", order); 68 | } catch (error) { 69 | console.log("Exception: ", error); 70 | } 71 | 72 | try { 73 | // get marketplace listings 74 | const listing = await marketplace.getListing(); 75 | console.log("Marketplaces: ", listing); 76 | } catch (e) { 77 | console.log("Exception: ", e); 78 | } 79 | ``` 80 | 81 | ### Using Promises 82 | 83 | Every method returns a chainable promise which can be used instead of a regular 84 | callback: 85 | 86 | ```js 87 | // create new order 88 | inscription 89 | .createOrder({ 90 | files: [ 91 | { 92 | size: 10, 93 | type: "plain/text", 94 | name: "my-text-inscription-file.txt", 95 | dataURL: "data:plain/text;base64,dGVzdCBvcmRlcg==", 96 | }, 97 | ], 98 | lowPostage: true, 99 | receiveAddress: "", 100 | fee: 11, 101 | }) 102 | .then((order) => { 103 | console.log("Order: ", order); 104 | }) 105 | .catch((error) => { 106 | console.log("Exception: ", error); 107 | }); 108 | 109 | // get marketplace listings 110 | marketplace 111 | .getListing() 112 | .then((listings) => { 113 | console.log("Order: ", listings); 114 | }) 115 | .catch((error) => { 116 | console.log("Exception: ", error); 117 | }); 118 | ``` 119 | 120 | ### Run examples 121 | You can check and run examples after setting your API Key 122 | ``` 123 | npx ts-node examples/example.ts 124 | ``` 125 | 126 | ### Using Wallets on the client side 127 | 128 | For client-side applications, the methods `marketplace.createListing()`, `marketplace.createOffer()`, and `marketplace.setupPaddingOutputs()` support the `walletProvider` parameter. This optional string parameter allows for specifying the wallet name, with current support for the Xverse wallet and plans to include additional wallets soon. When the `walletProvider` parameter is specified it triggers the invocation of the specified wallet, prompting the user to sign the transaction. This integration significantly streamlines the process by reducing the need for multiple API calls and simplifies the structuring of data required for wallet invocation and transaction signing. 129 | 130 | The following example demonstrates how to create a listing for sale. When you invoke `marketplace.createListing()` and specify `"xverse"` as the `walletProvider`, it initiates an API call to generate a listing transaction. The method processes the response, formatting the data according to the requirements of the Xverse wallet. Subsequently, the Xverse wallet is activated to prompt the user to sign the transaction. Once the user successfully signs, this method additionally triggers the `save-listing` API, using the appropriately formatted data. Finally, it returns the confirmed listing information as the response. 131 | 132 | ```js 133 | import { Ordinalsbot } from "ordinalsbot"; 134 | 135 | /** 136 | * Creates a new instance of Ordinalsbot with the provided API key, network type, optional TokenPay API key, and options. 137 | * 138 | * @param {string} API_KEY - The API key to authenticate requests. 139 | * @param {string} network - The network type to be used. Allowed values are 'testnet', 'mainnet', 'signet'. Defaults to 'mainnet'. 140 | * @param {string} [TOKENPAY_API_KEY] - Optional API key for TokenPay. 141 | * @param {ClientOptions} [options] - Optional configuration options for the client. 142 | * @returns {Ordinalsbot} An instance of the Ordinalsbot class. 143 | */ 144 | const ordinalsbotObj = new Ordinalsbot(API_KEY, "testnet"); 145 | const marketPlace = ordinalsbotObj.MarketPlace(); 146 | 147 | const listingRequest = { 148 | sellerOrdinals: [{ 149 | id: "0c9ac6fb5d4516aade728882e230b0d78337732ea71915c7fbc0cdabe5d29f3ci0", 150 | price: "1234" 151 | }], 152 | sellerPaymentAddress: "2NAurbuXjBK5dztb416bh98ibDS7MKxV75C", 153 | sellerOrdinalPublicKey: "594a4aaf5da5b144d0fa6b47987d966029d892fbc4aebb23214853e8b053702e", 154 | sellerOrdinalAddress: "tb1p79l2gnn7u8uqxfepd7ddeeajzrmuv9nkl20wpf77t2u473a2h89s483yk3", 155 | walletProvider: WALLET_PROVIDER.xverse 156 | }; 157 | 158 | //call the marketplace listing method 159 | const response = await marketPlace.createListing(listingRequest); 160 | 161 | // this will invoke wallet and prompt the user to sign the transaction 162 | // Once signed the listing data will be saved and the saved listing will be 163 | // returned as the response 164 | 165 | ``` 166 | 167 | ### Using L402 for API Access 168 | 169 | [L402](https://docs.sulu.sh/docs/introduction-to-L402) is an alternative way to pay and authenticate access to an API, powered natively by Bitcoin and the Lightning Network. With L402, developers do not need an auth key to use an API. Instead, a micro lightning payment is requested for anonymous authentication. Once payment has been done, the preimage of the payment is used as proof-of-payment to grant access to API resources for a limited amount of calls. 170 | 171 | OrdinalsBot has partnered with [Sulu](https://www.sulu.sh/) to offer developers all across the world access to their APIs through L402. All current OrdinalsBot endpoints can be paid for and accessed through L402 using the hostname `https://ordinalsbot.ln.sulu.sh` . 172 | 173 | For a price of `5 sats for 5 calls` developers can access any endpoint without rate limits or needing to obtain an auth key. 174 | 175 | Furthermore, Sulu has integrated L402 functionality into the OrdinalsBot Node.js Library. With access to a Lightning Node or wallet, developers can seamlessly pay for usage of the OrdinalsBot API using Bitcoin: 176 | 177 | ```javascript 178 | import { AlbyWallet, MemoryTokenStore } from "l402"; 179 | const { Client } = require('@getalby/sdk'); 180 | import { ClientOptions, Satscanner } from "../src"; 181 | require('dotenv').config(); 182 | 183 | // Load the Alby token from an environment variable 184 | const albyToken = process.env.ALBY_BEARER_TOKEN; 185 | if (!albyToken) { 186 | console.error('Missing ALBY_BEARER_TOKEN environment variable.'); 187 | process.exit(1); 188 | } 189 | 190 | const albyClient = new Client(albyToken) 191 | 192 | // Initialize the AlbyWallet using the bearer token 193 | const albyWallet = new AlbyWallet(albyClient); 194 | 195 | // Initialize the MemoryTokenStore 196 | const store = new MemoryTokenStore({ 197 | keyMode: 'hostname-only' // this is IMPORTANT, since all endpoints are monetized 198 | // using the same hostname-level package ie. the same 199 | // L402 token can be used for all endpoints. 200 | }); 201 | 202 | // Create Options to enable L402 access 203 | const options: ClientOptions = { 204 | useL402: true, 205 | l402Config: { 206 | wallet: albyWallet, 207 | tokenStore: store 208 | } 209 | }; 210 | 211 | // Create a new Satscanner instance 212 | // Allowed environments are ('testnet', 'mainnet', 'signet') 213 | // default environment is 'mainnet'. 214 | const satscanner = new Satscanner("", "mainnet", options); 215 | 216 | /** 217 | * use satscanner to get information about utxos owned by an address 218 | */ 219 | (async () => { 220 | try { 221 | const response = await satscanner.findSpecialRanges({ address: "bc1pjqmzr4ad437ltvfyn8pslcy8quls9ujfkrudpz6qxdh2j75qrncq44mp47" }); 222 | console.log(response); 223 | } catch (error) { 224 | console.error(`${error.status} | ${error.message} | ${error.data}`); 225 | } 226 | })(); 227 | ``` 228 | 229 | For more details on how the integration works, check out Sulu's `l402` [NPM package](https://www.npmjs.com/package/l402). At the time of writing, the[ Alby Wallet](https://getalby.com/) is the only officially supported wallet, but developers are free to write their own wallet integrations implementing the Wallet interface in the `l402` package. 230 | 231 | ## Testing 232 | `npm run test` 233 | -------------------------------------------------------------------------------- /src/types/tokenpay_types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a request to create an rune order. 3 | * 4 | * @interface CreateRuneOrderRequest 5 | */ 6 | export interface CreateRuneOrderRequest { 7 | /** 8 | * The amount for the order. 9 | * 10 | * @type {number} 11 | */ 12 | amount: number; 13 | 14 | /** 15 | * The token associated with the order. 16 | * 17 | * @type {string} 18 | */ 19 | token: string; 20 | 21 | /** 22 | * The webhook Url 23 | * 24 | * @type {string} 25 | */ 26 | webhookUrl?: string; 27 | 28 | /** 29 | * The Additional Fee 30 | * 31 | * @type {number} 32 | */ 33 | additionalFee?: number; 34 | 35 | /** 36 | * The description for the order 37 | * 38 | * @type {string} 39 | */ 40 | description?: string; 41 | } 42 | 43 | /** 44 | * Represents a fee or token charge. 45 | * 46 | * @interface Charge 47 | */ 48 | interface Charge { 49 | /** 50 | * The amount of the charge. 51 | * 52 | * @type {number} 53 | */ 54 | amount: number; 55 | 56 | /** 57 | * The token associated with the charge. 58 | * 59 | * @type {string} 60 | */ 61 | token: string; 62 | 63 | /** 64 | * The address associated with the charge. 65 | * 66 | * @type {string} 67 | */ 68 | address: string; 69 | 70 | /** 71 | * The state of the charge. 72 | * 73 | * @type {string} 74 | */ 75 | state: string; 76 | 77 | /** 78 | * The protocol used for the charge. 79 | * 80 | * @type {string} 81 | */ 82 | protocol: string; 83 | 84 | /** 85 | * The transaction ID of the charge. 86 | * 87 | * @type {string | null} 88 | */ 89 | txid: string | null; 90 | 91 | /** 92 | * The creation timestamp of the charge. 93 | * 94 | * @type {number} 95 | */ 96 | createdAt: number; 97 | 98 | /** 99 | * The additional of the charge. 100 | * 101 | * @type {number} 102 | */ 103 | additionalFee: number; 104 | } 105 | 106 | /** 107 | * Represents an order. 108 | * 109 | * @interface RuneOrderResponse 110 | */ 111 | export interface RuneOrderResponse { 112 | /** 113 | * The ID of the order. 114 | * 115 | * @type {string} 116 | */ 117 | id: string; 118 | 119 | /** 120 | * The creation timestamp of the order. 121 | * 122 | * @type {number} 123 | */ 124 | createdAt: number; 125 | 126 | /** 127 | * The account ID associated with the order. 128 | * 129 | * @type {string} 130 | */ 131 | accountId: string; 132 | 133 | /** 134 | * The fee charge details. 135 | * 136 | * @type {Charge} 137 | */ 138 | feeCharge: Charge; 139 | 140 | /** 141 | * The token charge details. 142 | * 143 | * @type {Charge} 144 | */ 145 | tokenCharge: Charge; 146 | 147 | /** 148 | * The webhook URL for the order. 149 | * 150 | * @type {string | null} 151 | */ 152 | webhookUrl: string | null; 153 | 154 | /** 155 | * The state of the order. 156 | * 157 | * @type {string} 158 | */ 159 | state: string; 160 | 161 | /** 162 | * The description for the order. 163 | * 164 | * @type {string | null} 165 | */ 166 | description: string | null; 167 | } 168 | 169 | /** 170 | * Interface representing a request to create a Payment Partially Signed Bitcoin Transaction (PSBT). 171 | */ 172 | export interface CreatePaymentPSBTRequest { 173 | /** 174 | * The unique identifier for the order. 175 | * @type {string} 176 | */ 177 | orderId: string; 178 | 179 | /** 180 | * The address to which the payment will be made. 181 | * @type {string} 182 | */ 183 | paymentAddress: string; 184 | 185 | /** 186 | * The public key associated with the payment. 187 | * @type {string} 188 | */ 189 | paymentPublicKey: string; 190 | 191 | /** 192 | * The ordinal address. 193 | * @type {string} 194 | */ 195 | ordinalAddress: string; 196 | 197 | /** 198 | * The ordinal public key. 199 | * @type {string} 200 | */ 201 | ordinalPublicKey: string; 202 | 203 | /** 204 | * The fee rate for the transaction, specified in satoshis per byte. 205 | * This field is optional. 206 | * @type {number} 207 | * @optional 208 | */ 209 | feeRate?: number; 210 | 211 | /** 212 | * Wallet Provider name 213 | * This field is optional. 214 | * @type {string} 215 | * @optional 216 | */ 217 | walletProvider?: string; 218 | } 219 | 220 | /** 221 | * Interface representing a rune input. 222 | */ 223 | interface RuneInput { 224 | /** 225 | * An array of indices for the rune inputs. 226 | * @type {number[]} 227 | */ 228 | indices: number[]; 229 | 230 | /** 231 | * The address associated with the rune inputs. 232 | * @type {string} 233 | */ 234 | address: string; 235 | } 236 | 237 | /** 238 | * Interface representing the inputs for a payment. 239 | */ 240 | interface PaymentInputs { 241 | /** 242 | * An array of indices for the payment inputs. 243 | * @type {number[]} 244 | */ 245 | indices: number[]; 246 | 247 | /** 248 | * The address associated with the payment inputs. 249 | * @type {string} 250 | */ 251 | address: string; 252 | } 253 | 254 | /** 255 | * Interface representing the response from creating a Payment Partially Signed Bitcoin Transaction (PSBT). 256 | */ 257 | export interface CreatePaymentPSBTResponse { 258 | /** 259 | * The PSBT encoded in Base64 format. 260 | * @type {string} 261 | */ 262 | psbtBase64: string; 263 | 264 | /** 265 | * The rune input used in the PSBT. 266 | * @type {RuneInput} 267 | */ 268 | runeInputs: RuneInput; 269 | 270 | /** 271 | * The payment inputs used in the PSBT. 272 | * @type {PaymentInputs} 273 | */ 274 | paymentInputs: PaymentInputs; 275 | 276 | /** 277 | * The PSBT encoded in hexadecimal format. 278 | * @type {string} 279 | */ 280 | psbtHex: string; 281 | } 282 | 283 | /** 284 | * Represents the response from a SatsConnectWrapper operation. 285 | */ 286 | export interface SatsConnectWrapperResponse { 287 | /** 288 | * Indicates if the operation was successful. 289 | */ 290 | success: boolean; 291 | 292 | /** 293 | * A message describing the result of the operation. 294 | */ 295 | message: string; 296 | 297 | /** 298 | * (Optional) The PSBT (Partially Signed Bitcoin Transaction) in Base64 format. 299 | */ 300 | psbtBase64?: string; 301 | 302 | /** 303 | * (Optional) The transaction ID. 304 | */ 305 | txId?: string; 306 | } 307 | 308 | /** 309 | * Represents the request payload from a CheckTransactionAsTxid method. 310 | */ 311 | export interface CheckTransactionAsTxidRequest { 312 | /** 313 | * The transaction ID. 314 | */ 315 | txid: string; 316 | } 317 | 318 | /** 319 | * Represents the response from a CheckTransactionAsTxidResponse operation. 320 | */ 321 | export interface CheckTransactionAsTxidResponse { 322 | /** 323 | * The transaction ID. 324 | */ 325 | txid: string; 326 | } 327 | 328 | /** 329 | * Represents the request payload from a getOrder method. 330 | */ 331 | export interface GetOrderRequest { 332 | /** 333 | * The order ID. 334 | */ 335 | orderId: string; 336 | } 337 | 338 | /** 339 | * Represents the response from a CheckTransactionAsTxidResponse operation. 340 | */ 341 | export interface GetOrderResponse extends RuneOrderResponse { 342 | // ... response from RuneOrderResponse 343 | } 344 | 345 | /** 346 | * Interface representing a request to withdraw an account. 347 | */ 348 | export interface AccountWithdrawRequest { 349 | /** 350 | * The protocol used for the withdraw (e.g., "rune"). 351 | */ 352 | protocol: string; 353 | 354 | /** 355 | * The token to be withdrawn (e.g., 'SHITCOIN'). 356 | */ 357 | token: string; 358 | 359 | /** 360 | * The amount of the token to be withdrawn. 361 | */ 362 | amount: number; 363 | 364 | /** 365 | * The address to which the token will be sent. 366 | */ 367 | address: string; 368 | } 369 | 370 | /** 371 | * Represents the response from a AccountWithDraw Response operation. 372 | */ 373 | export interface AccountWithdrawResponse { 374 | /** 375 | * The unique identifier for the withdraw. 376 | */ 377 | id: string; 378 | 379 | /** 380 | * The timestamp when the withdraw was created. 381 | */ 382 | createdAt: number; 383 | 384 | /** 385 | * The account ID associated with the withdraw. 386 | */ 387 | accountId: string; 388 | 389 | /** 390 | * The protocol used for the withdraw (e.g., "rune"). 391 | */ 392 | protocol: string; 393 | 394 | /** 395 | * The token involved in the withdraw (e.g., "SHITCOIN"). 396 | */ 397 | token: string; 398 | 399 | /** 400 | * The amount of the token being transacted. 401 | */ 402 | amount: number; 403 | 404 | /** 405 | * The address to which the withdraw is sent. 406 | */ 407 | address: string; 408 | 409 | /** 410 | * The current state of the withdraw. 411 | */ 412 | state: string; 413 | 414 | /** 415 | * The withdraw ID (if available). 416 | */ 417 | txid: string | null; 418 | 419 | /** 420 | * The fee charged by the blockchain network for processing the withdraw (if available). 421 | */ 422 | chainFee: number | null; 423 | 424 | /** 425 | * The number of attempts made to process the withdraw. 426 | */ 427 | tries: number; 428 | 429 | /** 430 | * The timestamp when the withdraw started processing (in milliseconds since epoch, if available). 431 | */ 432 | processingAt: number | null; 433 | 434 | /** 435 | * The fee per byte for the withdraw (if available). 436 | */ 437 | feePerByte: number | null; 438 | } 439 | 440 | 441 | /** 442 | * Represents the request payload from a getAccountWithdraw method. 443 | */ 444 | export interface GetAccountWithdrawRequest { 445 | /** 446 | * The unique identifier for the withdraw . 447 | */ 448 | withdrawalId: string; 449 | } 450 | 451 | /** 452 | * Represents a rune token balances. 453 | * Each key in the object is a rune token type, and the value is the balance. 454 | */ 455 | interface RuneTokenBalance { 456 | [key: string]: number; 457 | } 458 | 459 | /** 460 | * Represents account balances where each key is a unique account identifier. 461 | * The value for each key is a `CoinData` object, which contains the balances of various coins for that account. 462 | */ 463 | export interface AccountBalanceResponse { 464 | [key: string]: RuneTokenBalance; 465 | } -------------------------------------------------------------------------------- /src/inscription/index.ts: -------------------------------------------------------------------------------- 1 | import { InscriptionClient } from "../client"; 2 | import { ClientOptions, InscriptionEnv, InscriptionEnvNetwork, v1 } from "../types"; 3 | import { RunesEtchOrderRequest, RunesEtchOrderResponse, RunesMintOrderRequest, RunesMintOrderResponse } from "../types/runes_types"; 4 | import { CreateParentChildPsbtRequest, CreateParentChildPsbtResponse, CreateSpecialSatsRequest, CreateSpecialSatsResponse } from '../types/v1'; 5 | import { BitcoinNetworkType, SignTransactionResponse, signTransaction } from 'sats-connect'; 6 | 7 | /** 8 | * Main class for interacting with the Inscription API. 9 | */ 10 | export class Inscription { 11 | /** The instance of InscriptionClient. */ 12 | instance!: InscriptionClient; 13 | 14 | private network: BitcoinNetworkType; 15 | 16 | /** 17 | * Creates an instance of Inscription. 18 | * @param {string} [key=''] The API key. 19 | * @param {InscriptionEnv} [environment='mainnet'] - The environment (e.g., "testnet" , "mainnet", "signet") (optional, defaults to mainnet). 20 | * @param {ClientOptions} [options] - Options for enabling L402 support. 21 | */ 22 | constructor(key: string = "", environment: InscriptionEnv = InscriptionEnvNetwork.mainnet, options?: ClientOptions) { 23 | if (this.instance !== undefined) { 24 | console.error("inscription.setCredentials was called multiple times"); 25 | return; 26 | } 27 | environment = InscriptionEnvNetwork[environment]??InscriptionEnvNetwork.mainnet; 28 | switch (environment) { 29 | case InscriptionEnvNetwork.mainnet: 30 | this.network = BitcoinNetworkType.Mainnet; 31 | break; 32 | case InscriptionEnvNetwork.testnet: 33 | this.network = BitcoinNetworkType.Testnet; 34 | break; 35 | case InscriptionEnvNetwork.signet: 36 | // @ts-ignore 37 | this.network = BitcoinNetworkType.Signet; 38 | break; 39 | 40 | default: 41 | this.network = BitcoinNetworkType.Mainnet 42 | break; 43 | } 44 | this.instance = new InscriptionClient(key, environment, options); 45 | } 46 | 47 | /** 48 | * Gets the price for a given price request. 49 | * @param {v1.InscriptionPriceRequest} priceRequest The price request. 50 | * @returns {Promise} A promise that resolves with the price response. 51 | */ 52 | getPrice( 53 | priceRequest: v1.InscriptionPriceRequest 54 | ): Promise { 55 | return this.instance.getPrice(priceRequest); 56 | } 57 | 58 | /** 59 | * Creates an order with the given order request. 60 | * @param {v1.InscriptionOrderRequest} order The order request. 61 | * @returns {Promise} A promise that resolves with the created order. 62 | */ 63 | createOrder(order: v1.InscriptionOrderRequest): Promise { 64 | return this.instance.createOrder(order); 65 | } 66 | 67 | /** 68 | * Gets an order by its ID. 69 | * @param {string} id The ID of the order to retrieve. 70 | * @returns {Promise} A promise that resolves with the retrieved order. 71 | */ 72 | getOrder(id: string): Promise { 73 | return this.instance.getOrder(id); 74 | } 75 | 76 | /** 77 | * Creates an order with the given order request. 78 | * @param {v1.DirectInscriptionOrderRequest} order The order request. 79 | * @returns {Promise} A promise that resolves with the created order. 80 | */ 81 | createDirectOrder(order: v1.DirectInscriptionOrderRequest): Promise { 82 | return this.instance.createDirectOrder(order); 83 | } 84 | 85 | /** 86 | * Creates a collection with the given collection create request. 87 | * @param {v1.InscriptionCollectionCreateRequest} collection The collection create request. 88 | * @returns {Promise} A promise that resolves with the created collection response. 89 | */ 90 | createCollection( 91 | collection: v1.InscriptionCollectionCreateRequest 92 | ): Promise { 93 | return this.instance.createCollection(collection); 94 | } 95 | 96 | /** 97 | * updates collection phases with the given collection id. 98 | * @param {v1.UpdateCollectionPhasesRequest} collection The request data. 99 | * @returns {Promise} A promise that resolves with the updated collection response. 100 | */ 101 | updateCollectionPhases( 102 | collection: v1.UpdateCollectionPhasesRequest 103 | ): Promise { 104 | return this.instance.updateCollectionPhases(collection); 105 | } 106 | 107 | /** 108 | * Fetch the allocation and inscribedCount of reciever address by phases from collection. 109 | * @param {v1.GetAllocationRequest} allocation The request data. 110 | * @returns {Promise} A promise that resolves with the allocation response. 111 | */ 112 | getAllocation( 113 | allocation: v1.GetAllocationRequest 114 | ): Promise { 115 | return this.instance.getAllocation(allocation); 116 | } 117 | 118 | /** 119 | * Creates a collection order with the given collection order request. 120 | * @param {v1.InscriptionCollectionOrderRequest} collectionOrder The collection order request. 121 | * @returns {Promise} A promise that resolves with the created collection order. 122 | */ 123 | createCollectionOrder( 124 | collectionOrder: v1.InscriptionCollectionOrderRequest 125 | ): Promise { 126 | return this.instance.createCollectionOrder(collectionOrder); 127 | } 128 | 129 | /** 130 | * Creates a text order with the given text order request. 131 | * @param {v1.InscriptionTextOrderRequest} order The text order request. 132 | * @returns {Promise} A promise that resolves with the created text order. 133 | */ 134 | createTextOrder( 135 | order: v1.InscriptionTextOrderRequest 136 | ): Promise { 137 | return this.instance.createTextOrder(order); 138 | } 139 | 140 | /** 141 | * Creates an runes etch order with the given order request. 142 | * @param {RunesEtchOrderRequest} order The order request. 143 | * @returns {Promise} A promise that resolves with the created order. 144 | */ 145 | createRunesEtchOrder( 146 | order: RunesEtchOrderRequest 147 | ): Promise { 148 | return this.instance.createRunesEtchOrder(order); 149 | } 150 | 151 | /** 152 | * Creates an runes mint order with the given order request. 153 | * @param {RunesMintOrderRequest} order The order request. 154 | * @returns {Promise} A promise that resolves with the created order. 155 | */ 156 | createRunesMintOrder( 157 | order: RunesMintOrderRequest 158 | ): Promise { 159 | return this.instance.createRunesMintOrder(order); 160 | } 161 | 162 | /** 163 | * Gets the inventory of available items. 164 | * @returns {Promise} A promise that resolves with the inventory response. 165 | */ 166 | getInventory(): Promise { 167 | return this.instance.getInventory(); 168 | } 169 | 170 | /** 171 | * Sets a referral code. 172 | * @param {v1.InscriptionReferralRequest} referral The referral request. 173 | * @returns {Promise} A promise that resolves with the referral set response. 174 | */ 175 | setReferralCode( 176 | referral: v1.InscriptionReferralRequest 177 | ): Promise { 178 | return this.instance.setReferralCode(referral); 179 | } 180 | 181 | /** 182 | * Gets the status of a referral. 183 | * @param {v1.InscriptionReferralRequest} referral The referral request. 184 | * @returns {Promise} A promise that resolves with the referral status response. 185 | */ 186 | getReferralStatus( 187 | referral: v1.InscriptionReferralRequest 188 | ): Promise { 189 | return this.instance.getReferralStatus(referral); 190 | } 191 | 192 | /** 193 | * Creates a special sats psbt. 194 | * @param {CreateSpecialSatsRequest} createSpecialSatsRequest The order request. 195 | * @returns {Promise} A promise that resolves with the psbt for special sats. 196 | */ 197 | async createSpecialSatsPSBT( 198 | createSpecialSatsRequest: CreateSpecialSatsRequest 199 | ): Promise { 200 | if (!createSpecialSatsRequest.walletProvider) { 201 | return this.instance.createSpecialSatsPSBT(createSpecialSatsRequest); 202 | } 203 | const result: CreateSpecialSatsResponse = await this.instance.createSpecialSatsPSBT(createSpecialSatsRequest); 204 | const inputsToSign = [ 205 | { 206 | address: createSpecialSatsRequest.ordinalAddress, 207 | signingIndexes: result.ordinalInputIndices 208 | }, 209 | { 210 | address: createSpecialSatsRequest.paymentAddress, 211 | signingIndexes: result.paymentInputIndices 212 | }, 213 | ]; 214 | 215 | // Create the payload for signing the transaction 216 | const payload = { 217 | network: { type: this.network }, 218 | message: 'Sign Payment Transaction', 219 | psbtBase64: result.psbtBase64, 220 | broadcast: true, 221 | inputsToSign, 222 | }; 223 | 224 | return new Promise((resolve, reject) => { 225 | signTransaction({ 226 | payload, 227 | onFinish: async (response: any) => { 228 | return resolve(response) 229 | }, 230 | onCancel: () => { 231 | console.log('Payment canceled'); 232 | } 233 | }); 234 | }); 235 | } 236 | 237 | /** 238 | * Creates a Parent-Child PSBT (Partially Signed Bitcoin Transaction). 239 | * 240 | * @param {CreateParentChildPsbtRequest} createParentChildPsbt - The request object for creating the Parent-Child PSBT. 241 | * @returns {Promise} A promise that resolves to either a SignTransactionResponse or CreateParentChildPsbtResponse. 242 | */ 243 | async createParentChildPsbt( 244 | createParentChildPsbt: CreateParentChildPsbtRequest 245 | ): Promise { 246 | if (!createParentChildPsbt.walletProvider) { 247 | return this.instance.createParentChildPsbt(createParentChildPsbt); 248 | } 249 | const result: CreateParentChildPsbtResponse = await this.instance.createParentChildPsbt(createParentChildPsbt); 250 | const inputsToSign = [ 251 | { 252 | address: createParentChildPsbt.ordinalsAddress, 253 | signingIndexes: result.ordinalInputIndices 254 | }, 255 | { 256 | address: createParentChildPsbt.paymentAddress, 257 | signingIndexes: result.paymentInputIndices 258 | }, 259 | ]; 260 | 261 | // Create the payload for signing the seller transaction 262 | const payload = { 263 | network: { type: this.network }, 264 | message: 'Sign Payment Transaction', 265 | psbtBase64: result.psbtBase64, 266 | broadcast: true, 267 | inputsToSign, 268 | }; 269 | 270 | return new Promise((resolve, reject) => { 271 | signTransaction({ 272 | payload, 273 | onFinish: async (response: any) => { 274 | return resolve(response) 275 | }, 276 | onCancel: () => { 277 | console.log('Payment canceled'); 278 | } 279 | }); 280 | }); 281 | } 282 | } 283 | --------------------------------------------------------------------------------