diff --git a/package.json b/package.json index dabc39e..d6abf46 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/client.test.ts b/src/client.test.ts index 166d6c2..7ab062c 100644 --- a/src/client.test.ts +++ b/src/client.test.ts @@ -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, + ); + }); +}); diff --git a/src/client.ts b/src/client.ts index 3d1ed08..4f999c4 100644 --- a/src/client.ts +++ b/src/client.ts @@ -12,15 +12,16 @@ import type { Balance, ListingView, OwnedParty, - PendingMembership, OwnedRecord, OwnedWork, Ownership, PartySummary, + PendingMembership, PressingView, PurchaseReceipt, RecordAlbum, ReleaseDetail, + RoyaltyClaimsPage, WorkDetail, } from "./types.js"; @@ -342,6 +343,24 @@ export function createMisoApiClient(options: MisoApiClientOptions) { ): Promise => 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 => + required( + s.royaltyClaimsPageSchema, + `/wallets/${segment(address)}/royalty-claims`, + { before: page.before ?? undefined, limit: page.limit }, + opts, + ); + const listWalletPendingMemberships = ( address: string, opts: MisoRequestOptions = {}, @@ -451,6 +470,7 @@ export function createMisoApiClient(options: MisoApiClientOptions) { getArtist, listArtists, listWalletRecords, + listWalletRoyaltyClaims, listWalletParties, listWalletPendingMemberships, listWalletWorks, @@ -529,6 +549,7 @@ export const READ_CACHE_CLASS = { listArtists: "artist", getArtists: "artist", listWalletRecords: "private", + listWalletRoyaltyClaims: "private", getWalletRecords: "private", listWalletParties: "private", getWalletParties: "private", diff --git a/src/schemas.ts b/src/schemas.ts index 8619dec..297512e 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -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(), +}); diff --git a/src/types.ts b/src/types.ts index 26f80ff..77d1379 100644 --- a/src/types.ts +++ b/src/types.ts @@ -43,3 +43,6 @@ export type TrackRoyalty = z.infer; export type PurchaseReceipt = z.infer; export type ApiErrorBody = z.infer; +export type RoyaltyClaimEntry = z.infer; +export type RoyaltyClaim = z.infer; +export type RoyaltyClaimsPage = z.infer;