Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 3 additions & 24 deletions packages/bitbadgesjs-sdk/src/cli/commands/auctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from '../utils/indexer-options.js';
import { requireBb1AddressStrict } from '../utils/address.js';
import { addDeployOptions, runEmitOrDeploy } from '../utils/deploy-options.js';
import { normalizeCollection, validateCollectionOrExit } from '../utils/collection-options.js';
import { resolveAmount } from '../utils/amount.js';
import {
doesCollectionFollowAuctionProtocol,
Expand All @@ -25,34 +26,12 @@ import {
buildAuctionBidApproval,
buildAcceptAuctionBidMsg
} from '../../core/auctions.js';
import { BitBadgesCollection } from '../../api-indexer/BitBadgesCollection.js';
import { BigIntify } from '../../common/string-numbers.js';
import { UintRangeArray } from '../../core/uintRanges.js';
async function fetchCollection(collectionId: string, opts: NetworkFlags): Promise<any> {
const res = await callApi('GET', `/collection/${encodeURIComponent(collectionId)}`, opts);
const raw = res?.collection ?? res;
if (!raw) return raw;
try { return new BitBadgesCollection(raw).convert(BigIntify); } catch { return raw; }
return normalizeCollection(await callApi('GET', `/collection/${encodeURIComponent(collectionId)}`, opts));
}
function validateOrExit(collection: any, ctx: string): void {
if (!collection) {
process.stderr.write(`Error: collection not found while running ${ctx}.\n`);
process.exit(2);
}
const result = validateAuctionCollection(collection);
if (!result.valid) {
process.stderr.write(`Error: collection is not a valid Auction (failed in ${ctx}):\n`);
for (const e of result.errors) process.stderr.write(` - ${e}\n`);
if (result.warnings.length > 0) {
process.stderr.write('Warnings:\n');
for (const w of result.warnings) process.stderr.write(` - ${w}\n`);
}
process.exit(2);
}
if (result.warnings.length > 0 && process.env.BB_QUIET !== '1') {
process.stderr.write(`Warnings for ${ctx}:\n`);
for (const w of result.warnings) process.stderr.write(` - ${w}\n`);
}
validateCollectionOrExit(collection, ctx, validateAuctionCollection, 'Auction');
}

export const auctionsCommand = new Command('auctions').description(
Expand Down
23 changes: 3 additions & 20 deletions packages/bitbadgesjs-sdk/src/cli/commands/bounties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
} from '../utils/indexer-options.js';
import { requireBb1Address, requireBb1AddressStrict } from '../utils/address.js';
import { addDeployOptions, runEmitOrDeploy } from '../utils/deploy-options.js';
import { normalizeCollection, validateCollectionOrExit } from '../utils/collection-options.js';
import {
doesCollectionFollowBountyProtocol,
validateBountyCollection,
Expand All @@ -43,29 +44,11 @@ import {
} from '../../core/bounties.js';

async function fetchCollection(collectionId: string, opts: NetworkFlags): Promise<any> {
const res = await callApi('GET', `/collection/${encodeURIComponent(collectionId)}`, opts);
return res?.collection ?? res;
return normalizeCollection(await callApi('GET', `/collection/${encodeURIComponent(collectionId)}`, opts));
}

function validateOrExit(collection: any, ctx: string): void {
if (!collection) {
process.stderr.write(`Error: collection not found while running ${ctx}.\n`);
process.exit(2);
}
const result = validateBountyCollection(collection);
if (!result.valid) {
process.stderr.write(`Error: collection is not a valid Bounty (failed in ${ctx}):\n`);
for (const e of result.errors) process.stderr.write(` - ${e}\n`);
if (result.warnings.length > 0) {
process.stderr.write('Warnings:\n');
for (const w of result.warnings) process.stderr.write(` - ${w}\n`);
}
process.exit(2);
}
if (result.warnings.length > 0 && process.env.BB_QUIET !== '1') {
process.stderr.write(`Warnings for ${ctx}:\n`);
for (const w of result.warnings) process.stderr.write(` - ${w}\n`);
}
validateCollectionOrExit(collection, ctx, validateBountyCollection, 'Bounty');
}

function resolveStatus(collection: any, expirationTime: bigint): BountyStatus {
Expand Down
8 changes: 4 additions & 4 deletions packages/bitbadgesjs-sdk/src/cli/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ sharedOpts(
.requiredOption('--pay-amount <n>', 'Amount you send (display units)')
.requiredOption('--receive-denom <symbol|denom>', 'What you receive. BADGE, USDC, … or canonical denom (ubadge, ibc/...)')
.requiredOption('--receive-amount <n>', 'Amount you receive (display units)')
.option('--expiration <duration>', 'How long intent stays open (default 30d, matches `bb intents create`)', '30d')
.option('--expiration <when>', 'Intent expiry: ms-since-epoch (1748140800000) or duration (30d, 24h, monthly). Default 30d, matches `bb intents create`.', '30d')
).action(async (opts) => {
const { buildIntent } = await import('../../core/builders/intent.js');
if (opts.json) { emit(buildIntent(readJsonInput(opts.json)), opts); return; }
Expand All @@ -771,7 +771,7 @@ sharedOpts(
.requiredOption('--price <n>', 'Asking price (display units)')
.requiredOption('--denom <symbol|denom>', 'Price coin. BADGE, USDC, … or canonical denom (ubadge, ibc/...)')
.option('--max-sales <n>', 'Maximum number of sales', '1')
.option('--expiration <duration>', 'Listing duration', '30d')
.option('--expiration <when>', 'Listing expiry: ms-since-epoch (1748140800000) or duration (30d, 24h, monthly). Default 30d.', '30d')
).action(async (opts) => {
const { buildListing } = await import('../../core/builders/listing.js');
if (opts.json) { emit(buildListing(readJsonInput(opts.json)), opts); return; }
Expand Down Expand Up @@ -809,7 +809,7 @@ sharedOpts(
.requiredOption('--amount <n>', 'Number of tokens to sell')
.requiredOption('--price <n>', 'Total payment amount (display units)')
.requiredOption('--denom <symbol|denom>', 'Payment coin. BADGE, USDC, … or canonical denom (ubadge, ibc/...)')
.option('--expiration <duration>', 'How long intent stays open', '7d')
.option('--expiration <when>', 'Intent expiry: ms-since-epoch (1748140800000) or duration (24h, 7d, monthly). Default 24h, matches `bb prediction-markets buy/sell`.', '24h')
).action(async (opts) => {
const { buildPmSellIntent } = await import('../../core/builders/pm-sell-intent.js');
if (opts.json) { emit(buildPmSellIntent(readJsonInput(opts.json)), opts); return; }
Expand All @@ -828,7 +828,7 @@ sharedOpts(
.requiredOption('--amount <n>', 'Number of tokens to buy')
.requiredOption('--price <n>', 'Total payment amount (display units)')
.requiredOption('--denom <symbol|denom>', 'Payment coin. BADGE, USDC, … or canonical denom (ubadge, ibc/...)')
.option('--expiration <duration>', 'How long intent stays open', '7d')
.option('--expiration <when>', 'Intent expiry: ms-since-epoch (1748140800000) or duration (24h, 7d, monthly). Default 24h, matches `bb prediction-markets buy/sell`.', '24h')
).action(async (opts) => {
const { buildPmBuyIntent } = await import('../../core/builders/pm-buy-intent.js');
if (opts.json) { emit(buildPmBuyIntent(readJsonInput(opts.json)), opts); return; }
Expand Down
4 changes: 2 additions & 2 deletions packages/bitbadgesjs-sdk/src/cli/commands/credit-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { requireBb1AddressStrict } from '../utils/address.js';
import { resolveNetwork } from '../utils/io.js';
import { addDeployOptions, runEmitOrDeploy, type DeployOpts } from '../utils/deploy-options.js';
import { normalizeCollection } from '../utils/collection-options.js';
import {
doesCollectionFollowCreditTokenProtocol,
extractCreditTokenTiers,
Expand All @@ -31,8 +32,7 @@ import {
} from '../../core/credit-tokens.js';

async function fetchCollection(collectionId: string, opts: NetworkFlags): Promise<any> {
const res = await callApi('GET', `/collection/${encodeURIComponent(collectionId)}`, opts);
return res?.collection ?? res;
return normalizeCollection(await callApi('GET', `/collection/${encodeURIComponent(collectionId)}`, opts));
}

function validateOrExit(collection: any, ctx: string): void {
Expand Down
25 changes: 3 additions & 22 deletions packages/bitbadgesjs-sdk/src/cli/commands/crowdfunds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Command } from 'commander';
import { apiRequest, resolveApiKey, resolveBaseUrl } from '../utils/api-client.js';
import { requireBb1Address, requireBb1AddressStrict } from '../utils/address.js';
import { addDeployOptions, runEmitOrDeploy } from '../utils/deploy-options.js';
import { normalizeCollection, validateCollectionOrExit } from '../utils/collection-options.js';
import { addUnifiedNetworkOptions } from '../utils/network-options.js';
import { resolveAmount } from '../utils/amount.js';
import { emit, emitError } from '../utils/envelope.js';
Expand Down Expand Up @@ -46,30 +47,10 @@ async function callApi(method: 'GET' | 'POST', path: string, opts: NetworkFlags,
return apiRequest({ method, path, body, apiKey, baseUrl });
}
async function fetchCollection(collectionId: string, opts: NetworkFlags): Promise<any> {
const res = await callApi('GET', `/collection/${encodeURIComponent(collectionId)}`, opts);
const raw = res?.collection ?? res;
if (!raw) return raw;
try { return new BitBadgesCollection(raw).convert(BigIntify); } catch { return raw; }
return normalizeCollection(await callApi('GET', `/collection/${encodeURIComponent(collectionId)}`, opts));
}
function validateOrExit(collection: any, ctx: string): void {
if (!collection) {
process.stderr.write(`Error: collection not found while running ${ctx}.\n`);
process.exit(2);
}
const result = validateCrowdfundCollection(collection);
if (!result.valid) {
process.stderr.write(`Error: collection is not a valid Crowdfund (failed in ${ctx}):\n`);
for (const e of result.errors) process.stderr.write(` - ${e}\n`);
if (result.warnings.length > 0) {
process.stderr.write('Warnings:\n');
for (const w of result.warnings) process.stderr.write(` - ${w}\n`);
}
process.exit(2);
}
if (result.warnings.length > 0 && process.env.BB_QUIET !== '1') {
process.stderr.write(`Warnings for ${ctx}:\n`);
for (const w of result.warnings) process.stderr.write(` - ${w}\n`);
}
validateCollectionOrExit(collection, ctx, validateCrowdfundCollection, 'Crowdfund');
}

async function readRaised(collection: any, opts: NetworkFlags, details: any): Promise<bigint> {
Expand Down
24 changes: 3 additions & 21 deletions packages/bitbadgesjs-sdk/src/cli/commands/pay-requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
} from '../utils/indexer-options.js';
import { requireBb1Address, requireBb1AddressStrict } from '../utils/address.js';
import { addDeployOptions, runEmitOrDeploy } from '../utils/deploy-options.js';
import { normalizeCollection, validateCollectionOrExit } from '../utils/collection-options.js';
import {
doesCollectionFollowPaymentRequestProtocol,
validatePaymentRequestCollection,
Expand All @@ -41,9 +42,7 @@ import {
} from '../../core/payment-requests.js';

async function fetchCollection(collectionId: string, opts: NetworkFlags): Promise<any> {
const res = await callApi('GET', `/collection/${encodeURIComponent(collectionId)}`, opts);
// Indexer returns `{collection: {...}}` for the single-collection GET.
return res?.collection ?? res;
return normalizeCollection(await callApi('GET', `/collection/${encodeURIComponent(collectionId)}`, opts));
}

/**
Expand All @@ -52,24 +51,7 @@ async function fetchCollection(collectionId: string, opts: NetworkFlags): Promis
* same gate as the frontend view's short-circuit.
*/
function validateOrExit(collection: any, ctx: string): void {
if (!collection) {
process.stderr.write(`Error: collection not found while running ${ctx}.\n`);
process.exit(2);
}
const result = validatePaymentRequestCollection(collection);
if (!result.valid) {
process.stderr.write(`Error: collection is not a valid PaymentRequest (failed in ${ctx}):\n`);
for (const e of result.errors) process.stderr.write(` - ${e}\n`);
if (result.warnings.length > 0) {
process.stderr.write('Warnings:\n');
for (const w of result.warnings) process.stderr.write(` - ${w}\n`);
}
process.exit(2);
}
if (result.warnings.length > 0 && process.env.BB_QUIET !== '1') {
process.stderr.write(`Warnings for ${ctx}:\n`);
for (const w of result.warnings) process.stderr.write(` - ${w}\n`);
}
validateCollectionOrExit(collection, ctx, validatePaymentRequestCollection, 'PaymentRequest');
}

function resolveStatus(collection: any, expirationTime: bigint): PaymentRequestStatus {
Expand Down
23 changes: 3 additions & 20 deletions packages/bitbadgesjs-sdk/src/cli/commands/prediction-markets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
import { requireBb1AddressStrict } from '../utils/address.js';
import { bbError, BBErrorCode } from '../utils/envelope.js';
import { addDeployOptions, runEmitOrDeploy } from '../utils/deploy-options.js';
import { normalizeCollection, validateCollectionOrExit } from '../utils/collection-options.js';
import { resolveAmount } from '../utils/amount.js';
import { addExpiryOption, resolveExpiry } from '../utils/expiry-options.js';
import {
Expand All @@ -40,28 +41,10 @@ import {
} from '../../core/prediction-markets.js';
import { UintRangeArray } from '../../core/uintRanges.js';
async function fetchCollection(collectionId: string, opts: NetworkFlags): Promise<any> {
const res = await callApi('GET', `/collection/${encodeURIComponent(collectionId)}`, opts);
return res?.collection ?? res;
return normalizeCollection(await callApi('GET', `/collection/${encodeURIComponent(collectionId)}`, opts));
}
function validateOrExit(collection: any, ctx: string): void {
if (!collection) {
process.stderr.write(`Error: collection not found while running ${ctx}.\n`);
process.exit(2);
}
const result = validatePredictionMarketCollection(collection);
if (!result.valid) {
process.stderr.write(`Error: collection is not a valid Prediction Market (failed in ${ctx}):\n`);
for (const e of result.errors) process.stderr.write(` - ${e}\n`);
if (result.warnings.length > 0) {
process.stderr.write('Warnings:\n');
for (const w of result.warnings) process.stderr.write(` - ${w}\n`);
}
process.exit(2);
}
if (result.warnings.length > 0 && process.env.BB_QUIET !== '1') {
process.stderr.write(`Warnings for ${ctx}:\n`);
for (const w of result.warnings) process.stderr.write(` - ${w}\n`);
}
validateCollectionOrExit(collection, ctx, validatePredictionMarketCollection, 'Prediction Market');
}

/** Resolve settlement approval ids by inspecting collection approvals. */
Expand Down
36 changes: 36 additions & 0 deletions packages/bitbadgesjs-sdk/src/cli/commands/preview.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* preview.ts coverage (ticket 0430) — previously zero specs anywhere.
* ensureTxWrapper normalization + command surface (flag/arg drift).
* The indexer upload + URL assembly are network paths (integration
* territory; no runCli in unit specs, per project convention).
*/

import { previewCommand, ensureTxWrapper } from './preview.js';

describe('preview ensureTxWrapper', () => {
it('returns a {messages:[...]} body unchanged', () => {
const tx = { messages: [{ typeUrl: '/x.Msg', value: { a: 1 } }] };
expect(ensureTxWrapper(tx)).toBe(tx);
});
it('wraps a bare {typeUrl,value} Msg into a single-message body', () => {
const msg = { typeUrl: '/x.Msg', value: { a: 1 } };
expect(ensureTxWrapper(msg)).toEqual({ messages: [msg] });
});
it('passes non-object / non-Msg input through (→ invalid_shape downstream)', () => {
expect(ensureTxWrapper(undefined)).toBeUndefined();
const weird = { not: 'a tx' };
expect(ensureTxWrapper(weird)).toBe(weird);
});
});

describe('previewCommand shape', () => {
it('takes a single <input> argument', () => {
expect(previewCommand.name()).toBe('preview');
expect((previewCommand as any)._args.map((a: any) => a.name())).toEqual(['input']);
});
it('exposes --frontend-url with the bitbadges.io default', () => {
const opt = (previewCommand as any).options.find((o: any) => o.long === '--frontend-url');
expect(opt).toBeDefined();
expect(opt.defaultValue).toBe('https://bitbadges.io');
});
});
2 changes: 1 addition & 1 deletion packages/bitbadgesjs-sdk/src/cli/commands/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Command } from 'commander';
import { addNetworkOptions } from '../utils/io.js';
import { addOutputOptions, emit, emitError, commentary } from '../utils/envelope.js';

function ensureTxWrapper(input: any): any {
export function ensureTxWrapper(input: any): any {
if (!input || typeof input !== 'object') return input;
if (Array.isArray(input.messages)) return input;
if (typeof input.typeUrl === 'string' && input.value) return { messages: [input] };
Expand Down
27 changes: 3 additions & 24 deletions packages/bitbadgesjs-sdk/src/cli/commands/products.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,18 @@ import {
} from '../utils/indexer-options.js';
import { requireBb1AddressStrict } from '../utils/address.js';
import { addDeployOptions, runEmitOrDeploy } from '../utils/deploy-options.js';
import { normalizeCollection, validateCollectionOrExit } from '../utils/collection-options.js';
import {
doesCollectionFollowProductCatalogProtocol,
validateProductCatalogCollection,
extractAllProducts,
buildPurchaseProductMsg
} from '../../core/products.js';
import { BitBadgesCollection } from '../../api-indexer/BitBadgesCollection.js';
import { BigIntify } from '../../common/string-numbers.js';
async function fetchCollection(collectionId: string, opts: NetworkFlags): Promise<any> {
const res = await callApi('GET', `/collection/${encodeURIComponent(collectionId)}`, opts);
const raw = res?.collection ?? res;
if (!raw) return raw;
try { return new BitBadgesCollection(raw).convert(BigIntify); } catch { return raw; }
return normalizeCollection(await callApi('GET', `/collection/${encodeURIComponent(collectionId)}`, opts));
}
function validateOrExit(collection: any, ctx: string): void {
if (!collection) {
process.stderr.write(`Error: collection not found while running ${ctx}.\n`);
process.exit(2);
}
const result = validateProductCatalogCollection(collection);
if (!result.valid) {
process.stderr.write(`Error: collection is not a valid Product catalog (failed in ${ctx}):\n`);
for (const e of result.errors) process.stderr.write(` - ${e}\n`);
if (result.warnings.length > 0) {
process.stderr.write('Warnings:\n');
for (const w of result.warnings) process.stderr.write(` - ${w}\n`);
}
process.exit(2);
}
if (result.warnings.length > 0 && process.env.BB_QUIET !== '1') {
process.stderr.write(`Warnings for ${ctx}:\n`);
for (const w of result.warnings) process.stderr.write(` - ${w}\n`);
}
validateCollectionOrExit(collection, ctx, validateProductCatalogCollection, 'Product catalog');
}

export const productsCommand = new Command('products').description(
Expand Down
44 changes: 44 additions & 0 deletions packages/bitbadgesjs-sdk/src/cli/commands/simulate.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* simulate.ts coverage (ticket 0430) — previously zero specs anywhere.
* ensureTxWrapper is the load-bearing input-normalization helper; the
* command surface guards flag drift. Network paths are integration
* territory (no runCli in unit specs, per project convention).
*/

import { simulateCommand, ensureTxWrapper } from './simulate.js';

describe('simulate ensureTxWrapper', () => {
it('returns a {messages:[...]} body unchanged', () => {
const tx = { messages: [{ typeUrl: '/x.Msg', value: { a: 1 } }], memo: 'm' };
expect(ensureTxWrapper(tx)).toBe(tx);
});
it('wraps a bare {typeUrl,value} Msg into a single-message body', () => {
const msg = { typeUrl: '/x.Msg', value: { a: 1 } };
expect(ensureTxWrapper(msg)).toEqual({ messages: [msg] });
});
it('passes a non-object through untouched', () => {
expect(ensureTxWrapper(null)).toBeNull();
expect(ensureTxWrapper('-')).toBe('-');
expect(ensureTxWrapper(42 as any)).toBe(42);
});
it('passes an object that is neither messages-shaped nor a Msg through untouched (→ shape error downstream)', () => {
const weird = { foo: 'bar' };
expect(ensureTxWrapper(weird)).toBe(weird);
// typeUrl present but no value → NOT a valid Msg, passthrough
const noValue = { typeUrl: '/x.Msg' };
expect(ensureTxWrapper(noValue)).toBe(noValue);
});
});

describe('simulateCommand shape', () => {
it('takes a single <input> argument', () => {
expect(simulateCommand.name()).toBe('simulate');
expect((simulateCommand as any)._args.map((a: any) => a.name())).toEqual(['input']);
});
it('exposes the documented flags', () => {
const flags = (simulateCommand as any).options.map((o: any) => o.long);
for (const f of ['--creator', '--events', '--condensed', '--output-file']) {
expect(flags).toContain(f);
}
});
});
Loading