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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@misofm/api-client",
"version": "0.6.0",
"version": "0.7.0",
"description": "Typed client and response contract for the Miso API read layer. The one definition of what a Miso read returns.",
"license": "Apache-2.0",
"repository": {
Expand Down
43 changes: 43 additions & 0 deletions src/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,3 +535,46 @@ describe("cache buster", () => {
.toThrow(RangeError);
});
});

describe("royalty claims", () => {
const page = {
claims: [
{
txDigest: "FADgaLwmuoyiGgcFvqGyuXd5emk1Zq46cNH1p44Ca73U",
timestampMs: 1788953314915,
entries: [
{
poolId: "0x8619266a87ff5615803f3d4256ec90906f7b74828cc7f7f891941ff8d2a75e28",
stakeId: "0x77bd5f2852455cb897c7c210c943ac7a959817891dcc20b3839914a4079a857b",
shareType: "0x7805::share::Share",
currency: "0x7777::fakeusd::FakeUsd",
amount: "23800000",
},
],
},
],
nextCursor: "KAFCCggAEMSQr+oOGAU=",
availableFromMs: 1786601343736,
};

test("reads the newest page without a cursor and the next one with it", async () => {
const { fetch, calls } = stubFetch({ body: page });
const api = createMisoApiClient({ baseUrl: BASE, fetch });
const first = await api.listWalletRoyaltyClaims("0xabc");
expect(calls[0]).toBe(`${BASE}/read/v1/wallets/0xabc/royalty-claims`);
expect(first.claims[0]?.entries[0]?.amount).toBe("23800000");

await api.listWalletRoyaltyClaims("0xabc", { before: first.nextCursor, limit: 20 });
expect(calls[1]).toBe(
`${BASE}/read/v1/wallets/0xabc/royalty-claims?before=KAFCCggAEMSQr%2BoOGAU%3D&limit=20`,
);
});

test("rejects a page whose amounts are not u64 strings", async () => {
const broken = { ...page, claims: [{ ...page.claims[0], entries: [{ ...page.claims[0]!.entries[0], amount: "12.5" }] }] };
const { fetch } = stubFetch({ body: broken });
await expect(createMisoApiClient({ baseUrl: BASE, fetch }).listWalletRoyaltyClaims("0xabc")).rejects.toBeInstanceOf(
MisoApiContractError,
);
});
});
23 changes: 22 additions & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ import type {
Balance,
ListingView,
OwnedParty,
PendingMembership,
OwnedRecord,
OwnedWork,
Ownership,
PartySummary,
PendingMembership,
PressingView,
PurchaseReceipt,
RecordAlbum,
ReleaseDetail,
RoyaltyClaimsPage,
WorkDetail,
} from "./types.js";

Expand Down Expand Up @@ -342,6 +343,24 @@ export function createMisoApiClient(options: MisoApiClientOptions) {
): Promise<OwnedParty[]> =>
required(s.ownedPartiesSchema, `/wallets/${segment(address)}/parties`, {}, opts);

/**
* The wallet's royalty claim transactions, newest first, one page at a time.
* Pass a page's `nextCursor` as `before` for the next (older) page. The event
* index behind this keeps a bounded window; `availableFromMs` says how far
* back it reaches.
*/
const listWalletRoyaltyClaims = (
address: string,
page: { before?: string | null; limit?: number } = {},
opts: MisoRequestOptions = {},
): Promise<RoyaltyClaimsPage> =>
required(
s.royaltyClaimsPageSchema,
`/wallets/${segment(address)}/royalty-claims`,
{ before: page.before ?? undefined, limit: page.limit },
opts,
);

const listWalletPendingMemberships = (
address: string,
opts: MisoRequestOptions = {},
Expand Down Expand Up @@ -451,6 +470,7 @@ export function createMisoApiClient(options: MisoApiClientOptions) {
getArtist,
listArtists,
listWalletRecords,
listWalletRoyaltyClaims,
listWalletParties,
listWalletPendingMemberships,
listWalletWorks,
Expand Down Expand Up @@ -529,6 +549,7 @@ export const READ_CACHE_CLASS = {
listArtists: "artist",
getArtists: "artist",
listWalletRecords: "private",
listWalletRoyaltyClaims: "private",
getWalletRecords: "private",
listWalletParties: "private",
getWalletParties: "private",
Expand Down
32 changes: 32 additions & 0 deletions src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,3 +457,35 @@ export const apiErrorSchema = z.object({
message: z.string(),
}),
});

// ── Royalty claims ───────────────────────────────────────────────────────────

/** One pool swept by a royalty claim transaction. */
export const royaltyClaimEntrySchema = z.object({
poolId: suiIdSchema,
stakeId: suiIdSchema,
/** The pool's share type, e.g. `0x…::share::Share`. */
shareType: z.string(),
/** The currency paid out, e.g. the network's stable coin type. */
currency: z.string(),
/** Base units of `currency`, a u64 as a decimal string. */
amount: z.string().regex(/^\d+$/),
});

/** One royalty claim transaction sent by the wallet. */
export const royaltyClaimSchema = z.object({
txDigest: z.string().min(1),
/** Checkpoint timestamp, milliseconds since the epoch; 0 when the index had none. */
timestampMs: z.number().int().nonnegative(),
/** The pools swept, in event order. */
entries: z.array(royaltyClaimEntrySchema),
});

/** A page of a wallet's royalty claims, newest first. */
export const royaltyClaimsPageSchema = z.object({
claims: z.array(royaltyClaimSchema),
/** Pass as `before` for the next (older) page; null when none remain. */
nextCursor: z.string().nullable(),
/** Earliest timestamp the event index still covers, or null if unknown. */
availableFromMs: z.number().int().nullable(),
});
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ export type TrackRoyalty = z.infer<typeof s.trackRoyaltySchema>;
export type PurchaseReceipt = z.infer<typeof s.purchaseReceiptSchema>;

export type ApiErrorBody = z.infer<typeof s.apiErrorSchema>;
export type RoyaltyClaimEntry = z.infer<typeof s.royaltyClaimEntrySchema>;
export type RoyaltyClaim = z.infer<typeof s.royaltyClaimSchema>;
export type RoyaltyClaimsPage = z.infer<typeof s.royaltyClaimsPageSchema>;