From ca875a3383c5806e828c74261e433e24efe1305f Mon Sep 17 00:00:00 2001 From: aaroncox Date: Wed, 23 Sep 2026 23:56:56 -0700 Subject: [PATCH 01/18] Parse legacy public keys shortened by leading zero bytes --- packages/antelope/src/chain/public-key.ts | 7 ++++--- packages/antelope/test/chain.ts | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/antelope/src/chain/public-key.ts b/packages/antelope/src/chain/public-key.ts index 222d7b23..0eb94ef8 100644 --- a/packages/antelope/src/chain/public-key.ts +++ b/packages/antelope/src/chain/public-key.ts @@ -37,9 +37,10 @@ export class PublicKey implements ABISerializableObject { const size = type === KeyType.K1 || type === KeyType.R1 ? 33 : undefined const data = Base58.decodeRipemd160Check(parts[2], size, type) return new PublicKey(type, data) - } else if (value.length >= 50) { - // Legacy EOS key - const data = Base58.decodeRipemd160Check(value.slice(-50)) + } else if (value.startsWith('EOS') || value.length >= 50) { + // Legacy EOS key; leading zero bytes shorten the encoding, e.g. the null key + const encoded = value.startsWith('EOS') ? value.slice(3) : value.slice(-50) + const data = Base58.decodeRipemd160Check(encoded, 33) return new PublicKey(KeyType.K1, data) } else { throw new Error('Invalid public key string') diff --git a/packages/antelope/test/chain.ts b/packages/antelope/test/chain.ts index de18ad22..4a239e4a 100644 --- a/packages/antelope/test/chain.ts +++ b/packages/antelope/test/chain.ts @@ -540,6 +540,11 @@ suite('chain', function () { true ) + const nullKey = PublicKey.from('EOS1111111111111111111111111111111114T1Anm') + assert.equal(nullKey.data.length, 33) + assert.equal(nullKey.toLegacyString(), 'EOS1111111111111111111111111111111114T1Anm') + assert.equal(nullKey.toString(), 'PUB_K1_11111111111111111111111111111111149Mr2R') + const sig = Signature.from( 'SIG_K1_JyMXe1HU42qN2aM7GPUf5XrAcAjWPbRoojzfsKq9Rgto3dGsRcCZ4UaPsAcFPS2faGQMpRoSTRX8WQQUDEA5TfWHj8sr6q' ) From 7ea737db1211b1e9a685ce14343a116b2e7663f0 Mon Sep 17 00:00:00 2001 From: aaroncox Date: Thu, 24 Sep 2026 00:13:42 -0700 Subject: [PATCH 02/18] Remove the svelte-components AGENTS.md --- packages/svelte-components/.gitignore | 3 -- packages/svelte-components/AGENTS.md | 76 --------------------------- 2 files changed, 79 deletions(-) delete mode 100644 packages/svelte-components/AGENTS.md diff --git a/packages/svelte-components/.gitignore b/packages/svelte-components/.gitignore index 5280cd12..294b3857 100644 --- a/packages/svelte-components/.gitignore +++ b/packages/svelte-components/.gitignore @@ -22,6 +22,3 @@ Thumbs.db # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* - -# tooling -.opencode diff --git a/packages/svelte-components/AGENTS.md b/packages/svelte-components/AGENTS.md deleted file mode 100644 index 40f6f2f0..00000000 --- a/packages/svelte-components/AGENTS.md +++ /dev/null @@ -1,76 +0,0 @@ -## Agentic Coding Agents Guide - -This document provides guidelines for AI agents to effectively contribute to this repository. - -### Build and Test Commands - -- **Build**: `npm run build` -- **Lint**: `npm run lint` -- **Type Check**: `npm run check` -- **Format**: `npm run format` - -There is no specific command for running a single test. Use `npm run check` to type-check the entire project. - -### Code Style - -- **Formatting**: Code is formatted with Prettier. Use tabs, single quotes, and a print width of 100. -- **Imports**: Imports are organized by `prettier-plugin-organize-imports`. -- **Types**: All code must be strictly typed, as defined in `tsconfig.json`. -- **Naming Conventions**: Use camelCase for variables and functions, and PascalCase for Svelte components. -- **Error Handling**: Use try/catch blocks for asynchronous operations and throw errors where appropriate. -- **Components**: Create components in their own directories with an `index.ts` file to export them. -- **Dependencies**: Use `pnpm` for package management. - -## Development Workflow: Spec → Code - -THESE INSTRUCTIONS ARE CRITICAL! - -They dramatically improve the quality of the work you create. - -### Phase 1: Requirements First - -When asked to implement any feature or make changes, ALWAYS start by asking: -"Should I create a Spec for this task first?" - -IF user agrees: - -- Create a markdown file in `.opencode/scopes/FeatureName.md` -- Interview the user to clarify: -- Purpose & user problem -- Success criteria -- Scope & constraints -- Technical considerations -- Out of scope items - -### Phase 2: Review & Refine - -After drafting the Spec: - -- Present it to the user -- Ask: "Does this capture your intent? Any changes needed?" -- Iterate until user approves -- End with: "Spec looks good? Type 'GO!' when ready to implement" - -### Phase 3: Implementation - -ONLY after user types "GO!" or explicitly approves: - -- Begin coding based on the Spec -- Reference the Spec for decisions -- Update Spec if scope changes, but ask user first. - -### File Organization - -\`\`\` - -.opencode/ -├── scopes/ -│ ├── FeatureName.md # Shared/committed Specs -│ └── .local/ # Git-ignored experimental Specs -│ └── Experiment.md - -\`\`\` - -**Remember: Think first, ask clarifying questions, _then_ code. The Spec is your north star.** - -(source: https://lukebechtel.com/blog/vibe-speccing) From 9641a2086b82c3accc8e02ea832427d62efb6251 Mon Sep 17 00:00:00 2001 From: aaroncox Date: Thu, 24 Sep 2026 00:13:42 -0700 Subject: [PATCH 03/18] Point UPGRADING.md at the 4.0.0 release notes --- UPGRADING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 9dee4337..4ade1df9 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -2,5 +2,4 @@ Moving a project to `@wharfkit/*` 4.0.0 is covered on the website: -- [Upgrading to WharfKit 4.0.0](https://wharfkit.com/docs/releases/upgrading-to-4-0-0), the procedure, the version each package comes from, and a checklist. -- [Wharf 4.0.0 release notes](https://wharfkit.com/docs/releases/4-0-0), what each package gained. +- [Wharf 4.0.0](https://wharfkit.com/docs/releases/4-0-0#upgrading), the upgrade steps, the changes to check in your code, and what each package gained. From 6ba70cb63632b62f447624603cf7c0f4c3a40b61 Mon Sep 17 00:00:00 2001 From: aaroncox Date: Thu, 24 Sep 2026 14:05:00 -0700 Subject: [PATCH 04/18] Remove the session URL handoff --- packages/session/src/encoded.ts | 36 ------ packages/session/src/index-module.ts | 1 - packages/session/src/kit.ts | 77 +------------ packages/session/src/session.ts | 36 ------ packages/session/test/tests/kit.ts | 148 ------------------------- packages/session/test/tests/session.ts | 75 ------------- 6 files changed, 5 insertions(+), 368 deletions(-) delete mode 100644 packages/session/src/encoded.ts diff --git a/packages/session/src/encoded.ts b/packages/session/src/encoded.ts deleted file mode 100644 index 5fc722a1..00000000 --- a/packages/session/src/encoded.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {Checksum256, Name, Struct} from '@wharfkit/antelope' -import {SerializedSession, Session, SessionType} from './session' - -/** - * A serialized session encoded as an Antelope struct, for transport through a - * URL parameter or a message payload. - */ -@Struct.type('url_encoded_session') -export class URLEncodedSession extends Struct { - @Struct.field(Checksum256) declare chain: Checksum256 - @Struct.field(Name) declare actor: Name - @Struct.field(Name) declare permission: Name - @Struct.field('string') declare walletPlugin: string - @Struct.field('string', {optional: true}) declare data?: string - - static fromSession(data: SessionType): URLEncodedSession { - const session = data instanceof Session ? data.serialize() : data - return new URLEncodedSession({ - chain: session.chain, - actor: session.actor, - permission: session.permission, - walletPlugin: JSON.stringify(session.walletPlugin), - data: JSON.stringify(session.data), - }) - } - - get serialized(): SerializedSession { - return { - chain: this.chain, - actor: this.actor, - permission: this.permission, - walletPlugin: JSON.parse(this.walletPlugin), - data: this.data ? JSON.parse(this.data) : undefined, - } - } -} diff --git a/packages/session/src/index-module.ts b/packages/session/src/index-module.ts index 1767b85a..e256cfb0 100644 --- a/packages/session/src/index-module.ts +++ b/packages/session/src/index-module.ts @@ -1,4 +1,3 @@ -export * from './encoded' export * from './kit' export * from './login' export * from './session' diff --git a/packages/session/src/kit.ts b/packages/session/src/kit.ts index c5f5a133..d89fa6bb 100644 --- a/packages/session/src/kit.ts +++ b/packages/session/src/kit.ts @@ -1,14 +1,12 @@ import {ChainDefinition, type ChainDefinitionType, type Fetch} from '@wharfkit/common' import type {Contract} from '@wharfkit/contract' import { - Bytes, Checksum256, Checksum256Type, Name, NameType, PermissionLevel, PermissionLevelType, - Serializer, } from '@wharfkit/antelope' import { @@ -40,7 +38,6 @@ import { import {SessionKeyManager} from './sessionkey/manager' import {SessionKeyWalletPlugin} from './sessionkey/wallet' import {SessionKeyConfig} from './sessionkey/types' -import {URLEncodedSession} from './encoded' export interface LoginOptions { arbitrary?: Record // Arbitrary data that will be passed via context to wallet plugin @@ -91,8 +88,6 @@ export interface SessionKitArgs { export interface SessionKitOptions { abis?: TransactABIDef[] - acceptUrlSession?: boolean - acceptUrlSessionParam?: string allowModify?: boolean contracts?: Contract[] equalityFn?: SerializedSessionEqualityFn @@ -113,8 +108,6 @@ export interface SessionKitOptions { */ export class SessionKit { readonly abis: TransactABIDef[] = [] - readonly acceptUrlSession: boolean = false - readonly acceptUrlSessionParam: string = 'incomingWharfSession' readonly allowModify: boolean = true readonly appName: string readonly equalityFn: SerializedSessionEqualityFn = serializedSessionEquals @@ -151,12 +144,6 @@ export class SessionKit { if (options.abis) { this.abis = [...options.abis] } - if (options.acceptUrlSession) { - this.acceptUrlSession = options.acceptUrlSession - } - if (options.acceptUrlSessionParam) { - this.acceptUrlSessionParam = options.acceptUrlSessionParam - } if (options.equalityFn) { this.equalityFn = options.equalityFn } @@ -674,68 +661,14 @@ export class SessionKit { } /** - * Read a session handed over through the current URL, if one is present. - * - * Requires `acceptUrlSession` and a browser environment. The parameter is - * stripped from the URL once read, so a reload cannot replay it. - */ - restoreFromURL(): SerializedSession | undefined { - if (typeof window === 'undefined') { - return - } - const url = new URL(window.location.href) - const urlSessionParam = url.searchParams.get(this.acceptUrlSessionParam) - if (urlSessionParam) { - // Remove the session from the URL to prevent reuse, decodable or not - url.searchParams.delete(this.acceptUrlSessionParam) - window.history.replaceState(null, '', url) - try { - const encodedSession = Serializer.decode({ - data: Bytes.from(urlSessionParam, 'hex'), - type: URLEncodedSession, - }) - return encodedSession.serialized - } catch { - // eslint-disable-next-line no-console -- warn the developer since this may be unintentional - console.warn('Failed to decode session from URL: ' + urlSessionParam) - } - } - } - - private canRestore(serializedSession: SerializedSession): boolean { - return ( - !!this.getWalletPlugin(serializedSession.walletPlugin.id) && - this.chains.some((c) => c.id.equals(serializedSession.chain)) - ) - } - - /** - * Find the session to restore when the caller named none: the incoming URL - * session if one is offered, otherwise the default session in storage. + * Find the session to restore when the caller named none: the default + * session in storage. */ private async restoreWithoutArgs(): Promise { - let serializedSession: SerializedSession | undefined - - if (this.acceptUrlSession) { - const fromURL = this.restoreFromURL() - if (fromURL && this.canRestore(fromURL)) { - serializedSession = fromURL - } else if (fromURL) { - // eslint-disable-next-line no-console -- warn the developer since this may be unintentional - console.warn( - `Ignoring session from URL for chain ${fromURL.chain} and wallet plugin '${fromURL.walletPlugin.id}', which this SessionKit does not support.` - ) - } - } - - if (!serializedSession) { - const data = await this.storage.read('session') - if (data) { - serializedSession = JSON.parse(data) - } + const data = await this.storage.read('session') + if (data) { + return JSON.parse(data) } - - return serializedSession } /** diff --git a/packages/session/src/session.ts b/packages/session/src/session.ts index 3e3d4014..4ef82eb4 100644 --- a/packages/session/src/session.ts +++ b/packages/session/src/session.ts @@ -43,7 +43,6 @@ import { TransactRevisions, } from './transact' import {SessionStorage} from './storage' -import {URLEncodedSession} from './encoded' import { actionMatchesPermission, buildSendTransaction2Options, @@ -117,11 +116,6 @@ export interface PartialSerializedSession extends Partial {}, - } - } - - // Mock window.location with a writable href property - if (typeof (globalThis as any).window.location === 'undefined') { - ;(globalThis as any).window.location = {href: ''} - } else { - try { - ;(globalThis as any).window.location.href = - (globalThis as any).window.location.href || '' - } catch { - ;(globalThis as any).window.location = {href: ''} - } - } - - // Ensure no sessions - const sessions = await sessionKit.restoreAll() - assert.lengthOf(sessions, 0) - - // Set the href to include an incomingWharfSession parameter - window.location.href = - 'https://somewhere.com?incomingWharfSession=73e4385a2708e6d7048834fbc1079f2fabb17b3c125b146af438971e90716c4d104208d9c1754de3000000000090b1ca737b226964223a2277616c6c65742d706c7567696e2d707269766174656b6579222c2264617461223a7b22707269766174654b6579223a225056545f4b315f32355850314c7431527438376879796d6f755369654262676e554541657253317951486939777148433255656b326d677a48227d7d010f7b226669656c64223a22666f6f227d' - - // Attempt to restore the session from the URL - const session = await sessionKit.restore() - if (!session) { - throw new Error('Failed to restore session from URL') - } - - // Ensure session is correct - assert.isDefined(session) - assert.isTrue(session.chain.id.equals(mockChainDefinition.id), 'Incorrect chain') - assert.isTrue(session.actor.equals('wharfkit1111'), 'Incorrect actor') - assert.isTrue(session.permission.equals('test'), 'Incorrect permission') - assert.isTrue( - session.walletPlugin instanceof WalletPluginPrivateKey, - 'Incorrect walletPlugin type' - ) - assert.equal(session.data.field, 'foo', 'Incorrect session data') - assert.equal(session.walletPlugin.id, 'wallet-plugin-privatekey') - assert.equal( - session.walletPlugin.data.privateKey, - 'PVT_K1_25XP1Lt1Rt87hyymouSieBbgnUEAerS1yQHi9wqHC2Uek2mgzH' - ) - - // Ensure session was persisted to storage - const sessionsAfter = await sessionKit.restoreAll() - assert.lengthOf(sessionsAfter, 1) - }) - test('session from URL this kit cannot restore', async function () { - const sessionKit = new SessionKit(mockSessionKitArgs, { - ...mockSessionKitOptions, - acceptUrlSession: true, - storage: new MockStorage(), - }) - const stored = makeSession('session1') - await sessionKit.persistSession(stored) - - const existingWindow = (globalThis as any).window - ;(globalThis as any).window = { - history: {replaceState: () => undefined}, - location: {href: ''}, - } - const craft = (walletPluginId: string, chain: Checksum256Type) => - Serializer.encode({ - object: URLEncodedSession.from({ - chain, - actor: 'incoming1111', - permission: 'active', - walletPlugin: JSON.stringify({id: walletPluginId, data: {}}), - }), - }).toString('hex') - const unsupported = [ - craft('wallet-plugin-unregistered', mockChainDefinition.id), - craft( - 'wallet-plugin-privatekey', - '00000000000000000000000000000000000000000000000000000000deadbeef' - ), - ] - try { - for (const hex of unsupported) { - ;(globalThis as any).window.location.href = - `https://app.test?incomingWharfSession=${hex}` - // Falls back to the stored session rather than throwing - const restored = await sessionKit.restore() - assert.isTrue(restored?.actor.equals('session1')) - } - } finally { - ;(globalThis as any).window = existingWindow - } - }) - test('session from URL that fails to decode', async function () { - const sessionKit = new SessionKit(mockSessionKitArgs, { - ...mockSessionKitOptions, - acceptUrlSession: true, - storage: new MockStorage(), - }) - const existingWindow = (globalThis as any).window - let replaced: string | undefined - ;(globalThis as any).window = { - history: { - replaceState: (_s: unknown, _t: unknown, url: URL) => { - replaced = String(url) - }, - }, - location: {href: 'https://app.test?incomingWharfSession=not-hex-at-all'}, - } - try { - assert.isUndefined(await sessionKit.restore()) - assert.equal(replaced, 'https://app.test/') - } finally { - ;(globalThis as any).window = existingWindow - } - }) - test('session from URL outside a browser', function () { - const sessionKit = new SessionKit(mockSessionKitArgs, { - ...mockSessionKitOptions, - acceptUrlSession: true, - storage: new MockStorage(), - }) - const existing = (globalThis as any).window - delete (globalThis as any).window - try { - assert.isUndefined(sessionKit.restoreFromURL()) - } finally { - ;(globalThis as any).window = existing - } - }) test('no session returns undefined', async function () { const sessionKit = new SessionKit(mockSessionKitArgs, { ...mockSessionKitOptions, diff --git a/packages/session/test/tests/session.ts b/packages/session/test/tests/session.ts index 8c304d45..41d3ca11 100644 --- a/packages/session/test/tests/session.ts +++ b/packages/session/test/tests/session.ts @@ -5,16 +5,13 @@ import SessionKit, { ChainDefinition, Session, SessionOptions, - URLEncodedSession, } from '$lib' import { ABI, ABIDef, - Bytes, FetchProvider, Name, PermissionLevel, - Serializer, Signature, TimePointSec, } from '@wharfkit/antelope' @@ -45,7 +42,6 @@ const mockTransactOptions = { suite('session', function () { let session: Session - let kit: SessionKit setup(function () { // Establish new session before each test session = new Session( @@ -553,75 +549,4 @@ suite('session', function () { assert.equal(provider2.url, 'https://wax.greymass.com') }) }) - suite('encoded', function () { - setup(function () { - // Establish new session kit before each test - kit = new SessionKit( - { - appName: 'demo.app', - chains: [ - { - id: '73e4385a2708e6d7048834fbc1079f2fabb17b3c125b146af438971e90716c4d', - url: 'https://jungle4.greymass.com', - }, - ], - ui: new MockUserInterface(), - walletPlugins: [makeWallet()], - }, - { - fetch: mockFetch, // Required for unit tests - storage: new MockStorage(), - } - ) - }) - test('serialized', async function () { - const serialized = session.encode('serialized') - const fromSerialized = await kit.restore(serialized) - if (!fromSerialized) { - throw new Error('Failed to restore session from serialized') - } - assert.equal( - JSON.stringify(serialized), - JSON.stringify(fromSerialized.encode('serialized')) - ) - assert.deepEqual(serialized, fromSerialized.encode('serialized')) - }) - test('json', async function () { - const serialized = session.encode('serialized') - const json = session.encode('json') - const fromJson = await kit.restore(JSON.parse(json)) - if (!fromJson) { - throw new Error('Failed to restore session from json') - } - assert.equal(JSON.stringify(serialized), JSON.stringify(fromJson.encode('serialized'))) - assert.deepEqual(serialized, fromJson.encode('serialized')) - }) - test('encoded', async function () { - const serialized = session.encode('serialized') - const encoded = session.encode('encoded') - const fromEncoded = await kit.restore(encoded.serialized) - if (!fromEncoded) { - throw new Error('Failed to restore session from encoded') - } - assert.equal( - JSON.stringify(serialized), - JSON.stringify(fromEncoded.encode('serialized')) - ) - assert.deepEqual(serialized, fromEncoded.encode('serialized')) - }) - test('url', async function () { - const serialized = session.encode('serialized') - const url = session.encode('url') - const reconstructed = Serializer.decode({ - data: Bytes.from(url, 'hex'), - type: URLEncodedSession, - }) - const fromUrl = await kit.restore(reconstructed.serialized) - if (!fromUrl) { - throw new Error('Failed to restore session from url') - } - assert.equal(JSON.stringify(serialized), JSON.stringify(fromUrl.encode('serialized'))) - assert.deepEqual(serialized, fromUrl.encode('serialized')) - }) - }) }) From 0e84a0cbfce25a40cd5ab77b8d7e18da24a0478f Mon Sep 17 00:00:00 2001 From: Rob Konsdorf Date: Mon, 17 Aug 2026 17:58:01 -0400 Subject: [PATCH 05/18] Report the live collection fee on auctions, buyoffers, and template buyoffers --- packages/atomicassets/src/endpoints/types.ts | 21 +++- packages/atomicassets/test/v2-api.ts | 122 +++++++++++++++++++ 2 files changed, 142 insertions(+), 1 deletion(-) diff --git a/packages/atomicassets/src/endpoints/types.ts b/packages/atomicassets/src/endpoints/types.ts index 660692e6..b327ccfd 100644 --- a/packages/atomicassets/src/endpoints/types.ts +++ b/packages/atomicassets/src/endpoints/types.ts @@ -339,7 +339,11 @@ export class SaleObject extends Struct { @Struct.field('string') declare updated_at_time: string @Struct.field(UInt64) declare created_at_block: UInt64 @Struct.field('string') declare created_at_time: string - /** Collection fee resolved at listing time. AtomicMarket v2 only. */ + /** + * The collection's `market_fee` as of the last indexed block. The nested + * `collection.market_fee` is the listing-time snapshot, and the two differ + * while the listing is open. AtomicMarket v2 only, undefined on a v1 indexer. + */ @Struct.field(Float64, {optional: true}) declare current_collection_fee: Float64 } @@ -376,6 +380,11 @@ export class AuctionObject extends Struct { @Struct.field('string') declare created_at_time: string @Struct.field(UInt64) declare updated_at_block: UInt64 @Struct.field('string') declare updated_at_time: string + /** + * The collection's live market_fee; see SaleObject.current_collection_fee. + * AtomicMarket v2 only. + */ + @Struct.field(Float64, {optional: true}) declare current_collection_fee: Float64 } @Struct.type('buyoffer_object') @@ -398,6 +407,11 @@ export class BuyofferObject extends Struct { @Struct.field(UInt64) declare updated_at_block: UInt64 @Struct.field('string') declare updated_at_time: string @Struct.field(UInt8) declare state: UInt8 + /** + * The collection's live market_fee; see SaleObject.current_collection_fee. + * AtomicMarket v2 only. + */ + @Struct.field(Float64, {optional: true}) declare current_collection_fee: Float64 } @Struct.type('template_buyoffer_object') @@ -419,6 +433,11 @@ export class TemplateBuyofferObject extends Struct { @Struct.field(UInt64) declare updated_at_block: UInt64 @Struct.field('string') declare updated_at_time: string @Struct.field(UInt8) declare state: UInt8 + /** + * The collection's live market_fee; see SaleObject.current_collection_fee. + * AtomicMarket v2 only. + */ + @Struct.field(Float64, {optional: true}) declare current_collection_fee: Float64 } @Struct.type('marketplace') diff --git a/packages/atomicassets/test/v2-api.ts b/packages/atomicassets/test/v2-api.ts index a85c7740..caa6cc17 100644 --- a/packages/atomicassets/test/v2-api.ts +++ b/packages/atomicassets/test/v2-api.ts @@ -66,4 +66,126 @@ suite('v2 API response fields', function () { assert.isTrue(collection.new_author_name.equals('bob')) assert.equal(collection.new_author_date, '1785263212000') }) + + // The nested collection carries the fee the listing was created with, and + // current_collection_fee carries the fee as of the last indexed block. + const listingCollection = { + collection_name: 'royaltycol11', + author: 'alice', + allow_notify: true, + authorized_accounts: [], + notify_accounts: [], + market_fee: 0.05, + created_at_block: 1, + created_at_time: '1', + } + + const listingPrice = { + token_contract: 'eosio.token', + token_symbol: 'WAX', + token_precision: 8, + amount: '1250000', + } + + const listingTemplate = { + template_id: 662912, + is_transferable: true, + is_burnable: true, + issued_supply: 1, + max_supply: 10, + immutable_data: {name: 'Test'}, + created_at_block: 1, + created_at_time: '1', + } + + test('auctions, buyoffers, and template buyoffers report the live collection fee', function () { + const auction = Types.AuctionObject.from({ + market_contract: 'atomicmarket', + assets_contract: 'atomicassets', + auction_id: 1, + seller: 'alice', + assets: [], + end_time: '1783387748000', + price: listingPrice, + bids: [], + state: 1, + claimed_by_seller: false, + claimed_by_buyer: false, + collection: listingCollection, + is_seller_contract: false, + created_at_block: 1, + created_at_time: '1', + updated_at_block: 2, + updated_at_time: '2', + current_collection_fee: 0.07, + }) + + const buyoffer = Types.BuyofferObject.from({ + market_contract: 'atomicmarket', + assets_contract: 'atomicassets', + buyoffer_id: 2, + seller: 'alice', + buyer: 'bob', + price: listingPrice, + assets: [], + collection: listingCollection, + memo: '', + created_at_block: 1, + created_at_time: '1', + updated_at_block: 2, + updated_at_time: '2', + state: 0, + current_collection_fee: 0.07, + }) + + const templateBuyoffer = Types.TemplateBuyofferObject.from({ + market_contract: 'atomicmarket', + assets_contract: 'atomicassets', + buyoffer_id: 3, + buyer: 'bob', + price: listingPrice, + assets: [], + collection: listingCollection, + template: listingTemplate, + created_at_block: 1, + created_at_time: '1', + updated_at_block: 2, + updated_at_time: '2', + state: 0, + current_collection_fee: 0.07, + }) + + assert.equal(auction.current_collection_fee.value, 0.07) + assert.equal(buyoffer.current_collection_fee.value, 0.07) + assert.equal(templateBuyoffer.current_collection_fee.value, 0.07) + + assert.equal(auction.collection.market_fee.value, 0.05) + assert.equal(buyoffer.collection.market_fee.value, 0.05) + assert.equal(templateBuyoffer.collection.market_fee.value, 0.05) + }) + + test('an auction from a v1 indexer decodes without the collection fee', function () { + const auction = Types.AuctionObject.from({ + market_contract: 'atomicmarket', + assets_contract: 'atomicassets', + auction_id: 1, + seller: 'alice', + assets: [], + end_time: '1783387748000', + price: listingPrice, + bids: [], + state: 1, + claimed_by_seller: false, + claimed_by_buyer: false, + collection: listingCollection, + is_seller_contract: false, + created_at_block: 1, + created_at_time: '1', + updated_at_block: 2, + updated_at_time: '2', + }) + + assert.isUndefined(auction.current_collection_fee) + assert.equal(auction.collection.market_fee.value, 0.05) + }) }) From c2bbb7aa3dc3d714fcc107515a9c64bf0990f069 Mon Sep 17 00:00:00 2001 From: Rob Konsdorf Date: Mon, 17 Aug 2026 17:58:01 -0400 Subject: [PATCH 06/18] Report the media-type descriptors the schema endpoints return --- packages/atomicassets/src/endpoints/types.ts | 40 +++++++++++++++++--- packages/atomicassets/src/objects/schema.ts | 9 +++++ packages/atomicassets/test/schema.ts | 28 +++++++++++++- packages/atomicassets/test/v2-api.ts | 30 +++++++++++++++ 4 files changed, 100 insertions(+), 7 deletions(-) diff --git a/packages/atomicassets/src/endpoints/types.ts b/packages/atomicassets/src/endpoints/types.ts index b327ccfd..ab198c92 100644 --- a/packages/atomicassets/src/endpoints/types.ts +++ b/packages/atomicassets/src/endpoints/types.ts @@ -136,17 +136,38 @@ export class CollectionObject extends Struct { * A schema format field as returned by the API. * * This is intentionally not the contract's `FORMAT` struct: `createschema` takes - * only `{name, type}`, while the API additionally reports the `mediatype` and - * `info` recorded by the AtomicAssets v2 `setschematyp` action. Widening the - * contract struct would let those fields leak into serialized action data. + * only `{name, type}`, while the API additionally reports a `mediatype` and an + * `info` descriptor per field. Widening the contract struct would let those + * fields leak into serialized action data. */ @Struct.type('schema_format_field') export class SchemaFormatField extends Struct { @Struct.field('string') declare name: string @Struct.field('string') declare type: string - /** Media type recorded by `setschematyp`. AtomicAssets v2 only. */ + /** + * Media type for the field. The API merges the descriptors authored through + * `setschematyp` with a name and type heuristic, so a value here can be + * derived rather than stored. `SchemaObject.types` reports the authored + * ones. AtomicAssets v2 only. + */ + @Struct.field('string', {optional: true}) declare mediatype: string + /** + * Free-form descriptor for the field, merged with the same heuristic as + * `mediatype`. `SchemaObject.types` reports the authored ones. + * AtomicAssets v2 only. + */ + @Struct.field('string', {optional: true}) declare info: string +} + +/** + * A media-type descriptor authored through `setschematyp`, exactly as stored on + * chain. It carries no serialization `type`, which is what separates it from + * `SchemaFormatField`. + */ +@Struct.type('schema_format_type') +export class SchemaFormatType extends Struct { + @Struct.field('string') declare name: string @Struct.field('string', {optional: true}) declare mediatype: string - /** Free-form descriptor recorded by `setschematyp`. AtomicAssets v2 only. */ @Struct.field('string', {optional: true}) declare info: string } @@ -156,6 +177,15 @@ export class SchemaObject extends Struct { @Struct.field(UInt64, {optional: true}) declare assets: UInt64 @Struct.field(SchemaFormatField, {array: true}) declare format: SchemaFormatField[] + /** + * The descriptors authored through `setschematyp`, unmerged. Returned by + * `/schemas` and `/schemas/{collection_name}/{schema_name}` alone, so a + * schema nested in an asset, template, or account response omits it. An + * empty array means the schema has none, and an absent one means the + * response does not report them. AtomicAssets v2 only. + */ + @Struct.field(SchemaFormatType, {array: true, optional: true}) + declare types: SchemaFormatType[] @Struct.field(Name, {optional: true}) declare contract: Name @Struct.field(Name, {optional: true}) declare collection_name: Name @Struct.field(CollectionObject, {optional: true}) declare collection: CollectionObject diff --git a/packages/atomicassets/src/objects/schema.ts b/packages/atomicassets/src/objects/schema.ts index 67fa38bc..f55a7193 100644 --- a/packages/atomicassets/src/objects/schema.ts +++ b/packages/atomicassets/src/objects/schema.ts @@ -33,6 +33,15 @@ export class Schema { return this.data.format } + /** + * The descriptors authored through `setschematyp`. Undefined when the + * schema came from a response that does not report them, such as the schema + * nested in an asset or template. + */ + get types() { + return this.data.types + } + extendSchema( authorizedEditor: NameType, schemaFormat: AtomicAssetsContract.ActionParams.Type.FORMAT[] diff --git a/packages/atomicassets/test/schema.ts b/packages/atomicassets/test/schema.ts index b40eab25..346ac50f 100644 --- a/packages/atomicassets/test/schema.ts +++ b/packages/atomicassets/test/schema.ts @@ -5,8 +5,15 @@ import {mockFetch} from '@wharfkit/mock-data' import {PlaceholderAuth} from '@wharfkit/signing-request' import {BASE_URL, TIMEOUT, SLOW_THRESHOLD} from './config' -import type {Schema} from '$lib' -import {AtomicAssetsAPIClient, AtomicAssetsContract, AtomicAssetsKit, KitUtility, Types} from '$lib' +import { + AtomicAssetsAPIClient, + AtomicAssetsContract, + AtomicAssetsKit, + AtomicMarketContract, + KitUtility, + Schema, + Types, +} from '$lib' const client = new APIClient({ provider: new FetchProvider(Chains.WAX.url, {fetch: mockFetch}), @@ -78,6 +85,23 @@ suite('Schema', function () { assert.isNull(field.info) }) + test('types returns the authored media type descriptors', function () { + // The shared fixture comes from a v1 chain, whose schema endpoints + // report no types at all, so the descriptors are built here instead. + const schemaObject = Types.SchemaObject.from({ + schema_name: schemaName, + format: [{name: 'video', type: 'string', mediatype: 'video/mp4'}], + types: [{name: 'video', mediatype: 'video/mp4', info: 'trailer'}], + created_at_block: 1, + created_at_time: '1', + }) + const schema = Schema.from(schemaObject, utility) + + assert.instanceOf(schema.types[0], Types.SchemaFormatType) + assert.equal(schema.types[0].name, 'video') + assert.equal(schema.types[0].mediatype, 'video/mp4') + assert.equal(schema.types[0].info, 'trailer') + }) test('the API format type stays separate from the contract format type', function () { // The contract's FORMAT is what createschema and extendschema serialize, // so it must stay at {name, type}. The API reports two further fields. diff --git a/packages/atomicassets/test/v2-api.ts b/packages/atomicassets/test/v2-api.ts index caa6cc17..ee138bb4 100644 --- a/packages/atomicassets/test/v2-api.ts +++ b/packages/atomicassets/test/v2-api.ts @@ -188,4 +188,34 @@ suite('v2 API response fields', function () { assert.isUndefined(auction.current_collection_fee) assert.equal(auction.collection.market_fee.value, 0.05) }) + + test('a schema reports the authored media type descriptors', function () { + const schema = Types.SchemaObject.from({ + schema_name: 'cmbz.res', + format: [{name: 'video', type: 'string', mediatype: 'video/mp4', info: 'trailer'}], + types: [{name: 'video', mediatype: 'video/mp4', info: 'trailer'}], + created_at_block: 1, + created_at_time: '1', + }) + + assert.equal(schema.types.length, 1) + assert.instanceOf(schema.types[0], Types.SchemaFormatType) + assert.equal(schema.types[0].name, 'video') + assert.equal(schema.types[0].mediatype, 'video/mp4') + assert.equal(schema.types[0].info, 'trailer') + }) + + test('a schema without the descriptors decodes and reports none', function () { + const schema = Types.SchemaObject.from({ + schema_name: 'cmbz.res', + format: [{name: 'video', type: 'string'}], + created_at_block: 1, + created_at_time: '1', + }) + + // An absent types array means the response does not report descriptors, + // which is a different answer from the empty array a schema endpoint + // sends for a schema that has none. + assert.isUndefined(schema.types) + }) }) From 7cb8a5df01f14455c4df958a5f3360d752d42801 Mon Sep 17 00:00:00 2001 From: Rob Konsdorf Date: Mon, 17 Aug 2026 17:58:01 -0400 Subject: [PATCH 07/18] Add the AtomicMarket v2 royalty read endpoints --- .../src/endpoints/market/types.ts | 30 ++++ .../atomicassets/src/endpoints/market/v1.ts | 128 ++++++++++++++++++ packages/atomicassets/src/endpoints/types.ts | 110 +++++++++++++++ packages/atomicassets/test/config.ts | 7 +- ...a8dfbe47ba945e536c03785dd45e07b6a8297.json | 35 +++++ ...41a1006fdc5b9661c49ab5c64fc468c74cc3d.json | 60 ++++++++ ...5d8f0ee932826bbf7b7c92c4f4dd3f90d33c5.json | 60 ++++++++ ...685ade4a3243eb283607b3763322703a651c2.json | 37 +++++ ...b9d13567a6f1f07fbff1819392a88e675587e.json | 27 ++++ ...d4f7c4eff7d9021f8e2ebfb85622dcf85cc96.json | 43 ++++++ ...acc6aba98e145572720714fb869b5971f917a.json | 19 +++ ...58d05e6df3731ff638d315d58f8110ab96876.json | 15 ++ packages/atomicassets/test/royalties-api.ts | 97 +++++++++++++ packages/atomicassets/test/schema.ts | 15 ++ packages/atomicassets/test/v2-api.ts | 78 +++++++++++ 15 files changed, 760 insertions(+), 1 deletion(-) create mode 100644 packages/atomicassets/test/data/25aa8dfbe47ba945e536c03785dd45e07b6a8297.json create mode 100644 packages/atomicassets/test/data/31641a1006fdc5b9661c49ab5c64fc468c74cc3d.json create mode 100644 packages/atomicassets/test/data/49c5d8f0ee932826bbf7b7c92c4f4dd3f90d33c5.json create mode 100644 packages/atomicassets/test/data/54d685ade4a3243eb283607b3763322703a651c2.json create mode 100644 packages/atomicassets/test/data/8c1b9d13567a6f1f07fbff1819392a88e675587e.json create mode 100644 packages/atomicassets/test/data/b79d4f7c4eff7d9021f8e2ebfb85622dcf85cc96.json create mode 100644 packages/atomicassets/test/data/d99acc6aba98e145572720714fb869b5971f917a.json create mode 100644 packages/atomicassets/test/data/e3758d05e6df3731ff638d315d58f8110ab96876.json create mode 100644 packages/atomicassets/test/royalties-api.ts diff --git a/packages/atomicassets/src/endpoints/market/types.ts b/packages/atomicassets/src/endpoints/market/types.ts index 17a3fabf..f64f8d85 100644 --- a/packages/atomicassets/src/endpoints/market/types.ts +++ b/packages/atomicassets/src/endpoints/market/types.ts @@ -14,6 +14,11 @@ import { Marketplace, OfferObject, ResponseStruct, + RoyaltyAccountTotal, + RoyaltyAttributeRule, + RoyaltyConfig, + RoyaltyPayout, + RoyaltyTemplateRule, SaleObject, SalePrice, SalePriceDay, @@ -303,3 +308,28 @@ export class GetStatsGraphResponse extends ResponseStruct { export class GetStatsSalesResponse extends ResponseStruct { @Struct.field(MarketSale) declare data: MarketSale } + +@Struct.type('get_royalty_config_resp') +export class GetRoyaltyConfigResponse extends ResponseStruct { + @Struct.field(RoyaltyConfig) declare data: RoyaltyConfig +} + +@Struct.type('get_royalty_template_rules_resp') +export class GetRoyaltyTemplateRulesResponse extends ResponseStruct { + @Struct.field(RoyaltyTemplateRule, {array: true}) declare data: RoyaltyTemplateRule[] +} + +@Struct.type('get_royalty_attribute_rules_resp') +export class GetRoyaltyAttributeRulesResponse extends ResponseStruct { + @Struct.field(RoyaltyAttributeRule, {array: true}) declare data: RoyaltyAttributeRule[] +} + +@Struct.type('get_royalty_payouts_resp') +export class GetRoyaltyPayoutsResponse extends ResponseStruct { + @Struct.field(RoyaltyPayout, {array: true}) declare data: RoyaltyPayout[] +} + +@Struct.type('get_royalty_account_resp') +export class GetRoyaltyAccountResponse extends ResponseStruct { + @Struct.field(RoyaltyAccountTotal, {array: true}) declare data: RoyaltyAccountTotal[] +} diff --git a/packages/atomicassets/src/endpoints/market/v1.ts b/packages/atomicassets/src/endpoints/market/v1.ts index 200f5c17..25e53e6e 100644 --- a/packages/atomicassets/src/endpoints/market/v1.ts +++ b/packages/atomicassets/src/endpoints/market/v1.ts @@ -201,6 +201,26 @@ export interface GetTemplateBuyoffersOptions { sort?: 'created' | 'updated' | 'ending' | 'buyoffer_id' | 'price' | 'template_mint' | 'name' } +export interface GetRoyaltyPayoutsOptions { + recipient?: NameType[] + collection_name?: NameType[] + asset_id?: UInt64Type[] + symbol?: string + listing_type?: 'unresolved' | 'sale' | 'auction' | 'buyoffer' | 'template_buyoffer' + listing_id?: UInt64Type + category?: Array<'founders' | 'template' | 'attribute' | 'dust'> + ids?: UInt64Type[] + // lower_bound and upper_bound range over log_global_sequence. + lower_bound?: string + upper_bound?: string + before?: number + after?: number + page?: number + limit?: number + order?: 'asc' | 'desc' + sort?: 'created' | 'amount' +} + export class MarketV1APIClient { constructor(private client: APIClient) {} @@ -953,4 +973,112 @@ export class MarketV1APIClient { responseType: Market.GetConfigResponse, }) } + + /** + * The royalty configuration of a collection, mirrored from `royaltyconf`. + * + * A collection with no configuration answers HTTP 416, which reaches the + * caller as an `APIError` with `error.response.status === 416`. That is the + * "not configured" answer rather than a failure, and every collection on an + * AtomicMarket v1 chain answers that way. An indexer that predates the + * royalty routes answers 404. + */ + async get_royalty_config(collection_name: NameType) { + return this.client.call({ + path: `/atomicmarket/v1/royalties/${collection_name}`, + method: 'GET', + responseType: Market.GetRoyaltyConfigResponse, + }) + } + + async get_royalty_template_rules( + collection_name: NameType, + options?: { + template_id?: Int32Type[] + page?: number + limit?: number + } + ) { + const bodyParams = buildBodyParams(options) + + return this.client.call({ + path: `/atomicmarket/v1/royalties/${collection_name}/templates`, + method: 'POST', + params: bodyParams, + headers: {'Content-Type': 'application/json'}, + responseType: Market.GetRoyaltyTemplateRulesResponse, + }) + } + + async get_royalty_attribute_rules( + collection_name: NameType, + options?: { + source?: number + field?: string + page?: number + limit?: number + } + ) { + const bodyParams = buildBodyParams(options) + + return this.client.call({ + path: `/atomicmarket/v1/royalties/${collection_name}/attributes`, + method: 'POST', + params: bodyParams, + headers: {'Content-Type': 'application/json'}, + responseType: Market.GetRoyaltyAttributeRulesResponse, + }) + } + + /** The settled payout ledger. Empty on an AtomicMarket v1 chain. */ + async get_royalty_payouts( + options?: GetRoyaltyPayoutsOptions, + extra_options?: {[key: string]: string} + ) { + const bodyParams = buildBodyParams(options, extra_options) + + return this.client.call({ + path: `/atomicmarket/v1/royalties/payouts`, + method: 'POST', + params: bodyParams, + headers: {'Content-Type': 'application/json'}, + responseType: Market.GetRoyaltyPayoutsResponse, + }) + } + + async get_royalty_payouts_count( + options?: GetRoyaltyPayoutsOptions, + extra_options?: {[key: string]: string} + ) { + const bodyParams = buildBodyParams(options, extra_options) + + return this.client.call({ + path: `/atomicmarket/v1/royalties/payouts/_count`, + method: 'POST', + params: bodyParams, + headers: {'Content-Type': 'application/json'}, + responseType: CountResponseStruct, + }) + } + + /** One row per token symbol the account has been paid in. */ + async get_royalty_account( + account: NameType, + options?: { + collection_name?: NameType[] + symbol?: string + before?: number + after?: number + } + ) { + const bodyParams = buildBodyParams(options) + + return this.client.call({ + path: `/atomicmarket/v1/royalties/accounts/${account}`, + method: 'POST', + params: bodyParams, + headers: {'Content-Type': 'application/json'}, + responseType: Market.GetRoyaltyAccountResponse, + }) + } } diff --git a/packages/atomicassets/src/endpoints/types.ts b/packages/atomicassets/src/endpoints/types.ts index ab198c92..c85631c8 100644 --- a/packages/atomicassets/src/endpoints/types.ts +++ b/packages/atomicassets/src/endpoints/types.ts @@ -478,6 +478,116 @@ export class Marketplace extends Struct { @Struct.field('string') declare created_at_time: string } +/** + * A royalty recipient and its weight as returned by the API. + * + * This is intentionally not the AtomicMarket contract's `ROYALTYPAIR` struct. + * That one serializes action data for `setroyalconf` and the rule actions, this + * one only decodes an API row, and keeping them apart is what stops a decoded + * row from reaching the wire. + */ +@Struct.type('royalty_pair') +export class RoyaltyPair extends Struct { + @Struct.field(Name) declare recipient: Name + @Struct.field(UInt32) declare weight: UInt32 +} + +/** + * The `royaltyconf` row for a collection, mirrored field for field. + * + * The API answers HTTP 416 for a collection that has no row, and every + * collection on an AtomicMarket v1 chain answers that way. AtomicMarket v2 only. + */ +@Struct.type('royalty_config') +export class RoyaltyConfig extends Struct { + @Struct.field(Name) declare market_contract: Name + @Struct.field(Name) declare collection_name: Name + @Struct.field(RoyaltyPair, {array: true}) declare founders: RoyaltyPair[] + /** How attribute rules combine: 0 merged, 1 granular. */ + @Struct.field(UInt8) declare attribute_mode: UInt8 + @Struct.field(UInt32) declare split_founders: UInt32 + @Struct.field(UInt32) declare split_templates: UInt32 + @Struct.field(UInt32) declare split_attributes: UInt32 + @Struct.field(UInt64) declare updated_at_block: UInt64 + @Struct.field('string') declare updated_at_time: string + @Struct.field(UInt64) declare created_at_block: UInt64 + @Struct.field('string') declare created_at_time: string +} + +/** A `royaltytemp` row: the per-template recipient override. AtomicMarket v2 only. */ +@Struct.type('royalty_template_rule') +export class RoyaltyTemplateRule extends Struct { + @Struct.field(Name) declare market_contract: Name + @Struct.field(Name) declare collection_name: Name + @Struct.field(Int32) declare template_id: Int32 + @Struct.field(RoyaltyPair, {array: true}) declare recipients: RoyaltyPair[] + @Struct.field(UInt64) declare updated_at_block: UInt64 + @Struct.field('string') declare updated_at_time: string + @Struct.field(UInt64) declare created_at_block: UInt64 + @Struct.field('string') declare created_at_time: string +} + +/** A `royaltyattr` row: the attribute-matched recipient override. AtomicMarket v2 only. */ +@Struct.type('royalty_attribute_rule') +export class RoyaltyAttributeRule extends Struct { + @Struct.field(Name) declare market_contract: Name + @Struct.field(Name) declare collection_name: Name + @Struct.field(UInt64) declare rule_id: UInt64 + /** Which data source the matched attribute is read from, as the contract stores it. */ + @Struct.field(UInt8) declare source: UInt8 + @Struct.field('string') declare field: string + /** + * The raw `["type", value]` variant tuple the rule matches on. It is kept + * untyped so an integer payload stays the string the API sent and nothing + * is reparsed. The content is chain-authored by the collection and relayed + * by the indexer, so a caller shape-checks it before use. + */ + @Struct.field('any') declare value: [string, unknown] + @Struct.field(UInt32) declare weight: UInt32 + @Struct.field(RoyaltyPair, {array: true}) declare recipients: RoyaltyPair[] + /** Hex-encoded sha256 of the matched attribute, as the contract stores it. */ + @Struct.field('string') declare lookup_hash: string + @Struct.field(UInt64) declare updated_at_block: UInt64 + @Struct.field('string') declare updated_at_time: string + @Struct.field(UInt64) declare created_at_block: UInt64 + @Struct.field('string') declare created_at_time: string +} + +/** + * One settled royalty payout, keyed by the log trace global sequence and the + * entry's position in the payouts vector. AtomicMarket v2 only. + */ +@Struct.type('royalty_payout') +export class RoyaltyPayout extends Struct { + @Struct.field(Name) declare market_contract: Name + @Struct.field(UInt64) declare log_global_sequence: UInt64 + @Struct.field(UInt32) declare payout_index: UInt32 + /** One of unresolved, sale, auction, buyoffer, template_buyoffer. */ + @Struct.field('string', {optional: true}) declare listing_type: string + @Struct.field(UInt64, {optional: true}) declare listing_id: UInt64 + /** One of founders, template, attribute, dust. */ + @Struct.field('string', {optional: true}) declare category: string + @Struct.field(Name) declare collection_name: Name + @Struct.field(UInt64, {optional: true}) declare asset_id: UInt64 + @Struct.field(Int32, {optional: true}) declare template_id: Int32 + @Struct.field(UInt64, {optional: true}) declare rule_id: UInt64 + @Struct.field(Name) declare recipient: Name + @Struct.field(UInt64) declare amount: UInt64 + @Struct.field('string') declare token_symbol: string + @Struct.field(UInt8) declare token_precision: UInt8 + @Struct.field(Name) declare token_contract: Name + /** Hex-encoded id of the transaction the payout was logged in. */ + @Struct.field('string') declare txid: string + @Struct.field(UInt64) declare created_at_block: UInt64 + @Struct.field('string') declare created_at_time: string +} + +/** An account's settled royalties for one token symbol. AtomicMarket v2 only. */ +@Struct.type('royalty_account_total') +export class RoyaltyAccountTotal extends TokenAmount { + @Struct.field(UInt64) declare payout_count: UInt64 +} + @Struct.type('saleprice') export class SalePrice extends Struct { @Struct.field(UInt64, {optional: true}) declare sale_id: UInt64 diff --git a/packages/atomicassets/test/config.ts b/packages/atomicassets/test/config.ts index 0186006c..a539d6ee 100644 --- a/packages/atomicassets/test/config.ts +++ b/packages/atomicassets/test/config.ts @@ -3,10 +3,15 @@ export const TEST_CONFIG = { // Base URL for the AtomicAssets API BASE_URL: 'https://wax-atomic.alcor.exchange/', + // Base URL for an AtomicMarket v2 indexer. The host above indexes WAX + // mainnet, an AtomicMarket v1 chain, where every royalty route answers 416 + // or an empty array, so the royalty fixtures are recorded here instead. + V2_BASE_URL: 'https://test.wax.api.atomicassets.io', + // Default test timeout and slow thresholds TIMEOUT: 10 * 1000, SLOW_THRESHOLD: 300, } as const // Export individual values for convenience -export const {BASE_URL, TIMEOUT, SLOW_THRESHOLD} = TEST_CONFIG +export const {BASE_URL, V2_BASE_URL, TIMEOUT, SLOW_THRESHOLD} = TEST_CONFIG diff --git a/packages/atomicassets/test/data/25aa8dfbe47ba945e536c03785dd45e07b6a8297.json b/packages/atomicassets/test/data/25aa8dfbe47ba945e536c03785dd45e07b6a8297.json new file mode 100644 index 00000000..a880b83a --- /dev/null +++ b/packages/atomicassets/test/data/25aa8dfbe47ba945e536c03785dd45e07b6a8297.json @@ -0,0 +1,35 @@ +{ + "request": { + "path": "https://test.wax.api.atomicassets.io/atomicmarket/v1/royalties/royaltycol11/templates", + "params": { + "method": "POST", + "body": "{}", + "headers": { + "Content-Type": "application/json" + } + } + }, + "status": 200, + "json": { + "success": true, + "data": [ + { + "market_contract": "atomicmarket", + "collection_name": "royaltycol11", + "template_id": "662912", + "recipients": [ + { + "weight": 1, + "recipient": "pe2etestacct" + } + ], + "updated_at_block": "414895353", + "updated_at_time": "1783371584000", + "created_at_block": "414895353", + "created_at_time": "1783371584000" + } + ], + "query_time": 1787002594984 + }, + "text": "{\"success\":true,\"data\":[{\"market_contract\":\"atomicmarket\",\"collection_name\":\"royaltycol11\",\"template_id\":\"662912\",\"recipients\":[{\"weight\":1,\"recipient\":\"pe2etestacct\"}],\"updated_at_block\":\"414895353\",\"updated_at_time\":\"1783371584000\",\"created_at_block\":\"414895353\",\"created_at_time\":\"1783371584000\"}],\"query_time\":1787002594984}" +} \ No newline at end of file diff --git a/packages/atomicassets/test/data/31641a1006fdc5b9661c49ab5c64fc468c74cc3d.json b/packages/atomicassets/test/data/31641a1006fdc5b9661c49ab5c64fc468c74cc3d.json new file mode 100644 index 00000000..769ec7df --- /dev/null +++ b/packages/atomicassets/test/data/31641a1006fdc5b9661c49ab5c64fc468c74cc3d.json @@ -0,0 +1,60 @@ +{ + "request": { + "path": "https://test.wax.api.atomicassets.io/atomicmarket/v1/royalties/payouts", + "params": { + "method": "POST", + "body": "{\"limit\":\"2\"}", + "headers": { + "Content-Type": "application/json" + } + } + }, + "status": 200, + "json": { + "success": true, + "data": [ + { + "market_contract": "atomicmarket", + "log_global_sequence": "840330124", + "payout_index": 0, + "listing_type": "sale", + "listing_id": "46890", + "category": "attribute", + "collection_name": "royaltycol11", + "asset_id": "1099603751717", + "template_id": null, + "rule_id": "2", + "recipient": "jacktestr125", + "amount": "1250000", + "token_symbol": "WAX", + "token_precision": 8, + "token_contract": "eosio.token", + "txid": "6df6118add83bab9b0d79b5a7a6cf3877133893daefb96280f6f52943db74f2a", + "created_at_block": "414927681", + "created_at_time": "1783387748000" + }, + { + "market_contract": "atomicmarket", + "log_global_sequence": "840330123", + "payout_index": 0, + "listing_type": "sale", + "listing_id": "46890", + "category": "template", + "collection_name": "royaltycol11", + "asset_id": "1099603751717", + "template_id": "662912", + "rule_id": null, + "recipient": "pe2etestacct", + "amount": "1250000", + "token_symbol": "WAX", + "token_precision": 8, + "token_contract": "eosio.token", + "txid": "6df6118add83bab9b0d79b5a7a6cf3877133893daefb96280f6f52943db74f2a", + "created_at_block": "414927681", + "created_at_time": "1783387748000" + } + ], + "query_time": 1787002593620 + }, + "text": "{\"success\":true,\"data\":[{\"market_contract\":\"atomicmarket\",\"log_global_sequence\":\"840330124\",\"payout_index\":0,\"listing_type\":\"sale\",\"listing_id\":\"46890\",\"category\":\"attribute\",\"collection_name\":\"royaltycol11\",\"asset_id\":\"1099603751717\",\"template_id\":null,\"rule_id\":\"2\",\"recipient\":\"jacktestr125\",\"amount\":\"1250000\",\"token_symbol\":\"WAX\",\"token_precision\":8,\"token_contract\":\"eosio.token\",\"txid\":\"6df6118add83bab9b0d79b5a7a6cf3877133893daefb96280f6f52943db74f2a\",\"created_at_block\":\"414927681\",\"created_at_time\":\"1783387748000\"},{\"market_contract\":\"atomicmarket\",\"log_global_sequence\":\"840330123\",\"payout_index\":0,\"listing_type\":\"sale\",\"listing_id\":\"46890\",\"category\":\"template\",\"collection_name\":\"royaltycol11\",\"asset_id\":\"1099603751717\",\"template_id\":\"662912\",\"rule_id\":null,\"recipient\":\"pe2etestacct\",\"amount\":\"1250000\",\"token_symbol\":\"WAX\",\"token_precision\":8,\"token_contract\":\"eosio.token\",\"txid\":\"6df6118add83bab9b0d79b5a7a6cf3877133893daefb96280f6f52943db74f2a\",\"created_at_block\":\"414927681\",\"created_at_time\":\"1783387748000\"}],\"query_time\":1787002593620}" +} \ No newline at end of file diff --git a/packages/atomicassets/test/data/49c5d8f0ee932826bbf7b7c92c4f4dd3f90d33c5.json b/packages/atomicassets/test/data/49c5d8f0ee932826bbf7b7c92c4f4dd3f90d33c5.json new file mode 100644 index 00000000..1a621a0b --- /dev/null +++ b/packages/atomicassets/test/data/49c5d8f0ee932826bbf7b7c92c4f4dd3f90d33c5.json @@ -0,0 +1,60 @@ +{ + "request": { + "path": "https://test.wax.api.atomicassets.io/atomicmarket/v1/royalties/payouts", + "params": { + "method": "POST", + "body": "{\"category\":\"attribute\",\"listing_type\":\"sale\",\"sort\":\"amount\",\"order\":\"asc\",\"limit\":\"2\"}", + "headers": { + "Content-Type": "application/json" + } + } + }, + "status": 200, + "json": { + "success": true, + "data": [ + { + "market_contract": "atomicmarket", + "log_global_sequence": "840330124", + "payout_index": 0, + "listing_type": "sale", + "listing_id": "46890", + "category": "attribute", + "collection_name": "royaltycol11", + "asset_id": "1099603751717", + "template_id": null, + "rule_id": "2", + "recipient": "jacktestr125", + "amount": "1250000", + "token_symbol": "WAX", + "token_precision": 8, + "token_contract": "eosio.token", + "txid": "6df6118add83bab9b0d79b5a7a6cf3877133893daefb96280f6f52943db74f2a", + "created_at_block": "414927681", + "created_at_time": "1783387748000" + }, + { + "market_contract": "atomicmarket", + "log_global_sequence": "840242817", + "payout_index": 0, + "listing_type": "sale", + "listing_id": "46855", + "category": "attribute", + "collection_name": "royaltycol11", + "asset_id": "1099603751714", + "template_id": null, + "rule_id": "1", + "recipient": "jacktestr125", + "amount": "12500000", + "token_symbol": "WAX", + "token_precision": 8, + "token_contract": "eosio.token", + "txid": "a346ac1e0324c39a677f14dd85f23f6f94d602bf229d6810050ac3512a395a08", + "created_at_block": "414849702", + "created_at_time": "1783348758500" + } + ], + "query_time": 1787002593925 + }, + "text": "{\"success\":true,\"data\":[{\"market_contract\":\"atomicmarket\",\"log_global_sequence\":\"840330124\",\"payout_index\":0,\"listing_type\":\"sale\",\"listing_id\":\"46890\",\"category\":\"attribute\",\"collection_name\":\"royaltycol11\",\"asset_id\":\"1099603751717\",\"template_id\":null,\"rule_id\":\"2\",\"recipient\":\"jacktestr125\",\"amount\":\"1250000\",\"token_symbol\":\"WAX\",\"token_precision\":8,\"token_contract\":\"eosio.token\",\"txid\":\"6df6118add83bab9b0d79b5a7a6cf3877133893daefb96280f6f52943db74f2a\",\"created_at_block\":\"414927681\",\"created_at_time\":\"1783387748000\"},{\"market_contract\":\"atomicmarket\",\"log_global_sequence\":\"840242817\",\"payout_index\":0,\"listing_type\":\"sale\",\"listing_id\":\"46855\",\"category\":\"attribute\",\"collection_name\":\"royaltycol11\",\"asset_id\":\"1099603751714\",\"template_id\":null,\"rule_id\":\"1\",\"recipient\":\"jacktestr125\",\"amount\":\"12500000\",\"token_symbol\":\"WAX\",\"token_precision\":8,\"token_contract\":\"eosio.token\",\"txid\":\"a346ac1e0324c39a677f14dd85f23f6f94d602bf229d6810050ac3512a395a08\",\"created_at_block\":\"414849702\",\"created_at_time\":\"1783348758500\"}],\"query_time\":1787002593925}" +} \ No newline at end of file diff --git a/packages/atomicassets/test/data/54d685ade4a3243eb283607b3763322703a651c2.json b/packages/atomicassets/test/data/54d685ade4a3243eb283607b3763322703a651c2.json new file mode 100644 index 00000000..8ad46f28 --- /dev/null +++ b/packages/atomicassets/test/data/54d685ade4a3243eb283607b3763322703a651c2.json @@ -0,0 +1,37 @@ +{ + "request": { + "path": "https://test.wax.api.atomicassets.io/atomicmarket/v1/royalties/royaltycol11", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": { + "success": true, + "data": { + "market_contract": "atomicmarket", + "collection_name": "royaltycol11", + "founders": [ + { + "weight": 1, + "recipient": "jacktestr125" + }, + { + "weight": 3, + "recipient": "pe2etestacct" + } + ], + "attribute_mode": 0, + "split_founders": "2", + "split_templates": "1", + "split_attributes": "1", + "updated_at_block": "414895352", + "updated_at_time": "1783371583500", + "created_at_block": "414895352", + "created_at_time": "1783371583500" + }, + "query_time": 1787002594513 + }, + "text": "{\"success\":true,\"data\":{\"market_contract\":\"atomicmarket\",\"collection_name\":\"royaltycol11\",\"founders\":[{\"weight\":1,\"recipient\":\"jacktestr125\"},{\"weight\":3,\"recipient\":\"pe2etestacct\"}],\"attribute_mode\":0,\"split_founders\":\"2\",\"split_templates\":\"1\",\"split_attributes\":\"1\",\"updated_at_block\":\"414895352\",\"updated_at_time\":\"1783371583500\",\"created_at_block\":\"414895352\",\"created_at_time\":\"1783371583500\"},\"query_time\":1787002594513}" +} \ No newline at end of file diff --git a/packages/atomicassets/test/data/8c1b9d13567a6f1f07fbff1819392a88e675587e.json b/packages/atomicassets/test/data/8c1b9d13567a6f1f07fbff1819392a88e675587e.json new file mode 100644 index 00000000..80ea5ba0 --- /dev/null +++ b/packages/atomicassets/test/data/8c1b9d13567a6f1f07fbff1819392a88e675587e.json @@ -0,0 +1,27 @@ +{ + "request": { + "path": "https://test.wax.api.atomicassets.io/atomicmarket/v1/royalties/accounts/jacktestr125", + "params": { + "method": "POST", + "body": "{}", + "headers": { + "Content-Type": "application/json" + } + } + }, + "status": 200, + "json": { + "success": true, + "data": [ + { + "token_symbol": "WAX", + "token_precision": 8, + "token_contract": "eosio.token", + "amount": "76875000", + "payout_count": "10" + } + ], + "query_time": 1787002594271 + }, + "text": "{\"success\":true,\"data\":[{\"token_symbol\":\"WAX\",\"token_precision\":8,\"token_contract\":\"eosio.token\",\"amount\":\"76875000\",\"payout_count\":\"10\"}],\"query_time\":1787002594271}" +} \ No newline at end of file diff --git a/packages/atomicassets/test/data/b79d4f7c4eff7d9021f8e2ebfb85622dcf85cc96.json b/packages/atomicassets/test/data/b79d4f7c4eff7d9021f8e2ebfb85622dcf85cc96.json new file mode 100644 index 00000000..5177ba89 --- /dev/null +++ b/packages/atomicassets/test/data/b79d4f7c4eff7d9021f8e2ebfb85622dcf85cc96.json @@ -0,0 +1,43 @@ +{ + "request": { + "path": "https://test.wax.api.atomicassets.io/atomicmarket/v1/royalties/royaltycol11/attributes", + "params": { + "method": "POST", + "body": "{}", + "headers": { + "Content-Type": "application/json" + } + } + }, + "status": 200, + "json": { + "success": true, + "data": [ + { + "market_contract": "atomicmarket", + "collection_name": "royaltycol11", + "rule_id": "2", + "source": 0, + "field": "rarity", + "value": [ + "string", + "legendary" + ], + "weight": "1", + "recipients": [ + { + "weight": 1, + "recipient": "jacktestr125" + } + ], + "lookup_hash": "68ec3427c453d24cfd9faaa1db9d39533ab06b8127bb09a6f2958e5c26ebce74", + "updated_at_block": "414895354", + "updated_at_time": "1783371584500", + "created_at_block": "414895354", + "created_at_time": "1783371584500" + } + ], + "query_time": 1787002595151 + }, + "text": "{\"success\":true,\"data\":[{\"market_contract\":\"atomicmarket\",\"collection_name\":\"royaltycol11\",\"rule_id\":\"2\",\"source\":0,\"field\":\"rarity\",\"value\":[\"string\",\"legendary\"],\"weight\":\"1\",\"recipients\":[{\"weight\":1,\"recipient\":\"jacktestr125\"}],\"lookup_hash\":\"68ec3427c453d24cfd9faaa1db9d39533ab06b8127bb09a6f2958e5c26ebce74\",\"updated_at_block\":\"414895354\",\"updated_at_time\":\"1783371584500\",\"created_at_block\":\"414895354\",\"created_at_time\":\"1783371584500\"}],\"query_time\":1787002595151}" +} \ No newline at end of file diff --git a/packages/atomicassets/test/data/d99acc6aba98e145572720714fb869b5971f917a.json b/packages/atomicassets/test/data/d99acc6aba98e145572720714fb869b5971f917a.json new file mode 100644 index 00000000..0e9b925e --- /dev/null +++ b/packages/atomicassets/test/data/d99acc6aba98e145572720714fb869b5971f917a.json @@ -0,0 +1,19 @@ +{ + "request": { + "path": "https://test.wax.api.atomicassets.io/atomicmarket/v1/royalties/payouts/_count", + "params": { + "method": "POST", + "body": "{}", + "headers": { + "Content-Type": "application/json" + } + } + }, + "status": 200, + "json": { + "success": true, + "data": "20", + "query_time": 1787002594102 + }, + "text": "{\"success\":true,\"data\":\"20\",\"query_time\":1787002594102}" +} \ No newline at end of file diff --git a/packages/atomicassets/test/data/e3758d05e6df3731ff638d315d58f8110ab96876.json b/packages/atomicassets/test/data/e3758d05e6df3731ff638d315d58f8110ab96876.json new file mode 100644 index 00000000..6e4b39db --- /dev/null +++ b/packages/atomicassets/test/data/e3758d05e6df3731ff638d315d58f8110ab96876.json @@ -0,0 +1,15 @@ +{ + "request": { + "path": "https://test.wax.api.atomicassets.io/atomicmarket/v1/royalties/alien.worlds", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 416, + "json": { + "success": false, + "message": "Royalty config not found" + }, + "text": "{\"success\":false,\"message\":\"Royalty config not found\"}" +} \ No newline at end of file diff --git a/packages/atomicassets/test/royalties-api.ts b/packages/atomicassets/test/royalties-api.ts new file mode 100644 index 00000000..6d817181 --- /dev/null +++ b/packages/atomicassets/test/royalties-api.ts @@ -0,0 +1,97 @@ +import {assert} from 'chai' + +import {APIClient, APIError, FetchProvider} from '@wharfkit/antelope' +import {mockFetch} from '@wharfkit/mock-data' +import {V2_BASE_URL, TIMEOUT, SLOW_THRESHOLD} from './config' + +import {AtomicAssetsAPIClient, Types} from '$lib' + +// The royalty routes answer with rows only on an AtomicMarket v2 chain, so this +// client points at the testnet indexer instead of the shared BASE_URL. +const client = new APIClient({ + provider: new FetchProvider(V2_BASE_URL, {fetch: mockFetch}), +}) + +// Setup the API +const atomicassets = new AtomicAssetsAPIClient(client) + +const collectionName = 'royaltycol11' +const unconfiguredCollection = 'alien.worlds' +const recipient = 'jacktestr125' + +suite('atomicmarket royalties', function () { + this.slow(SLOW_THRESHOLD) + this.timeout(TIMEOUT) + + test('get_royalty_payouts', async function () { + const res = await atomicassets.atomicmarket.v1.get_royalty_payouts({limit: 2}) + assert.instanceOf(res, Types.Market.GetRoyaltyPayoutsResponse) + assert.equal(res.success, true) + assert.isNotEmpty(res.data) + }) + + test('get_royalty_payouts filtered', async function () { + const res = await atomicassets.atomicmarket.v1.get_royalty_payouts({ + category: ['attribute'], + listing_type: 'sale', + sort: 'amount', + order: 'asc', + limit: 2, + }) + assert.instanceOf(res, Types.Market.GetRoyaltyPayoutsResponse) + assert.equal(res.success, true) + assert.isNotEmpty(res.data) + }) + + test('get_royalty_payouts_count', async function () { + const res = await atomicassets.atomicmarket.v1.get_royalty_payouts_count() + assert.instanceOf(res, Types.CountResponseStruct) + assert.equal(res.success, true) + assert.isAbove(res.data.toNumber(), 0) + }) + + test('get_royalty_account', async function () { + const res = await atomicassets.atomicmarket.v1.get_royalty_account(recipient) + assert.instanceOf(res, Types.Market.GetRoyaltyAccountResponse) + assert.equal(res.success, true) + assert.isNotEmpty(res.data) + assert.isAbove(res.data[0].payout_count.toNumber(), 0) + }) + + test('get_royalty_config', async function () { + const res = await atomicassets.atomicmarket.v1.get_royalty_config(collectionName) + assert.instanceOf(res, Types.Market.GetRoyaltyConfigResponse) + assert.equal(res.success, true) + assert.isNotEmpty(res.data) + assert.isTrue(res.data.collection_name.equals(collectionName)) + }) + + test('get_royalty_config answers 416 for an unconfigured collection', async function () { + // A collection with no royalty configuration is not a failure for the + // caller, and on an AtomicMarket v1 chain every collection answers so. + let error: APIError | undefined + + try { + await atomicassets.atomicmarket.v1.get_royalty_config(unconfiguredCollection) + } catch (caught) { + error = caught as APIError + } + + assert.instanceOf(error, APIError) + assert.equal(error?.response.status, 416) + }) + + test('get_royalty_template_rules', async function () { + const res = await atomicassets.atomicmarket.v1.get_royalty_template_rules(collectionName) + assert.instanceOf(res, Types.Market.GetRoyaltyTemplateRulesResponse) + assert.equal(res.success, true) + assert.isNotEmpty(res.data) + }) + + test('get_royalty_attribute_rules', async function () { + const res = await atomicassets.atomicmarket.v1.get_royalty_attribute_rules(collectionName) + assert.instanceOf(res, Types.Market.GetRoyaltyAttributeRulesResponse) + assert.equal(res.success, true) + assert.isNotEmpty(res.data) + }) +}) diff --git a/packages/atomicassets/test/schema.ts b/packages/atomicassets/test/schema.ts index 346ac50f..b5bc81e3 100644 --- a/packages/atomicassets/test/schema.ts +++ b/packages/atomicassets/test/schema.ts @@ -102,6 +102,21 @@ suite('Schema', function () { assert.equal(schema.types[0].mediatype, 'video/mp4') assert.equal(schema.types[0].info, 'trailer') }) + + test('the API royalty pair stays separate from the contract royalty pair', function () { + // The contract's ROYALTYPAIR is what setroyalconf and the rule actions + // serialize. The API struct only decodes a response row, and keeping the + // two apart is what stops a decoded row from reaching action data. + const contractFields = AtomicMarketContract.Types.ROYALTYPAIR.abiFields?.map((f) => f.name) + const apiFields = Types.RoyaltyPair.abiFields?.map((f) => f.name) + + assert.deepEqual(contractFields, ['recipient', 'weight']) + assert.deepEqual(apiFields, ['recipient', 'weight']) + assert.notEqual(Types.RoyaltyPair, AtomicMarketContract.Types.ROYALTYPAIR as any) + assert.equal(AtomicMarketContract.Types.ROYALTYPAIR.abiName, 'ROYALTYPAIR') + assert.equal(Types.RoyaltyPair.abiName, 'royalty_pair') + }) + test('the API format type stays separate from the contract format type', function () { // The contract's FORMAT is what createschema and extendschema serialize, // so it must stay at {name, type}. The API reports two further fields. diff --git a/packages/atomicassets/test/v2-api.ts b/packages/atomicassets/test/v2-api.ts index ee138bb4..e9e65bd6 100644 --- a/packages/atomicassets/test/v2-api.ts +++ b/packages/atomicassets/test/v2-api.ts @@ -218,4 +218,82 @@ suite('v2 API response fields', function () { // sends for a schema that has none. assert.isUndefined(schema.types) }) + + test('a royalty payout decodes the nullable ids the indexer emits', function () { + const attributePayout = Types.RoyaltyPayout.from({ + market_contract: 'atomicmarket', + log_global_sequence: '840330124', + payout_index: 0, + listing_type: 'sale', + listing_id: '46890', + category: 'attribute', + collection_name: 'royaltycol11', + asset_id: '1099603751717', + template_id: null, + rule_id: '2', + recipient: 'jacktestr125', + amount: '1250000', + token_symbol: 'WAX', + token_precision: 8, + token_contract: 'eosio.token', + txid: '6df6118add83bab9b0d79b5a7a6cf3877133893daefb96280f6f52943db74f2a', + created_at_block: '414927681', + created_at_time: '1783387748000', + }) + + assert.equal(attributePayout.amount.toNumber(), 1250000) + assert.equal(attributePayout.listing_id.toNumber(), 46890) + assert.equal(attributePayout.rule_id.toNumber(), 2) + assert.isUndefined(attributePayout.template_id) + + // listing_type and category are the strings the indexer maps the stored + // integers to, and it sends null for a value it has no name for. + const unmappedPayout = Types.RoyaltyPayout.from({ + market_contract: 'atomicmarket', + log_global_sequence: '840330125', + payout_index: 1, + listing_type: null, + listing_id: null, + category: null, + collection_name: 'royaltycol11', + asset_id: null, + template_id: null, + rule_id: null, + recipient: 'jacktestr125', + amount: '1250000', + token_symbol: 'WAX', + token_precision: 8, + token_contract: 'eosio.token', + txid: '6df6118add83bab9b0d79b5a7a6cf3877133893daefb96280f6f52943db74f2a', + created_at_block: '414927681', + created_at_time: '1783387748000', + }) + + assert.isNull(unmappedPayout.listing_type) + assert.isNull(unmappedPayout.category) + assert.isUndefined(unmappedPayout.listing_id) + assert.isUndefined(unmappedPayout.asset_id) + }) + + test('a royalty attribute rule keeps the raw variant tuple', function () { + const rule = Types.RoyaltyAttributeRule.from({ + market_contract: 'atomicmarket', + collection_name: 'royaltycol11', + rule_id: '2', + source: 0, + field: 'rarity', + value: ['string', 'legendary'], + weight: '1', + recipients: [{recipient: 'jacktestr125', weight: 1}], + lookup_hash: '68ec3427c453d24cfd9faaa1db9d39533ab06b8127bb09a6f2958e5c26ebce74', + updated_at_block: '414895354', + updated_at_time: '1783371584500', + created_at_block: '414895354', + created_at_time: '1783371584500', + }) + + assert.deepEqual(rule.value, ['string', 'legendary']) + assert.equal(rule.weight.toNumber(), 1) + assert.isTrue(rule.recipients[0].recipient.equals('jacktestr125')) + }) }) From f9fa484edc4c33e8f7d61cb677f316b7062730f4 Mon Sep 17 00:00:00 2001 From: Rob Konsdorf Date: Mon, 17 Aug 2026 17:58:01 -0400 Subject: [PATCH 08/18] Add the marketplace stats endpoint --- .../src/endpoints/market/types.ts | 31 +++ .../atomicassets/src/endpoints/market/v1.ts | 24 +++ packages/atomicassets/test/api.ts | 15 ++ ...bd9169724d91c76934cae7a95530d2ffc4bf9.json | 187 ++++++++++++++++++ packages/atomicassets/test/v2-api.ts | 31 +++ 5 files changed, 288 insertions(+) create mode 100644 packages/atomicassets/test/data/81dbd9169724d91c76934cae7a95530d2ffc4bf9.json diff --git a/packages/atomicassets/src/endpoints/market/types.ts b/packages/atomicassets/src/endpoints/market/types.ts index f64f8d85..b8e0f73d 100644 --- a/packages/atomicassets/src/endpoints/market/types.ts +++ b/packages/atomicassets/src/endpoints/market/types.ts @@ -108,6 +108,32 @@ export class MarketAccount extends Struct { @Struct.field(AccountStat) declare result: AccountStat } +@Struct.type('marketplace_stat') +export class MarketplaceStat extends Struct { + @Struct.field(Name) declare market_contract: Name + /** + * Null when no marketplace is stored, and the empty string when the + * stored value is the default marketplace name. Both mean the same + * thing to a client. + */ + @Struct.field('string', {optional: true}) declare marketplace_name: string + @Struct.field(UInt64) declare sellers: UInt64 + @Struct.field(UInt64) declare buyers: UInt64 + /** + * The field is declared optional defensively because the outer sort + * carries NULLS LAST. The aggregate answers `0`, not null, for a side + * with no rows. + */ + @Struct.field(UInt64, {optional: true}) declare maker_volume: UInt64 + @Struct.field(UInt64, {optional: true}) declare taker_volume: UInt64 +} + +@Struct.type('market_marketplaces') +export class MarketMarketplaces extends Struct { + @Struct.field(Token) declare symbol: Token + @Struct.field(MarketplaceStat, {array: true}) declare results: MarketplaceStat[] +} + @Struct.type('schema_stat_v1') export class SchemaStatV1 extends Struct { @Struct.field(Name) declare contract: Name @@ -309,6 +335,11 @@ export class GetStatsSalesResponse extends ResponseStruct { @Struct.field(MarketSale) declare data: MarketSale } +@Struct.type('get_stats_markets_resp') +export class GetStatsMarketsResponse extends ResponseStruct { + @Struct.field(MarketMarketplaces) declare data: MarketMarketplaces +} + @Struct.type('get_royalty_config_resp') export class GetRoyaltyConfigResponse extends ResponseStruct { @Struct.field(RoyaltyConfig) declare data: RoyaltyConfig diff --git a/packages/atomicassets/src/endpoints/market/v1.ts b/packages/atomicassets/src/endpoints/market/v1.ts index 25e53e6e..fbc7f966 100644 --- a/packages/atomicassets/src/endpoints/market/v1.ts +++ b/packages/atomicassets/src/endpoints/market/v1.ts @@ -966,6 +966,30 @@ export class MarketV1APIClient { }) } + /** + * Seller, buyer, and volume totals per marketplace for one token symbol. + * + * Bound the query with `collection_whitelist` or an `after` window. On a + * mainnet-sized indexer the unbounded aggregate exceeds the server timeout. + */ + async get_stats_markets(options: { + symbol: string + collection_blacklist?: NameType[] + collection_whitelist?: NameType[] + before?: number + after?: number + }) { + const bodyParams = buildBodyParams(options) + + return this.client.call({ + path: `/atomicmarket/v1/stats/markets`, + method: 'POST', + params: bodyParams, + headers: {'Content-Type': 'application/json'}, + responseType: Market.GetStatsMarketsResponse, + }) + } + async get_config() { return this.client.call({ path: '/atomicmarket/v1/config', diff --git a/packages/atomicassets/test/api.ts b/packages/atomicassets/test/api.ts index 5c63ea8e..69e528e5 100644 --- a/packages/atomicassets/test/api.ts +++ b/packages/atomicassets/test/api.ts @@ -779,6 +779,21 @@ suite('atomicmarket', function () { assert.isNotEmpty(res.data) }) + test('get_stats_markets', async function () { + // The aggregate runs over every sale the indexer holds, so an unbounded + // call answers 408 on a mainnet-sized host. One collection and an after + // window keep it inside the server timeout. + const res = await atomicassets.atomicmarket.v1.get_stats_markets({ + symbol: 'WAX', + collection_whitelist: ['alien.worlds'], + after: 1750000000000, + }) + assert.instanceOf(res, Types.Market.GetStatsMarketsResponse) + assert.equal(res.success, true) + assert.isNotEmpty(res.data.symbol) + assert.isNotEmpty(res.data.results) + }) + test('get_config', async function () { const res = await atomicassets.atomicmarket.v1.get_config() assert.instanceOf(res, Types.Market.GetConfigResponse) diff --git a/packages/atomicassets/test/data/81dbd9169724d91c76934cae7a95530d2ffc4bf9.json b/packages/atomicassets/test/data/81dbd9169724d91c76934cae7a95530d2ffc4bf9.json new file mode 100644 index 00000000..c152541b --- /dev/null +++ b/packages/atomicassets/test/data/81dbd9169724d91c76934cae7a95530d2ffc4bf9.json @@ -0,0 +1,187 @@ +{ + "request": { + "path": "https://wax-atomic.alcor.exchange/atomicmarket/v1/stats/markets", + "params": { + "method": "POST", + "body": "{\"symbol\":\"WAX\",\"collection_whitelist\":\"alien.worlds\",\"after\":\"1750000000000\"}", + "headers": { + "Content-Type": "application/json" + } + } + }, + "status": 200, + "json": { + "success": true, + "data": { + "symbol": { + "token_symbol": "WAX", + "token_contract": "eosio.token", + "token_precision": 8 + }, + "results": [ + { + "market_contract": "atomicmarket", + "marketplace_name": null, + "sellers": "4896", + "buyers": "0", + "maker_volume": "1749594747693104", + "taker_volume": "0" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "", + "sellers": "0", + "buyers": "2106", + "maker_volume": "0", + "taker_volume": "1412670696427700" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "nft.hive", + "sellers": "549", + "buyers": "1082", + "maker_volume": "406043235255205", + "taker_volume": "553583840630693" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "market.nefty", + "sellers": "452", + "buyers": "395", + "maker_volume": "90515525429321", + "taker_volume": "269335609220115" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "f12keymarket", + "sellers": "9", + "buyers": "53", + "maker_volume": "541643330848", + "taker_volume": "9862644226728" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "atomicsniper", + "sellers": "4", + "buyers": "14", + "maker_volume": "2934474234377", + "taker_volume": "2933355536600" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "market.nemo", + "sellers": "0", + "buyers": "1", + "maker_volume": "0", + "taker_volume": "5470000000000" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "bazaarbazaar", + "sellers": "6", + "buyers": "1", + "maker_volume": "3189822290372", + "taker_volume": "8131096465" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "market.wax", + "sellers": "44", + "buyers": "0", + "maker_volume": "555352208064", + "taker_volume": "0" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "market.place", + "sellers": "1", + "buyers": "0", + "maker_volume": "300000000000", + "taker_volume": "0" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "waxplorercom", + "sellers": "23", + "buyers": "0", + "maker_volume": "244857289370", + "taker_volume": "0" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "earncashback", + "sellers": "0", + "buyers": "1", + "maker_volume": "0", + "taker_volume": "190773056817" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "alcor", + "sellers": "2", + "buyers": "0", + "maker_volume": "111153500000", + "taker_volume": "0" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "waxportal", + "sellers": "47", + "buyers": "1", + "maker_volume": "108116880000", + "taker_volume": "38000000" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "hoardiostore", + "sellers": "0", + "buyers": "1", + "maker_volume": "0", + "taker_volume": "75957262807" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "kipsmarket11", + "sellers": "1", + "buyers": "1", + "maker_volume": "992228134", + "taker_volume": "12700255878" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "wax.stash", + "sellers": "5", + "buyers": "0", + "maker_volume": "3378000000", + "taker_volume": "0" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "chainchampss", + "sellers": "1", + "buyers": "0", + "maker_volume": "424710000", + "taker_volume": "0" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "wombatmarket", + "sellers": "2", + "buyers": "0", + "maker_volume": "29200000", + "taker_volume": "0" + }, + { + "market_contract": "atomicmarket", + "marketplace_name": "rwax", + "sellers": "0", + "buyers": "1", + "maker_volume": "0", + "taker_volume": "6534992" + } + ] + }, + "query_time": 1787002602663 + }, + "text": "{\"success\":true,\"data\":{\"symbol\":{\"token_symbol\":\"WAX\",\"token_contract\":\"eosio.token\",\"token_precision\":8},\"results\":[{\"market_contract\":\"atomicmarket\",\"marketplace_name\":null,\"sellers\":\"4896\",\"buyers\":\"0\",\"maker_volume\":\"1749594747693104\",\"taker_volume\":\"0\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"\",\"sellers\":\"0\",\"buyers\":\"2106\",\"maker_volume\":\"0\",\"taker_volume\":\"1412670696427700\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"nft.hive\",\"sellers\":\"549\",\"buyers\":\"1082\",\"maker_volume\":\"406043235255205\",\"taker_volume\":\"553583840630693\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"market.nefty\",\"sellers\":\"452\",\"buyers\":\"395\",\"maker_volume\":\"90515525429321\",\"taker_volume\":\"269335609220115\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"f12keymarket\",\"sellers\":\"9\",\"buyers\":\"53\",\"maker_volume\":\"541643330848\",\"taker_volume\":\"9862644226728\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"atomicsniper\",\"sellers\":\"4\",\"buyers\":\"14\",\"maker_volume\":\"2934474234377\",\"taker_volume\":\"2933355536600\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"market.nemo\",\"sellers\":\"0\",\"buyers\":\"1\",\"maker_volume\":\"0\",\"taker_volume\":\"5470000000000\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"bazaarbazaar\",\"sellers\":\"6\",\"buyers\":\"1\",\"maker_volume\":\"3189822290372\",\"taker_volume\":\"8131096465\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"market.wax\",\"sellers\":\"44\",\"buyers\":\"0\",\"maker_volume\":\"555352208064\",\"taker_volume\":\"0\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"market.place\",\"sellers\":\"1\",\"buyers\":\"0\",\"maker_volume\":\"300000000000\",\"taker_volume\":\"0\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"waxplorercom\",\"sellers\":\"23\",\"buyers\":\"0\",\"maker_volume\":\"244857289370\",\"taker_volume\":\"0\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"earncashback\",\"sellers\":\"0\",\"buyers\":\"1\",\"maker_volume\":\"0\",\"taker_volume\":\"190773056817\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"alcor\",\"sellers\":\"2\",\"buyers\":\"0\",\"maker_volume\":\"111153500000\",\"taker_volume\":\"0\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"waxportal\",\"sellers\":\"47\",\"buyers\":\"1\",\"maker_volume\":\"108116880000\",\"taker_volume\":\"38000000\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"hoardiostore\",\"sellers\":\"0\",\"buyers\":\"1\",\"maker_volume\":\"0\",\"taker_volume\":\"75957262807\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"kipsmarket11\",\"sellers\":\"1\",\"buyers\":\"1\",\"maker_volume\":\"992228134\",\"taker_volume\":\"12700255878\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"wax.stash\",\"sellers\":\"5\",\"buyers\":\"0\",\"maker_volume\":\"3378000000\",\"taker_volume\":\"0\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"chainchampss\",\"sellers\":\"1\",\"buyers\":\"0\",\"maker_volume\":\"424710000\",\"taker_volume\":\"0\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"wombatmarket\",\"sellers\":\"2\",\"buyers\":\"0\",\"maker_volume\":\"29200000\",\"taker_volume\":\"0\"},{\"market_contract\":\"atomicmarket\",\"marketplace_name\":\"rwax\",\"sellers\":\"0\",\"buyers\":\"1\",\"maker_volume\":\"0\",\"taker_volume\":\"6534992\"}]},\"query_time\":1787002602663}" +} \ No newline at end of file diff --git a/packages/atomicassets/test/v2-api.ts b/packages/atomicassets/test/v2-api.ts index e9e65bd6..13edc783 100644 --- a/packages/atomicassets/test/v2-api.ts +++ b/packages/atomicassets/test/v2-api.ts @@ -296,4 +296,35 @@ suite('v2 API response fields', function () { assert.equal(rule.weight.toNumber(), 1) assert.isTrue(rule.recipients[0].recipient.equals('jacktestr125')) }) + + test('a marketplace stat decodes an unnamed marketplace and empty volumes', function () { + const unnamed = Types.Market.MarketplaceStat.from({ + market_contract: 'atomicmarket', + marketplace_name: null, + sellers: '4896', + buyers: '0', + maker_volume: '1749594747693104', + taker_volume: null, + }) + + // Null when no marketplace is stored, and the empty string when the + // stored value is the default marketplace name. Both mean the same + // thing to a client. + const blank = Types.Market.MarketplaceStat.from({ + market_contract: 'atomicmarket', + marketplace_name: '', + sellers: '0', + buyers: '2106', + maker_volume: null, + taker_volume: '1412670696427700', + }) + + assert.isNull(unnamed.marketplace_name) + assert.equal(unnamed.sellers.toNumber(), 4896) + assert.isUndefined(unnamed.taker_volume) + + assert.equal(blank.marketplace_name, '') + assert.isUndefined(blank.maker_volume) + assert.equal(blank.taker_volume.toNumber(), 1412670696427700) + }) }) From cae2b5e38748ab2508e4b64fc1837985d0a51d6e Mon Sep 17 00:00:00 2001 From: Rob Konsdorf Date: Mon, 17 Aug 2026 17:58:02 -0400 Subject: [PATCH 09/18] Document AtomicAssets v2 and AtomicMarket v2 in the README --- packages/atomicassets/README.md | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/packages/atomicassets/README.md b/packages/atomicassets/README.md index 6dcdaafc..ed67b30b 100644 --- a/packages/atomicassets/README.md +++ b/packages/atomicassets/README.md @@ -8,8 +8,79 @@ AtomicAssets library for Wharf yarn add @wharfkit/atomicassets ``` +## AtomicAssets v2 and AtomicMarket v2 + +The v2 contracts add fields that the API returns only from a v2 indexer. Every one of them is +declared optional, so a response from a v1 indexer decodes unchanged and the field reads back +empty. + +| Field | Returned on | Meaning | +| --- | --- | --- | +| `current_collection_fee` | sales, auctions, buyoffers, template buyoffers | The collection's `market_fee` as of the last indexed block. The nested `collection.market_fee` is the listing-time snapshot, and the two differ while the listing is open. | +| `types` | schemas | The media type descriptors authored through `setschematyp`, exactly as stored. | +| `mutable_data`, `data` | templates | The data written by `settempldata`, and the immutable and mutable data merged. | +| `deleted_at_block`, `deleted_at_time` | templates | Set once `deltemplate` removes the template. | +| `new_author_name`, `new_author_date` | collections | The pending author succession written by `createauswap`, and the date it may be accepted. | + +A schema also reports `mediatype` and `info` inside `format[]`, but the API merges the authored +descriptors with a name and type heuristic there, so a reader cannot tell a stored value from a +derived one. `types` reports the authored ones alone. This matters to a client that calls +`setschematyp`, which replaces the whole array: resubmitting a `format[]` value writes the +heuristic's guess to chain. An empty `types` array means the schema has no descriptors, and an +absent one means the response does not report them, which is what a schema nested inside an asset +or template answers. + +### Royalties + +The AtomicMarket v2 royalty tables and the settled payout ledger are read through the v1 market +client. The example needs an indexer of an AtomicMarket v2 chain, such as the WAX testnet one +shown below: + +```ts +import {APIClient, FetchProvider} from '@wharfkit/antelope' +import {AtomicAssetsAPIClient} from '@wharfkit/atomicassets' + +const api = new AtomicAssetsAPIClient( + new APIClient({provider: new FetchProvider('https://test.wax.api.atomicassets.io')}) +) +const market = api.atomicmarket.v1 + +// The royaltyconf, royaltytemp, and royaltyattr rows of one collection. +const config = await market.get_royalty_config('mycollection') +const templateRules = await market.get_royalty_template_rules('mycollection') +const attributeRules = await market.get_royalty_attribute_rules('mycollection') + +// The payouts an account has been paid, the total number of them, and the +// per-token totals. +const payouts = await market.get_royalty_payouts({recipient: ['myaccount'], limit: 100}) +const payoutCount = await market.get_royalty_payouts_count({recipient: ['myaccount']}) +const totals = await market.get_royalty_account('myaccount') +``` + +A payout carries `amount` in the token's smallest unit, `category` naming which rule paid +(`founders`, `template`, `attribute`, or `dust`), and a nullable `listing_id`, `asset_id`, +`template_id`, and `rule_id`. `get_royalty_account` answers one row per token symbol. + +A collection with no royalty configuration answers HTTP 416, which reaches the caller as an +`APIError` with `error.response.status === 416`. On an AtomicMarket v1 chain every collection +answers that way, and the payout routes answer an empty array. An indexer without the royalty +routes answers 404 on all six methods. + ## Running Tests ``` make test ``` + +The suite never contacts the network. `@wharfkit/mock-data` replays recorded responses from +`test/data`, where each filename is a hash of the full request URL and its parameters, so +responses from two hosts never collide. To re-record one fixture, name its test: + +``` +MOCK=overwrite make test grep='get_royalty_payouts' +``` + +`MOCK=overwrite make test` without a `grep=` re-records every fixture in `test/data` against the +live hosts. The royalty fixtures come from `V2_BASE_URL` in `test/config.ts`, an AtomicMarket v2 +indexer, because the default host indexes an AtomicMarket v1 chain where those routes answer 416 +or an empty array. From b30148fea909dafa930b3e23313a8937bb3cba3c Mon Sep 17 00:00:00 2001 From: Rob Konsdorf Date: Mon, 17 Aug 2026 18:36:45 -0400 Subject: [PATCH 10/18] Percent-encode caller-supplied path segments and refuse dot segments Every endpoint interpolated its NameType, UInt64Type, or string argument raw into the request path, so a value carrying a slash, a question mark, or a hash escaped its own segment and rewrote the request target, and a dot segment survived percent-encoding and was collapsed by the URL parser onto a sibling route of the same origin. One helper now encodes each segment where the path is assembled and throws on an empty, missing, or dot value, which is never an id or a name. The recorded fixtures replay unchanged, since every value they carry is already unreserved. --- .../atomicassets/src/endpoints/assets/v1.ts | 54 ++++++++++++------- .../atomicassets/src/endpoints/market/v1.ts | 48 ++++++++--------- .../atomicassets/src/endpoints/market/v2.ts | 4 +- .../atomicassets/src/endpoints/tools/v1.ts | 6 +-- packages/atomicassets/src/endpoints/utils.ts | 21 ++++++++ packages/atomicassets/test/utils.ts | 53 ++++++++++++++++++ 6 files changed, 137 insertions(+), 49 deletions(-) create mode 100644 packages/atomicassets/test/utils.ts diff --git a/packages/atomicassets/src/endpoints/assets/v1.ts b/packages/atomicassets/src/endpoints/assets/v1.ts index c89b9963..3524433f 100644 --- a/packages/atomicassets/src/endpoints/assets/v1.ts +++ b/packages/atomicassets/src/endpoints/assets/v1.ts @@ -4,7 +4,7 @@ import type {ActionNames as ActionType} from '../../contracts/atomicassets' import {CountResponseStruct} from '../../types' import * as Assets from './types' import type {OfferState} from '../../types' -import {buildBodyParams} from '../utils' +import {buildBodyParams, pathSegment} from '../utils' export interface GetAssetsOptions { collection_name?: NameType[] @@ -233,7 +233,7 @@ export class AssetsV1APIClient { async get_asset(asset_id: UInt64Type) { return this.client.call({ - path: `/atomicassets/v1/assets/${asset_id}`, + path: `/atomicassets/v1/assets/${pathSegment(asset_id)}`, method: 'GET', responseType: Assets.GetAssetResponse, }) @@ -241,7 +241,7 @@ export class AssetsV1APIClient { async get_asset_stats(asset_id: UInt64Type) { return this.client.call({ - path: `/atomicassets/v1/assets/${asset_id}/stats`, + path: `/atomicassets/v1/assets/${pathSegment(asset_id)}/stats`, method: 'GET', responseType: Assets.GetAssetStatsResponse, }) @@ -260,7 +260,7 @@ export class AssetsV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicassets/v1/assets/${asset_id}/logs`, + path: `/atomicassets/v1/assets/${pathSegment(asset_id)}/logs`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -294,7 +294,7 @@ export class AssetsV1APIClient { async get_collection(collection_name: NameType) { return this.client.call({ - path: `/atomicassets/v1/collections/${collection_name}`, + path: `/atomicassets/v1/collections/${pathSegment(collection_name)}`, method: 'GET', responseType: Assets.GetCollectionResponse, }) @@ -302,7 +302,7 @@ export class AssetsV1APIClient { async get_collection_stats(collection_name: NameType) { return this.client.call({ - path: `/atomicassets/v1/collections/${collection_name}/stats`, + path: `/atomicassets/v1/collections/${pathSegment(collection_name)}/stats`, method: 'GET', responseType: Assets.GetCollectionStatsResponse, }) @@ -310,7 +310,7 @@ export class AssetsV1APIClient { async get_collection_schemas(collection_name: NameType) { return this.client.call({ - path: `/atomicassets/v1/collections/${collection_name}/schemas`, + path: `/atomicassets/v1/collections/${pathSegment(collection_name)}/schemas`, method: 'GET', responseType: Assets.GetCollectionSchemasResponse, }) @@ -329,7 +329,7 @@ export class AssetsV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicassets/v1/collections/${collection_name}/logs`, + path: `/atomicassets/v1/collections/${pathSegment(collection_name)}/logs`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -363,7 +363,9 @@ export class AssetsV1APIClient { async get_schema(collection_name: NameType, schema_name: NameType) { return this.client.call({ - path: `/atomicassets/v1/schemas/${collection_name}/${schema_name}`, + path: `/atomicassets/v1/schemas/${pathSegment(collection_name)}/${pathSegment( + schema_name + )}`, method: 'GET', responseType: Assets.GetSchemaResponse, }) @@ -371,7 +373,9 @@ export class AssetsV1APIClient { async get_schema_stats(collection_name: NameType, schema_name: NameType) { return this.client.call({ - path: `/atomicassets/v1/schemas/${collection_name}/${schema_name}/stats`, + path: `/atomicassets/v1/schemas/${pathSegment(collection_name)}/${pathSegment( + schema_name + )}/stats`, method: 'GET', responseType: Assets.GetSchemaStatsResponse, }) @@ -391,7 +395,9 @@ export class AssetsV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicassets/v1/schemas/${collection_name}/${schema_name}/logs`, + path: `/atomicassets/v1/schemas/${pathSegment(collection_name)}/${pathSegment( + schema_name + )}/logs`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -428,7 +434,9 @@ export class AssetsV1APIClient { async get_template(collection_name: NameType, template_id: Int32Type) { return this.client.call({ - path: `/atomicassets/v1/templates/${collection_name}/${template_id}`, + path: `/atomicassets/v1/templates/${pathSegment(collection_name)}/${pathSegment( + template_id + )}`, method: 'GET', responseType: Assets.GetTemplateResponse, }) @@ -442,8 +450,10 @@ export class AssetsV1APIClient { ) { const path = typeof template_id === 'undefined' - ? `/atomicassets/v1/templates/${collection_name_or_template_id}/stats` - : `/atomicassets/v1/templates/${collection_name_or_template_id}/${template_id}/stats` + ? `/atomicassets/v1/templates/${pathSegment(collection_name_or_template_id)}/stats` + : `/atomicassets/v1/templates/${pathSegment( + collection_name_or_template_id + )}/${pathSegment(template_id)}/stats` return this.client.call({ path, @@ -466,7 +476,9 @@ export class AssetsV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicassets/v1/templates/${collection_name}/${template_id}/logs`, + path: `/atomicassets/v1/templates/${pathSegment(collection_name)}/${pathSegment( + template_id + )}/logs`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -500,7 +512,7 @@ export class AssetsV1APIClient { async get_offer(offer_id: UInt64Type) { return this.client.call({ - path: `/atomicassets/v1/offers/${offer_id}`, + path: `/atomicassets/v1/offers/${pathSegment(offer_id)}`, method: 'GET', responseType: Assets.GetOfferResponse, }) @@ -519,7 +531,7 @@ export class AssetsV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicassets/v1/offers/${offer_id}/logs`, + path: `/atomicassets/v1/offers/${pathSegment(offer_id)}/logs`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -586,7 +598,7 @@ export class AssetsV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicassets/v1/accounts/${account}`, + path: `/atomicassets/v1/accounts/${pathSegment(account)}`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -596,7 +608,9 @@ export class AssetsV1APIClient { async get_account_template_schema_count(account: NameType, collection_name: NameType) { return this.client.call({ - path: `/atomicassets/v1/accounts/${account}/${collection_name}`, + path: `/atomicassets/v1/accounts/${pathSegment(account)}/${pathSegment( + collection_name + )}`, method: 'GET', responseType: Assets.GetAccountTemplateSchemaCountResponse, }) @@ -625,7 +639,7 @@ export class AssetsV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicassets/v1/burns/${account}`, + path: `/atomicassets/v1/burns/${pathSegment(account)}`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, diff --git a/packages/atomicassets/src/endpoints/market/v1.ts b/packages/atomicassets/src/endpoints/market/v1.ts index fbc7f966..740fd0ce 100644 --- a/packages/atomicassets/src/endpoints/market/v1.ts +++ b/packages/atomicassets/src/endpoints/market/v1.ts @@ -5,7 +5,7 @@ import {CountResponseStruct} from '../../types' import * as Market from './types' import type {ActionNames as SActionType} from '../../contracts/atomicassets' import type {ActionNames as MActionType} from '../../contracts/atomicmarket' -import {buildBodyParams} from '../utils' +import {buildBodyParams, pathSegment} from '../utils' export interface GetAssetsOptions { collection_name?: NameType[] @@ -250,7 +250,7 @@ export class MarketV1APIClient { async get_asset(asset_id: UInt64Type) { return this.client.call({ - path: `/atomicmarket/v1/assets/${asset_id}`, + path: `/atomicmarket/v1/assets/${pathSegment(asset_id)}`, method: 'GET', responseType: Market.GetAssetResponse, }) @@ -258,7 +258,7 @@ export class MarketV1APIClient { async get_asset_stats(asset_id: UInt64Type) { return this.client.call({ - path: `/atomicmarket/v1/assets/${asset_id}/stats`, + path: `/atomicmarket/v1/assets/${pathSegment(asset_id)}/stats`, method: 'GET', responseType: Market.GetAssetStatsResponse, }) @@ -277,7 +277,7 @@ export class MarketV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v1/assets/${asset_id}/logs`, + path: `/atomicmarket/v1/assets/${pathSegment(asset_id)}/logs`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -297,7 +297,7 @@ export class MarketV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v1/assets/${asset_id}/sales`, + path: `/atomicmarket/v1/assets/${pathSegment(asset_id)}/sales`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -351,7 +351,7 @@ export class MarketV1APIClient { async get_offer(offer_id: UInt64Type) { return this.client.call({ - path: `/atomicmarket/v1/offers/${offer_id}`, + path: `/atomicmarket/v1/offers/${pathSegment(offer_id)}`, method: 'GET', responseType: Market.GetOfferResponse, }) @@ -370,7 +370,7 @@ export class MarketV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v1/offers/${offer_id}/logs`, + path: `/atomicmarket/v1/offers/${pathSegment(offer_id)}/logs`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -415,7 +415,7 @@ export class MarketV1APIClient { async get_sale(sale_id: UInt64Type) { return this.client.call({ - path: `/atomicmarket/v1/sales/${sale_id}`, + path: `/atomicmarket/v1/sales/${pathSegment(sale_id)}`, method: 'GET', responseType: Market.GetSaleResponse, }) @@ -434,7 +434,7 @@ export class MarketV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v1/sales/${sale_id}/logs`, + path: `/atomicmarket/v1/sales/${pathSegment(sale_id)}/logs`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -510,7 +510,7 @@ export class MarketV1APIClient { async get_auction(auction_id: UInt64Type) { return this.client.call({ - path: `/atomicmarket/v1/auctions/${auction_id}`, + path: `/atomicmarket/v1/auctions/${pathSegment(auction_id)}`, method: 'GET', responseType: Market.GetAuctionResponse, }) @@ -529,7 +529,7 @@ export class MarketV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v1/auctions/${auction_id}/logs`, + path: `/atomicmarket/v1/auctions/${pathSegment(auction_id)}/logs`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -566,7 +566,7 @@ export class MarketV1APIClient { async get_buyoffer(buyoffer_id: UInt64Type) { return this.client.call({ - path: `/atomicmarket/v1/buyoffers/${buyoffer_id}`, + path: `/atomicmarket/v1/buyoffers/${pathSegment(buyoffer_id)}`, method: 'GET', responseType: Market.GetBuyofferResponse, }) @@ -585,7 +585,7 @@ export class MarketV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v1/buyoffers/${buyoffer_id}/logs`, + path: `/atomicmarket/v1/buyoffers/${pathSegment(buyoffer_id)}/logs`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -625,7 +625,7 @@ export class MarketV1APIClient { async get_template_buyoffer(buyoffer_id: UInt64Type) { return this.client.call({ - path: `/atomicmarket/v1/template_buyoffers/${buyoffer_id}`, + path: `/atomicmarket/v1/template_buyoffers/${pathSegment(buyoffer_id)}`, method: 'GET', responseType: Market.GetTemplateBuyofferResponse, }) @@ -644,7 +644,7 @@ export class MarketV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v1/template_buyoffers/${buyoffer_id}/logs`, + path: `/atomicmarket/v1/template_buyoffers/${pathSegment(buyoffer_id)}/logs`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -662,7 +662,7 @@ export class MarketV1APIClient { async get_marketplace(marketplace_name: string) { return this.client.call({ - path: `/atomicmarket/v1/marketplaces/${marketplace_name}`, + path: `/atomicmarket/v1/marketplaces/${pathSegment(marketplace_name)}`, method: 'GET', responseType: Market.GetMarketplaceResponse, }) @@ -789,7 +789,7 @@ export class MarketV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v1/prices/inventory/${account}`, + path: `/atomicmarket/v1/prices/inventory/${pathSegment(account)}`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -833,7 +833,7 @@ export class MarketV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v1/stats/collections/${collection_name}`, + path: `/atomicmarket/v1/stats/collections/${pathSegment(collection_name)}`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -875,7 +875,7 @@ export class MarketV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v1/stats/accounts/${account}`, + path: `/atomicmarket/v1/stats/accounts/${pathSegment(account)}`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -898,7 +898,7 @@ export class MarketV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v1/stats/schemas/${collection_name}`, + path: `/atomicmarket/v1/stats/schemas/${pathSegment(collection_name)}`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -1009,7 +1009,7 @@ export class MarketV1APIClient { */ async get_royalty_config(collection_name: NameType) { return this.client.call({ - path: `/atomicmarket/v1/royalties/${collection_name}`, + path: `/atomicmarket/v1/royalties/${pathSegment(collection_name)}`, method: 'GET', responseType: Market.GetRoyaltyConfigResponse, }) @@ -1026,7 +1026,7 @@ export class MarketV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v1/royalties/${collection_name}/templates`, + path: `/atomicmarket/v1/royalties/${pathSegment(collection_name)}/templates`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -1046,7 +1046,7 @@ export class MarketV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v1/royalties/${collection_name}/attributes`, + path: `/atomicmarket/v1/royalties/${pathSegment(collection_name)}/attributes`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, @@ -1098,7 +1098,7 @@ export class MarketV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v1/royalties/accounts/${account}`, + path: `/atomicmarket/v1/royalties/accounts/${pathSegment(account)}`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, diff --git a/packages/atomicassets/src/endpoints/market/v2.ts b/packages/atomicassets/src/endpoints/market/v2.ts index eabadefa..49c3df25 100644 --- a/packages/atomicassets/src/endpoints/market/v2.ts +++ b/packages/atomicassets/src/endpoints/market/v2.ts @@ -3,7 +3,7 @@ import type {Float64Type, Int32Type, NameType, UInt64Type} from '@wharfkit/antel import type {SaleState} from '../../types' import {CountResponseStruct} from '../../types' import * as Market from './types' -import {buildBodyParams} from '../utils' +import {buildBodyParams, pathSegment} from '../utils' export interface GetSalesOptions { state?: SaleState[] @@ -96,7 +96,7 @@ export class MarketV2APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomicmarket/v2/stats/schemas/${collection_name}`, + path: `/atomicmarket/v2/stats/schemas/${pathSegment(collection_name)}`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, diff --git a/packages/atomicassets/src/endpoints/tools/v1.ts b/packages/atomicassets/src/endpoints/tools/v1.ts index a36c1f2e..412d81f3 100644 --- a/packages/atomicassets/src/endpoints/tools/v1.ts +++ b/packages/atomicassets/src/endpoints/tools/v1.ts @@ -4,7 +4,7 @@ import type {LinkState} from '../../types' import {CountResponseStruct} from '../../types' import * as Tools from './types' import type {ActionNames as ActionType} from '../../contracts/atomictoolsx' -import {buildBodyParams} from '../utils' +import {buildBodyParams, pathSegment} from '../utils' export interface GetLinksOptions { creator?: NameType[] @@ -54,7 +54,7 @@ export class ToolsV1APIClient { async get_link(link_id: UInt64Type) { return this.client.call({ - path: `/atomictools/v1/links/${link_id}`, + path: `/atomictools/v1/links/${pathSegment(link_id)}`, method: 'GET', responseType: Tools.GetLinkResponse, }) @@ -73,7 +73,7 @@ export class ToolsV1APIClient { const bodyParams = buildBodyParams(options) return this.client.call({ - path: `/atomictools/v1/links/${link_id}/logs`, + path: `/atomictools/v1/links/${pathSegment(link_id)}/logs`, method: 'POST', params: bodyParams, headers: {'Content-Type': 'application/json'}, diff --git a/packages/atomicassets/src/endpoints/utils.ts b/packages/atomicassets/src/endpoints/utils.ts index 48cd95cc..d92111da 100644 --- a/packages/atomicassets/src/endpoints/utils.ts +++ b/packages/atomicassets/src/endpoints/utils.ts @@ -41,3 +41,24 @@ export function serializeQueryParams(params?: {[key: string]: any}): {[key: stri return result } + +/** + * Encode a value for use as one path segment. + * + * An empty value or a dot segment would rewrite the request path, so both are rejected, as is a + * missing value, which would otherwise travel as the literal segment 'undefined'. A value that + * equals a sibling route literal such as '_count' is a valid segment and is not rejected here. + */ +export function pathSegment(value: unknown): string { + if (value === null || value === undefined) { + throw new Error('Invalid path segment: a value is required') + } + const segment = String(value) + if (segment === '' || segment === '.' || segment === '..') { + throw new Error( + `Invalid path segment '${segment}': an empty or dot segment rewrites the request path` + ) + } + + return encodeURIComponent(segment) +} diff --git a/packages/atomicassets/test/utils.ts b/packages/atomicassets/test/utils.ts new file mode 100644 index 00000000..e7e4c583 --- /dev/null +++ b/packages/atomicassets/test/utils.ts @@ -0,0 +1,53 @@ +import {assert} from 'chai' +import {APIClient, FetchProvider, Name, UInt64} from '@wharfkit/antelope' +import {mockFetch} from '@wharfkit/mock-data' +import {BASE_URL, TIMEOUT, SLOW_THRESHOLD} from './config' + +import {AtomicAssetsAPIClient} from '$lib' +import {pathSegment} from '../src/endpoints/utils' + +// Setup the API +const atomicassets = new AtomicAssetsAPIClient( + new APIClient({ + provider: new FetchProvider(BASE_URL, {fetch: mockFetch}), + }) +) + +suite('utils', function () { + this.slow(SLOW_THRESHOLD) + this.timeout(TIMEOUT) + + test('pathSegment encodes characters that rewrite the path', function () { + assert.equal(pathSegment('alien/worlds'), 'alien%2Fworlds') + assert.equal(pathSegment('alien?worlds'), 'alien%3Fworlds') + assert.equal(pathSegment('alien#worlds'), 'alien%23worlds') + assert.equal(pathSegment('alien worlds'), 'alien%20worlds') + assert.equal(pathSegment('alien&worlds'), 'alien%26worlds') + }) + + test('pathSegment returns the text of a Name and a UInt64', function () { + assert.equal(pathSegment(Name.from('alice')), 'alice') + assert.equal(pathSegment(UInt64.from(5)), '5') + }) + + test('pathSegment rejects an empty value and the dot segments', function () { + assert.throws(() => pathSegment(''), /Invalid path segment/) + assert.throws(() => pathSegment('.'), /Invalid path segment/) + assert.throws(() => pathSegment('..'), /Invalid path segment/) + assert.throws(() => pathSegment(null), /a value is required/) + assert.throws(() => pathSegment(undefined), /a value is required/) + }) + + test('get_asset rejects a dot segment before it requests anything', async function () { + let error: unknown + + try { + await atomicassets.atomicassets.v1.get_asset('..') + } catch (caught) { + error = caught + } + + assert.instanceOf(error, Error) + assert.match((error as Error).message, /^Invalid path segment '\.\.'/) + }) +}) From d80af8d36e517732fc01254c78d52668e3d5302d Mon Sep 17 00:00:00 2001 From: aaroncox Date: Thu, 24 Sep 2026 14:38:59 -0700 Subject: [PATCH 11/18] Format the session tests --- packages/session/test/tests/kit.ts | 6 +----- packages/session/test/tests/session.ts | 7 +------ 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/session/test/tests/kit.ts b/packages/session/test/tests/kit.ts index 6605e2e5..e5fc5512 100644 --- a/packages/session/test/tests/kit.ts +++ b/packages/session/test/tests/kit.ts @@ -1,9 +1,5 @@ import {assert} from 'chai' -import { - Checksum256, - PermissionLevel, - TimePointSec, -} from '@wharfkit/antelope' +import {Checksum256, PermissionLevel, TimePointSec} from '@wharfkit/antelope' import {WalletPluginPrivateKey} from '@wharfkit/wallet-plugin-privatekey' import { diff --git a/packages/session/test/tests/session.ts b/packages/session/test/tests/session.ts index 41d3ca11..1afd1667 100644 --- a/packages/session/test/tests/session.ts +++ b/packages/session/test/tests/session.ts @@ -1,11 +1,6 @@ import {assert} from 'chai' -import SessionKit, { - BaseTransactPlugin, - ChainDefinition, - Session, - SessionOptions, -} from '$lib' +import SessionKit, {BaseTransactPlugin, ChainDefinition, Session, SessionOptions} from '$lib' import { ABI, ABIDef, From 08b703e050de80a6fc033849baf39ef20bd29b80 Mon Sep 17 00:00:00 2001 From: aaroncox Date: Thu, 24 Sep 2026 14:38:59 -0700 Subject: [PATCH 12/18] Send a scope bound of zero --- .../contract/src/contract/scope-cursor.ts | 8 ++++-- packages/contract/test/tests/table.ts | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/contract/src/contract/scope-cursor.ts b/packages/contract/src/contract/scope-cursor.ts index eace9bce..17d47293 100644 --- a/packages/contract/src/contract/scope-cursor.ts +++ b/packages/contract/src/contract/scope-cursor.ts @@ -37,8 +37,12 @@ export class TableScopeCursor extends TableCursor { code: this.params.code, table: this.params.table, limit, - lower_bound: lower_bound ? String(lower_bound) : undefined, - upper_bound: this.params.upper_bound ? String(this.params.upper_bound) : undefined, + lower_bound: + lower_bound === undefined || lower_bound === null ? undefined : String(lower_bound), + upper_bound: + this.params.upper_bound === undefined || this.params.upper_bound === null + ? undefined + : String(this.params.upper_bound), } const result = await this.client!.v1.chain.get_table_by_scope(query) diff --git a/packages/contract/test/tests/table.ts b/packages/contract/test/tests/table.ts index b57ba2c1..069fef19 100644 --- a/packages/contract/test/tests/table.ts +++ b/packages/contract/test/tests/table.ts @@ -729,5 +729,32 @@ suite('Table', () => { ) }) }) + + test('should send a bound of zero', async function () { + const queries: any[] = [] + const client = new APIClient({ + provider: { + call: async ({params}: any) => { + queries.push(JSON.parse(JSON.stringify(params))) + return { + status: 200, + headers: {}, + text: '{"rows":[],"more":""}', + json: {rows: [], more: ''}, + } + }, + }, + }) + const cursor = new TableScopeCursor({ + abi: msig.abi, + client, + params: {code: 'eosio.msig', table: 'proposal', lower_bound: 0, upper_bound: 0}, + }) + + await cursor.next() + + assert.equal(queries[0].lower_bound, '0') + assert.equal(queries[0].upper_bound, '0') + }) }) }) From 4dcf7bacc516c379128c276921436c2a79f54abf Mon Sep 17 00:00:00 2001 From: aaroncox Date: Thu, 24 Sep 2026 14:38:59 -0700 Subject: [PATCH 13/18] Omit absent binary extensions when decoding --- packages/antelope/src/serializer/decoder.ts | 13 +++- packages/antelope/test/serializer.ts | 75 +++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/packages/antelope/src/serializer/decoder.ts b/packages/antelope/src/serializer/decoder.ts index bfbf6e16..50820f66 100644 --- a/packages/antelope/src/serializer/decoder.ts +++ b/packages/antelope/src/serializer/decoder.ts @@ -198,6 +198,9 @@ function decodeBinary(type: ABI.ResolvedType, decoder: ABIDecoder, ctx: Decoding } const rv: any = {} for (const field of fields) { + if (field.type.isExtension && !ctx.strictExtensions && !decoder.canRead()) { + continue + } ctx.codingPath.push({field: field.name, type: field.type}) rv[field.name] = decodeBinary(field.type, decoder, ctx) ctx.codingPath.pop() @@ -283,8 +286,16 @@ function decodeObject(value: any, type: ABI.ResolvedType, ctx: DecodingContext): } const struct: any = {} for (const field of fields) { + const fieldValue = value[field.name] + if ( + field.type.isExtension && + !ctx.strictExtensions && + (fieldValue === undefined || (fieldValue === null && !field.type.isOptional)) + ) { + continue + } ctx.codingPath.push({field: field.name, type: field.type}) - struct[field.name] = decodeObject(value[field.name], field.type, ctx) + struct[field.name] = decodeObject(fieldValue, field.type, ctx) ctx.codingPath.pop() } if (abiType) { diff --git a/packages/antelope/test/serializer.ts b/packages/antelope/test/serializer.ts index 7f1c0302..8e00cb27 100644 --- a/packages/antelope/test/serializer.ts +++ b/packages/antelope/test/serializer.ts @@ -1222,6 +1222,81 @@ suite('serializer', function () { assert.notProperty(Serializer.objectify(res1), 'proposal_hash') }) + test('absent binary extensions are omitted', function () { + const abi = ABI.from({ + structs: [ + { + name: 'permission_level', + base: '', + fields: [ + {name: 'actor', type: 'name'}, + {name: 'permission', type: 'name'}, + ], + }, + { + name: 'approve', + base: '', + fields: [ + {name: 'proposer', type: 'name'}, + {name: 'proposal_name', type: 'name'}, + {name: 'level', type: 'permission_level'}, + {name: 'proposal_hash', type: 'checksum256$'}, + ], + }, + { + name: 'optionals', + base: '', + fields: [ + {name: 'name', type: 'name'}, + {name: 'maybe', type: 'checksum256?'}, + {name: 'maybe_ext', type: 'bool?$'}, + ], + }, + ], + }) + const action = { + proposer: 'foo', + proposal_name: 'bar', + level: {actor: 'baz', permission: 'active'}, + } + const data = Serializer.encode({object: action, abi, type: 'approve'}) + const fromBinary = Serializer.decode({data, abi, type: 'approve'}) + assert.notProperty(fromBinary, 'proposal_hash') + assert.deepEqual(Serializer.objectify(fromBinary), action) + assert.equal( + Serializer.encode({object: fromBinary, abi, type: 'approve'}).hexString, + data.hexString + ) + + const fromObject = Serializer.decode({object: action, abi, type: 'approve'}) + assert.notProperty(fromObject, 'proposal_hash') + const fromNull = Serializer.decode({ + object: {...action, proposal_hash: null}, + abi, + type: 'approve', + }) + assert.notProperty(fromNull, 'proposal_hash') + + const optionals = Serializer.decode({ + data: Serializer.encode({ + object: {name: 'foo', maybe: null, maybe_ext: null}, + abi, + type: 'optionals', + }), + abi, + type: 'optionals', + }) as any + assert.strictEqual(optionals.maybe, null) + assert.strictEqual(optionals.maybe_ext, null) + const optionalsAbsent = Serializer.decode({ + data: '0000000000000000' + '00', + abi, + type: 'optionals', + }) + assert.strictEqual((optionalsAbsent as any).maybe, null) + assert.notProperty(optionalsAbsent, 'maybe_ext') + }) + test('action_results', function () { const raw = { ____comment: 'This file was generated with eosio-abigen. DO NOT EDIT ', From b59984ac63e2a4f6c6675494c357386500bf755e Mon Sep 17 00:00:00 2001 From: aaroncox Date: Thu, 24 Sep 2026 14:49:52 -0700 Subject: [PATCH 14/18] Omit absent binary extensions from structs --- packages/antelope/src/chain/struct.ts | 15 +++++++++++---- packages/antelope/test/serializer.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/packages/antelope/src/chain/struct.ts b/packages/antelope/src/chain/struct.ts index d0ec5d92..67ea80e4 100644 --- a/packages/antelope/src/chain/struct.ts +++ b/packages/antelope/src/chain/struct.ts @@ -14,6 +14,14 @@ export interface StructConstructor extends ABISerializableConstructor { structFields: ABIField[] } +function fieldModifiers(field: ABIField) { + if (typeof field.type === 'string') { + const resolved = new ABI.ResolvedType(field.type) + return {isOptional: resolved.isOptional, isExtension: resolved.isExtension} + } + return {isOptional: !!field.optional, isExtension: !!field.extension} +} + export class Struct implements ABISerializableObject { static abiName = '__struct' static abiFields: ABIField[] @@ -50,12 +58,10 @@ export class Struct implements ABISerializableObject { constructor(object: any) { const self = this.constructor as typeof Struct for (const field of self.structFields) { - const isOptional = - typeof field.type === 'string' - ? new ABI.ResolvedType(String(field.type)).isOptional - : field.optional + const {isOptional, isExtension} = fieldModifiers(field) const value = object[field.name] if (isOptional && !value) continue + if (isExtension && value === undefined) continue this[field.name] = value } } @@ -84,6 +90,7 @@ export class Struct implements ABISerializableObject { const rv: any = {} for (const field of self.structFields) { if (field.optional && !this[field.name]) continue + if (this[field.name] === undefined && fieldModifiers(field).isExtension) continue rv[field.name] = this[field.name] } return rv diff --git a/packages/antelope/test/serializer.ts b/packages/antelope/test/serializer.ts index 8e00cb27..81a07202 100644 --- a/packages/antelope/test/serializer.ts +++ b/packages/antelope/test/serializer.ts @@ -1297,6 +1297,33 @@ suite('serializer', function () { assert.notProperty(optionalsAbsent, 'maybe_ext') }) + test('absent binary extensions are omitted from structs', function () { + @Struct.type('approve_ext') + class ApproveExt extends Struct { + @Struct.field(Name) declare proposer: Name + @Struct.field('checksum256$') declare proposal_hash?: Checksum256 + @Struct.field(Checksum256, {extension: true}) declare other_hash?: Checksum256 + } + const data = Serializer.encode({object: ApproveExt.from({proposer: 'foo'})}) + const decoded = Serializer.decode({data, type: ApproveExt}) + assert.notProperty(decoded, 'proposal_hash') + assert.notProperty(decoded, 'other_hash') + assert.deepEqual(Serializer.objectify(decoded), {proposer: 'foo'}) + assert.equal(Serializer.encode({object: decoded}).hexString, data.hexString) + + const hash = '0000000000000000000000000000000000000000000000000000000000000001' + const full = ApproveExt.from({proposer: 'foo', proposal_hash: hash, other_hash: hash}) + const fullDecoded = Serializer.decode({ + data: Serializer.encode({object: full}), + type: ApproveExt, + }) + assert.deepEqual(Serializer.objectify(fullDecoded), { + proposer: 'foo', + proposal_hash: hash, + other_hash: hash, + }) + }) + test('action_results', function () { const raw = { ____comment: 'This file was generated with eosio-abigen. DO NOT EDIT ', From 6eeeefc6fc70ff1a1f33ce780caf38d5e2304e91 Mon Sep 17 00:00:00 2001 From: aaroncox Date: Thu, 24 Sep 2026 17:02:53 -0700 Subject: [PATCH 15/18] Seal messages with the stripped shared secret --- packages/sealed-messages/src/index.ts | 6 +-- packages/sealed-messages/test/tests/utils.ts | 44 +++++++++++++++++++- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/packages/sealed-messages/src/index.ts b/packages/sealed-messages/src/index.ts index 41d23beb..db78a0a5 100644 --- a/packages/sealed-messages/src/index.ts +++ b/packages/sealed-messages/src/index.ts @@ -58,7 +58,7 @@ export function sealMessage( publicKey: PublicKey, nonce: UInt64 ): Bytes { - const secret = privateKey.sharedSecret(publicKey) + const secret = privateKey.sharedSecret(publicKey, {legacy: true}) const iv = createIV(nonce, secret) const symmetricKey = createSymmetricKey(secret, nonce) return encryptMessage(iv, symmetricKey, message) @@ -73,7 +73,7 @@ export function sealedMessagePayload( if (!nonce) { nonce = UInt64.random() } - const secret = privateKey.sharedSecret(publicKey) + const secret = privateKey.sharedSecret(publicKey, {legacy: true}) const iv = createIV(nonce, secret) const symmetricKey = createSymmetricKey(secret, nonce) const ciphertext = encryptMessage(iv, symmetricKey, message) @@ -102,7 +102,7 @@ export function unsealMessage( publicKey: PublicKey, nonce: UInt64 ): string { - const secret = privateKey.sharedSecret(publicKey) + const secret = privateKey.sharedSecret(publicKey, {legacy: true}) const iv = createIV(nonce, secret) const symmetricKey = createSymmetricKey(secret, nonce) const decryptedMessage = decryptMessage(iv, symmetricKey, message) diff --git a/packages/sealed-messages/test/tests/utils.ts b/packages/sealed-messages/test/tests/utils.ts index 442d539b..4181814e 100644 --- a/packages/sealed-messages/test/tests/utils.ts +++ b/packages/sealed-messages/test/tests/utils.ts @@ -1,6 +1,13 @@ import {assert} from 'chai' -import {PrivateKey, UInt64} from '@wharfkit/antelope' -import {sealMessage, unsealMessage} from '../../src/index' +import {Checksum256, PrivateKey, UInt64} from '@wharfkit/antelope' +import { + createIV, + createSymmetricKey, + encryptMessage, + sealedMessagePayload, + sealMessage, + unsealMessage, +} from '../../src/index' suite('utils', function () { test('sealMessage', function () { @@ -26,4 +33,37 @@ suite('utils', function () { const unsealedMessage = unsealMessage(sealedMessage, to, from.toPublic(), nonce) assert.equal(unsealedMessage, message) }) + + suite('shared secret with a leading zero byte', function () { + // ECDH x-coordinate of this pair starts with 0x00 + const from = PrivateKey.from('5KZJED2ubZVe7L5Xw7u7sb6pktpaoT69rxGRjQT6PKrDRzp9Gu8') + const to = PrivateKey.from('5Ke897mHoFzE1mTSk4j8KCxrsSRyzQZCqYPGv1rahh39XVpqcTL') + const nonce = UInt64.from(1234567890) + const message = 'Hello, World!' + + test('seals with the stripped derivation', function () { + const payload = sealedMessagePayload(message, from, to.toPublic(), nonce) + assert.equal(payload.ciphertext.hexString, '51686e0c16836c4d30b8bb0938e4cfe5') + assert.equal(Number(payload.checksum), 3865664645) + assert.equal(String(payload.from), String(from.toPublic())) + }) + + test('differs from the 32-byte derivation', function () { + const secret = from.sharedSecret(to.toPublic()) + const iv = createIV(nonce, secret) + const ciphertext = encryptMessage(iv, createSymmetricKey(secret, nonce), message) + const checksum = new DataView(Checksum256.hash(iv.array).array.buffer).getUint32( + 0, + true + ) + const payload = sealedMessagePayload(message, from, to.toPublic(), nonce) + assert.notEqual(payload.ciphertext.hexString, ciphertext.hexString) + assert.notEqual(Number(payload.checksum), checksum) + }) + + test('unseals the stripped derivation', function () { + const payload = sealedMessagePayload(message, from, to.toPublic(), nonce) + assert.equal(unsealMessage(payload.ciphertext, to, payload.from, nonce), message) + }) + }) }) From cded60f66b46b84b98547d94856b6ab8915b7699 Mon Sep 17 00:00:00 2001 From: aaroncox Date: Thu, 24 Sep 2026 17:21:46 -0700 Subject: [PATCH 16/18] Link GitHub releases to the release notes index --- scripts/release.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/release.ts b/scripts/release.ts index 384e7c54..45b3255c 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -456,10 +456,7 @@ function publish(force: boolean) { if (semver.prerelease(version)) { releaseArgs.push('--prerelease') } else { - releaseArgs.push( - '--notes', - `Release notes: https://wharfkit.com/docs/releases/${version.replace(/\./g, '-')}` - ) + releaseArgs.push('--notes', 'Release notes: https://wharfkit.com/docs/releases') } if (trySh('gh', ['release', 'view', tag]) !== null) { log(`GitHub release for ${tag} already exists`) From 685ea4874b653b4475c90e1c5c105b911fd33718 Mon Sep 17 00:00:00 2001 From: aaroncox Date: Thu, 24 Sep 2026 17:21:54 -0700 Subject: [PATCH 17/18] Point UPGRADING.md at the 4.0.1 release notes --- UPGRADING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 4ade1df9..798416ae 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,5 +1,5 @@ # Upgrading -Moving a project to `@wharfkit/*` 4.0.0 is covered on the website: +Moving a project to `@wharfkit/*` 4.0.1 is covered on the website: -- [Wharf 4.0.0](https://wharfkit.com/docs/releases/4-0-0#upgrading), the upgrade steps, the changes to check in your code, and what each package gained. +- [Wharf 4.0.1](https://wharfkit.com/docs/releases/4-0-1#upgrading), the upgrade steps, the changes to check in your code, and what each package gained. From 48d7957a155cdd307b19f0efde16df41e723f3b4 Mon Sep 17 00:00:00 2001 From: aaroncox Date: Thu, 24 Sep 2026 17:25:56 -0700 Subject: [PATCH 18/18] Version 4.0.1 --- bun.lock | 102 +++++++++--------- package.json | 2 +- packages/abicache/package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- packages/account/package.json | 2 +- packages/actionstream/package.json | 2 +- packages/antelope/package.json | 2 +- packages/atomicassets/package.json | 2 +- packages/bundle/README.md | 14 +-- packages/bundle/package.json | 2 +- packages/cli/package.json | 2 +- packages/common/package.json | 2 +- packages/conformance/package.json | 2 +- packages/contract/package.json | 2 +- packages/hyperion/package.json | 2 +- packages/light-api/package.json | 2 +- packages/mock-data/package.json | 2 +- packages/msigs/package.json | 2 +- packages/protocol-esr/package.json | 2 +- packages/protocol-scatter/package.json | 2 +- packages/resources/package.json | 2 +- packages/roborovski/package.json | 2 +- packages/sealed-messages/package.json | 2 +- packages/session/package.json | 2 +- packages/signing-request/package.json | 2 +- packages/svelte-components/package.json | 2 +- packages/token/package.json | 2 +- .../transact-plugin-autocorrect/package.json | 2 +- .../transact-plugin-cosigner/package.json | 2 +- .../transact-plugin-explorerlink/package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- packages/transact-plugin-mock/package.json | 2 +- .../transact-plugin-msig-propose/package.json | 2 +- .../package.json | 2 +- packages/wallet-plugin-anchor/package.json | 2 +- packages/wallet-plugin-cleos/package.json | 2 +- .../wallet-plugin-cloudwallet/package.json | 2 +- .../wallet-plugin-gatewallet/package.json | 2 +- packages/wallet-plugin-imtoken/package.json | 2 +- packages/wallet-plugin-metamask/package.json | 2 +- packages/wallet-plugin-mimic/package.json | 2 +- packages/wallet-plugin-mock/package.json | 2 +- packages/wallet-plugin-paycash/package.json | 2 +- .../wallet-plugin-privatekey/package.json | 2 +- packages/wallet-plugin-scatter/package.json | 2 +- packages/wallet-plugin-tacklebox/package.json | 2 +- .../wallet-plugin-tokenpocket/package.json | 2 +- .../package.json | 2 +- packages/web-renderer/package.json | 2 +- packages/web-ui/package.json | 2 +- packages/webauthn/package.json | 2 +- 54 files changed, 110 insertions(+), 110 deletions(-) diff --git a/bun.lock b/bun.lock index c7ac3e36..dd8a84f3 100644 --- a/bun.lock +++ b/bun.lock @@ -32,7 +32,7 @@ }, "packages/abicache": { "name": "@wharfkit/abicache", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", "@wharfkit/signing-request": "workspace:*", @@ -44,7 +44,7 @@ }, "packages/account": { "name": "@wharfkit/account", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", "@wharfkit/common": "workspace:*", @@ -60,7 +60,7 @@ }, "packages/account-creation-plugin-anchor": { "name": "@wharfkit/account-creation-plugin-anchor", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -71,7 +71,7 @@ }, "packages/account-creation-plugin-jungle4": { "name": "@wharfkit/account-creation-plugin-jungle4", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -82,7 +82,7 @@ }, "packages/account-creation-plugin-metamask": { "name": "@wharfkit/account-creation-plugin-metamask", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@metamask/providers": "^17.0.0", "@wharfkit/session": "workspace:*", @@ -94,7 +94,7 @@ }, "packages/actionstream": { "name": "@wharfkit/actionstream", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", }, @@ -105,7 +105,7 @@ }, "packages/antelope": { "name": "@wharfkit/antelope", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@noble/curves": "^2.2.0", "@noble/hashes": "^2.2.0", @@ -115,7 +115,7 @@ }, "packages/atomicassets": { "name": "@wharfkit/atomicassets", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", "@wharfkit/common": "workspace:*", @@ -128,7 +128,7 @@ }, "packages/bundle": { "name": "@wharfkit/bundle", - "version": "4.0.0", + "version": "4.0.1", "devDependencies": { "@wharfkit/account": "workspace:*", "@wharfkit/antelope": "workspace:*", @@ -154,7 +154,7 @@ }, "packages/cli": { "name": "@wharfkit/cli", - "version": "4.0.0", + "version": "4.0.1", "bin": { "wharfkit": "./lib/cli.js", }, @@ -178,7 +178,7 @@ }, "packages/common": { "name": "@wharfkit/common", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", }, @@ -189,7 +189,7 @@ }, "packages/conformance": { "name": "@wharfkit/conformance", - "version": "4.0.0", + "version": "4.0.1", "devDependencies": { "@greymass/vert": "^3.0.0", "@types/bun": "^1.0.4", @@ -200,7 +200,7 @@ }, "packages/contract": { "name": "@wharfkit/contract", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/abicache": "workspace:*", "@wharfkit/antelope": "workspace:*", @@ -214,7 +214,7 @@ }, "packages/hyperion": { "name": "@wharfkit/hyperion", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", }, @@ -224,7 +224,7 @@ }, "packages/light-api": { "name": "@wharfkit/light-api", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", }, @@ -234,7 +234,7 @@ }, "packages/mock-data": { "name": "@wharfkit/mock-data", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", "@wharfkit/session": "workspace:*", @@ -243,7 +243,7 @@ }, "packages/msigs": { "name": "@wharfkit/msigs", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", }, @@ -253,7 +253,7 @@ }, "packages/protocol-esr": { "name": "@wharfkit/protocol-esr", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@greymass/buoy": "catalog:", "@wharfkit/sealed-messages": "workspace:*", @@ -272,7 +272,7 @@ }, "packages/protocol-scatter": { "name": "@wharfkit/protocol-scatter", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", "@wharfkit/session": "workspace:*", @@ -287,7 +287,7 @@ }, "packages/resources": { "name": "@wharfkit/resources", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", "bn.js": "catalog:", @@ -299,7 +299,7 @@ }, "packages/roborovski": { "name": "@wharfkit/roborovski", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", }, @@ -309,7 +309,7 @@ }, "packages/sealed-messages": { "name": "@wharfkit/sealed-messages", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@greymass/miniaes": "^1.0.0", "@wharfkit/antelope": "workspace:*", @@ -320,7 +320,7 @@ }, "packages/session": { "name": "@wharfkit/session", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/abicache": "workspace:*", "@wharfkit/antelope": "workspace:*", @@ -337,14 +337,14 @@ }, "packages/signing-request": { "name": "@wharfkit/signing-request", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", }, }, "packages/svelte-components": { "name": "@wharfkit/svelte-components", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@lucide/svelte": "^0.516.0", "@melt-ui/svelte": "^0.86.6", @@ -391,7 +391,7 @@ }, "packages/token": { "name": "@wharfkit/token", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", "@wharfkit/contract": "workspace:*", @@ -402,7 +402,7 @@ }, "packages/transact-plugin-autocorrect": { "name": "@wharfkit/transact-plugin-autocorrect", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/resources": "workspace:*", "@wharfkit/session": "workspace:*", @@ -415,7 +415,7 @@ }, "packages/transact-plugin-cosigner": { "name": "@wharfkit/transact-plugin-cosigner", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -427,7 +427,7 @@ }, "packages/transact-plugin-explorerlink": { "name": "@wharfkit/transact-plugin-explorerlink", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -439,7 +439,7 @@ }, "packages/transact-plugin-finality-callback": { "name": "@wharfkit/transact-plugin-finality-callback", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -450,7 +450,7 @@ }, "packages/transact-plugin-finality-checker": { "name": "@wharfkit/transact-plugin-finality-checker", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -462,7 +462,7 @@ }, "packages/transact-plugin-mock": { "name": "@wharfkit/transact-plugin-mock", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -474,7 +474,7 @@ }, "packages/transact-plugin-msig-propose": { "name": "@wharfkit/transact-plugin-msig-propose", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -486,7 +486,7 @@ }, "packages/transact-plugin-resource-provider": { "name": "@wharfkit/transact-plugin-resource-provider", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/resources": "workspace:*", "@wharfkit/session": "workspace:*", @@ -501,7 +501,7 @@ }, "packages/wallet-plugin-anchor": { "name": "@wharfkit/wallet-plugin-anchor", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@greymass/buoy": "catalog:", "@wharfkit/antelope": "workspace:*", @@ -522,7 +522,7 @@ }, "packages/wallet-plugin-cleos": { "name": "@wharfkit/wallet-plugin-cleos", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -533,7 +533,7 @@ }, "packages/wallet-plugin-cloudwallet": { "name": "@wharfkit/wallet-plugin-cloudwallet", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -544,7 +544,7 @@ }, "packages/wallet-plugin-gatewallet": { "name": "@wharfkit/wallet-plugin-gatewallet", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/protocol-scatter": "workspace:*", "@wharfkit/session": "workspace:*", @@ -557,7 +557,7 @@ }, "packages/wallet-plugin-imtoken": { "name": "@wharfkit/wallet-plugin-imtoken", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/protocol-scatter": "workspace:*", "@wharfkit/session": "workspace:*", @@ -570,7 +570,7 @@ }, "packages/wallet-plugin-metamask": { "name": "@wharfkit/wallet-plugin-metamask", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@metamask/providers": "^17.0.0", "@wharfkit/session": "workspace:*", @@ -582,7 +582,7 @@ }, "packages/wallet-plugin-mimic": { "name": "@wharfkit/wallet-plugin-mimic", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -593,7 +593,7 @@ }, "packages/wallet-plugin-mock": { "name": "@wharfkit/wallet-plugin-mock", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -605,7 +605,7 @@ }, "packages/wallet-plugin-paycash": { "name": "@wharfkit/wallet-plugin-paycash", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/protocol-esr": "workspace:*", "@wharfkit/session": "workspace:*", @@ -617,7 +617,7 @@ }, "packages/wallet-plugin-privatekey": { "name": "@wharfkit/wallet-plugin-privatekey", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -629,7 +629,7 @@ }, "packages/wallet-plugin-scatter": { "name": "@wharfkit/wallet-plugin-scatter", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/protocol-scatter": "workspace:*", "@wharfkit/session": "workspace:*", @@ -641,7 +641,7 @@ }, "packages/wallet-plugin-tacklebox": { "name": "@wharfkit/wallet-plugin-tacklebox", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@greymass/buoy": "catalog:", "@wharfkit/protocol-esr": "workspace:*", @@ -656,7 +656,7 @@ }, "packages/wallet-plugin-tokenpocket": { "name": "@wharfkit/wallet-plugin-tokenpocket", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/protocol-scatter": "workspace:*", "@wharfkit/session": "workspace:*", @@ -668,7 +668,7 @@ }, "packages/wallet-plugin-web-authenticator": { "name": "@wharfkit/wallet-plugin-web-authenticator", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@greymass/buoy": "catalog:", "@wharfkit/antelope": "workspace:*", @@ -687,7 +687,7 @@ }, "packages/web-renderer": { "name": "@wharfkit/web-renderer", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -738,7 +738,7 @@ }, "packages/web-ui": { "name": "@wharfkit/web-ui", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/common": "workspace:*", "@wharfkit/session": "workspace:*", @@ -770,7 +770,7 @@ }, "packages/webauthn": { "name": "@wharfkit/webauthn", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@wharfkit/antelope": "workspace:*", "cborg": "^4.5.8", diff --git a/package.json b/package.json index 071b2b0c..ed40c7f5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "wharfkit-js", "private": true, - "version": "4.0.0", + "version": "4.0.1", "workspaces": { "packages": [ "packages/*" diff --git a/packages/abicache/package.json b/packages/abicache/package.json index d26e717e..a7dcab34 100644 --- a/packages/abicache/package.json +++ b/packages/abicache/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/abicache", "description": "ABI Caching Mechanism for use in Session and Contract Kits", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/account-creation-plugin-anchor/package.json b/packages/account-creation-plugin-anchor/package.json index 5bc7408e..9bd353e0 100644 --- a/packages/account-creation-plugin-anchor/package.json +++ b/packages/account-creation-plugin-anchor/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/account-creation-plugin-anchor", "description": "An account creation plugin using the Greymass account creation service", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/account-creation-plugin-anchor", "license": "BSD-3-Clause", "engines": { diff --git a/packages/account-creation-plugin-jungle4/package.json b/packages/account-creation-plugin-jungle4/package.json index 2e0f7c54..78834099 100644 --- a/packages/account-creation-plugin-jungle4/package.json +++ b/packages/account-creation-plugin-jungle4/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/account-creation-plugin-jungle4", "description": "Plugin to create a Jungle4 Testnet acccount.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/account-creation-plugin-jungle4", "license": "BSD-3-Clause", "engines": { diff --git a/packages/account-creation-plugin-metamask/package.json b/packages/account-creation-plugin-metamask/package.json index d8d72629..1eaae9ce 100644 --- a/packages/account-creation-plugin-metamask/package.json +++ b/packages/account-creation-plugin-metamask/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/account-creation-plugin-metamask", "description": "A MetaMask plugin to create EOS accounts using Metamask public keys.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/account-creation-plugin-metamask", "license": "BSD-3-Clause", "engines": { diff --git a/packages/account/package.json b/packages/account/package.json index 5aaab8cc..12df116e 100644 --- a/packages/account/package.json +++ b/packages/account/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/account", "description": "Account kit for Wharf Kit", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/docs/account-kit", "license": "BSD-3-Clause", "engines": { diff --git a/packages/actionstream/package.json b/packages/actionstream/package.json index 8d7b7d89..d43cfeaa 100644 --- a/packages/actionstream/package.json +++ b/packages/actionstream/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/actionstream", "description": "Client library for subscribing to Roborovski action streams", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/antelope/package.json b/packages/antelope/package.json index a3959b06..2044e163 100644 --- a/packages/antelope/package.json +++ b/packages/antelope/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/antelope", "description": "Library for working with Antelope powered blockchains.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/docs/antelope", "license": "BSD-3-Clause", "engines": { diff --git a/packages/atomicassets/package.json b/packages/atomicassets/package.json index d493bba3..05878b98 100644 --- a/packages/atomicassets/package.json +++ b/packages/atomicassets/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/atomicassets", "description": "AtomicAsset library for Wharf", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/bundle/README.md b/packages/bundle/README.md index 8381b755..432aec5f 100644 --- a/packages/bundle/README.md +++ b/packages/bundle/README.md @@ -5,7 +5,7 @@ A prepackaged bundle of common Wharf libraries, built as a self-contained IIFE ( ## Usage ```html - + ``` @@ -30,7 +30,7 @@ Both URLs above name an exact version and the full file path. jsDelivr serves th Use `https://cdn.jsdelivr.net/npm/@wharfkit/bundle@4/dist/wharf.bundle.js` if you want patch releases without editing the page. The trade is that the bytes are no longer the package's own and no longer eligible for Subresource Integrity. -To add an `integrity` attribute, take the hash for the exact version from `https://data.jsdelivr.com/v1/packages/npm/@wharfkit/bundle@4.0.0?structure=flat`, and pair it with `crossorigin="anonymous"`, which SRI requires. An `integrity` attribute on a ` ``` -Mixing versions across imports, or adding a package still on the `1.x` line, splits it again. esm.sh takes an explicit pin against that: `?deps=@wharfkit/antelope@4.0.0` on each top-level URL rewrites the antelope import to one concrete build and propagates into every transitive `@wharfkit/*` request, so it costs one query parameter per import rather than one entry per transitive package. jsDelivr's `+esm` route has no query-parameter equivalent, and its output carries jsDelivr's own warning against pairing it with Subresource Integrity. +Mixing versions across imports, or adding a package still on the `1.x` line, splits it again. esm.sh takes an explicit pin against that: `?deps=@wharfkit/antelope@4.0.1` on each top-level URL rewrites the antelope import to one concrete build and propagates into every transitive `@wharfkit/*` request, so it costs one query parameter per import rather than one entry per transitive package. jsDelivr's `+esm` route has no query-parameter equivalent, and its output carries jsDelivr's own warning against pairing it with Subresource Integrity. The bundle stays the recommended browser artifact. It is the only one that guarantees a single antelope without a resolver. ## Examples -`public/bundle.html` and `public/esm.html` are runnable examples of both forms, loading the built files next to them. `make` copies them into `dist/`, so open them from there after a build. The same pages ship in the package, at `https://cdn.jsdelivr.net/npm/@wharfkit/bundle@4.0.0/dist/bundle.html` and `.../dist/esm.html`. +`public/bundle.html` and `public/esm.html` are runnable examples of both forms, loading the built files next to them. `make` copies them into `dist/`, so open them from there after a build. The same pages ship in the package, at `https://cdn.jsdelivr.net/npm/@wharfkit/bundle@4.0.1/dist/bundle.html` and `.../dist/esm.html`. ## Types diff --git a/packages/bundle/package.json b/packages/bundle/package.json index 762ae55a..894ad132 100644 --- a/packages/bundle/package.json +++ b/packages/bundle/package.json @@ -1,6 +1,6 @@ { "name": "@wharfkit/bundle", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "description": "A prepackaged bundle of common Wharf libraries re-exported for IIFE or ESM", "license": "BSD-3-Clause", diff --git a/packages/cli/package.json b/packages/cli/package.json index 00f0880f..149ecf13 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wharfkit/cli", - "version": "4.0.0", + "version": "4.0.1", "license": "BSD-3-Clause", "homepage": "https://wharfkit.com/docs/utilities/cli", "description": "Command line utilities for Wharf", diff --git a/packages/common/package.json b/packages/common/package.json index 435bd68f..9fd8e8ea 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/common", "description": "Common data and functions shared across WharfKit packages", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/docs/utilities/common-library", "license": "BSD-3-Clause", "engines": { diff --git a/packages/conformance/package.json b/packages/conformance/package.json index 69bd7d33..a8917e35 100644 --- a/packages/conformance/package.json +++ b/packages/conformance/package.json @@ -1,6 +1,6 @@ { "name": "@wharfkit/conformance", - "version": "4.0.0", + "version": "4.0.1", "type": "module", "license": "BSD-3-Clause", "engines": { diff --git a/packages/contract/package.json b/packages/contract/package.json index 4726b64f..f9b05567 100644 --- a/packages/contract/package.json +++ b/packages/contract/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/contract", "description": "ContractKit for Wharf", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/docs/contract-kit", "license": "BSD-3-Clause", "engines": { diff --git a/packages/hyperion/package.json b/packages/hyperion/package.json index b97ebaa8..00d3e02d 100644 --- a/packages/hyperion/package.json +++ b/packages/hyperion/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/hyperion", "description": "API Client to access Hyperion API endpoints", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/light-api/package.json b/packages/light-api/package.json index 73be129d..99792eb8 100644 --- a/packages/light-api/package.json +++ b/packages/light-api/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/light-api", "description": "API Client to access Light API endpoints", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/mock-data/package.json b/packages/mock-data/package.json index 4e98cb0c..312bfdb1 100644 --- a/packages/mock-data/package.json +++ b/packages/mock-data/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/mock-data", "description": "Sample data for usage in tests throughout @wharfkit", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/msigs/package.json b/packages/msigs/package.json index 46849f61..fec46193 100644 --- a/packages/msigs/package.json +++ b/packages/msigs/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/msigs", "description": "API Client to access Roborovski msigs API endpoints", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/protocol-esr/package.json b/packages/protocol-esr/package.json index ade28985..3ddb52b0 100644 --- a/packages/protocol-esr/package.json +++ b/packages/protocol-esr/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/protocol-esr", "description": "Abstract methods useful to all ESR-based wallet plugins", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/protocol-scatter/package.json b/packages/protocol-scatter/package.json index 6d20891c..2d0b1007 100644 --- a/packages/protocol-scatter/package.json +++ b/packages/protocol-scatter/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/protocol-scatter", "description": "Abstract methods useful to all Scatter-based wallet plugins", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/resources/package.json b/packages/resources/package.json index b07d38d2..79315c7f 100644 --- a/packages/resources/package.json +++ b/packages/resources/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/resources", "description": "Library to assist in Antelope-blockchain resource calculations.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/roborovski/package.json b/packages/roborovski/package.json index 361b8f33..a3aeb583 100644 --- a/packages/roborovski/package.json +++ b/packages/roborovski/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/roborovski", "description": "API Client to access Roborovski API endpoints", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/sealed-messages/package.json b/packages/sealed-messages/package.json index b49dcdcf..1576871e 100644 --- a/packages/sealed-messages/package.json +++ b/packages/sealed-messages/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/sealed-messages", "description": "", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/session/package.json b/packages/session/package.json index 0a53693c..b6849884 100644 --- a/packages/session/package.json +++ b/packages/session/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/session", "description": "Create account-based sessions, perform transactions, and allow users to login using Antelope-based blockchains.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/docs/session-kit", "license": "BSD-3-Clause", "engines": { diff --git a/packages/signing-request/package.json b/packages/signing-request/package.json index 52f60d98..4be7bfa2 100644 --- a/packages/signing-request/package.json +++ b/packages/signing-request/package.json @@ -1,6 +1,6 @@ { "name": "@wharfkit/signing-request", - "version": "4.0.0", + "version": "4.0.1", "description": "Signing Request (ESR / EEP-7) encoder and decoder for Antelope blockchains", "homepage": "https://wharfkit.com/docs/utilities/signing-request-library", "license": "BSD-3-Clause", diff --git a/packages/svelte-components/package.json b/packages/svelte-components/package.json index 5f449650..d1b28703 100644 --- a/packages/svelte-components/package.json +++ b/packages/svelte-components/package.json @@ -1,6 +1,6 @@ { "name": "@wharfkit/svelte-components", - "version": "4.0.0", + "version": "4.0.1", "description": "Svelte 5 and Tailwind v4 component library for Antelope applications", "license": "BSD-3-Clause", "repository": { diff --git a/packages/token/package.json b/packages/token/package.json index 50979c39..a935e90d 100644 --- a/packages/token/package.json +++ b/packages/token/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/token", "description": "Library to work with Antelope-blockchain system tokens.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/transact-plugin-autocorrect/package.json b/packages/transact-plugin-autocorrect/package.json index 66c9398e..59e54ffb 100644 --- a/packages/transact-plugin-autocorrect/package.json +++ b/packages/transact-plugin-autocorrect/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/transact-plugin-autocorrect", "description": "A plugin to correct common issues users experience while performing transactions.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/transact-plugin-autocorrect", "license": "BSD-3-Clause", "engines": { diff --git a/packages/transact-plugin-cosigner/package.json b/packages/transact-plugin-cosigner/package.json index 307449e2..345e2611 100644 --- a/packages/transact-plugin-cosigner/package.json +++ b/packages/transact-plugin-cosigner/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/transact-plugin-cosigner", "description": "Automatically cosign transactions to assume resource costs using a noop action.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/transact-plugin-cosigner", "license": "BSD-3-Clause", "engines": { diff --git a/packages/transact-plugin-explorerlink/package.json b/packages/transact-plugin-explorerlink/package.json index 4e1f31bf..074f18ab 100644 --- a/packages/transact-plugin-explorerlink/package.json +++ b/packages/transact-plugin-explorerlink/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/transact-plugin-explorerlink", "description": "A transact plugin to display a link to a block explorer after a transaction is broadcast.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/transact-plugin-explorerlink", "license": "BSD-3-Clause", "engines": { diff --git a/packages/transact-plugin-finality-callback/package.json b/packages/transact-plugin-finality-callback/package.json index c98d1e71..086f6f97 100644 --- a/packages/transact-plugin-finality-callback/package.json +++ b/packages/transact-plugin-finality-callback/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/transact-plugin-finality-callback", "description": "A template to create plugins for use with @wharfkit/session transact method.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/transact-plugin-finality-callback", "license": "BSD-3-Clause", "engines": { diff --git a/packages/transact-plugin-finality-checker/package.json b/packages/transact-plugin-finality-checker/package.json index 0be048ae..6242a9eb 100644 --- a/packages/transact-plugin-finality-checker/package.json +++ b/packages/transact-plugin-finality-checker/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/transact-plugin-finality-checker", "description": "A template to create plugins for use with @wharfkit/session transact method.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/transact-plugin-finality-checker", "license": "BSD-3-Clause", "engines": { diff --git a/packages/transact-plugin-mock/package.json b/packages/transact-plugin-mock/package.json index 80259835..f6dfac63 100644 --- a/packages/transact-plugin-mock/package.json +++ b/packages/transact-plugin-mock/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/transact-plugin-mock", "description": "A mock TransactPlugin to simulate specific events.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/transact-plugin-mock", "license": "BSD-3-Clause", "engines": { diff --git a/packages/transact-plugin-msig-propose/package.json b/packages/transact-plugin-msig-propose/package.json index 96510ea3..f4492c7f 100644 --- a/packages/transact-plugin-msig-propose/package.json +++ b/packages/transact-plugin-msig-propose/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/transact-plugin-msig-propose", "description": "A template to create plugins for use with @wharfkit/session transact method.", - "version": "4.0.0", + "version": "4.0.1", "private": true, "license": "BSD-3-Clause", "engines": { diff --git a/packages/transact-plugin-resource-provider/package.json b/packages/transact-plugin-resource-provider/package.json index a96f5938..18bab4eb 100644 --- a/packages/transact-plugin-resource-provider/package.json +++ b/packages/transact-plugin-resource-provider/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/transact-plugin-resource-provider", "description": "Plugin to automatically provide network resources for transactions using the Resource Provider implementation standard.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/transact-plugin-resource-provider", "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-anchor/package.json b/packages/wallet-plugin-anchor/package.json index 4e5708bc..b0285fbd 100644 --- a/packages/wallet-plugin-anchor/package.json +++ b/packages/wallet-plugin-anchor/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/wallet-plugin-anchor", "description": "An Anchor plugin for use with @wharfkit/session.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/wallet-plugin-anchor", "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-cleos/package.json b/packages/wallet-plugin-cleos/package.json index e4841e8f..b12ac738 100644 --- a/packages/wallet-plugin-cleos/package.json +++ b/packages/wallet-plugin-cleos/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/wallet-plugin-cleos", "description": "A template to create wallet plugins for use with @wharfkit/session.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/wallet-plugin-cleos", "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-cloudwallet/package.json b/packages/wallet-plugin-cloudwallet/package.json index b9503ec8..ce4de17e 100644 --- a/packages/wallet-plugin-cloudwallet/package.json +++ b/packages/wallet-plugin-cloudwallet/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/wallet-plugin-cloudwallet", "description": "A WalletPlugin for My Cloud Wallet for use within the @wharfkit/session.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/wallet-plugin-cloudwallet", "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-gatewallet/package.json b/packages/wallet-plugin-gatewallet/package.json index bc435e13..b9bd9cbb 100644 --- a/packages/wallet-plugin-gatewallet/package.json +++ b/packages/wallet-plugin-gatewallet/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/wallet-plugin-gatewallet", "description": "A template to create wallet plugins for use with @wharfkit/session.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/wallet-plugin-gatewallet", "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-imtoken/package.json b/packages/wallet-plugin-imtoken/package.json index 6baa5b27..4cfc8782 100644 --- a/packages/wallet-plugin-imtoken/package.json +++ b/packages/wallet-plugin-imtoken/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/wallet-plugin-imtoken", "description": "A template to create wallet plugins for use with @wharfkit/session.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/wallet-plugin-imtoken", "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-metamask/package.json b/packages/wallet-plugin-metamask/package.json index 84e96311..b3458b23 100644 --- a/packages/wallet-plugin-metamask/package.json +++ b/packages/wallet-plugin-metamask/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/wallet-plugin-metamask", "description": "A MetaMask plugin for use with @wharfkit/session.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/wallet-plugin-metamask", "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-mimic/package.json b/packages/wallet-plugin-mimic/package.json index 58905d96..227bfb9f 100644 --- a/packages/wallet-plugin-mimic/package.json +++ b/packages/wallet-plugin-mimic/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/wallet-plugin-mimic", "description": "A template to create wallet plugins for use with @wharfkit/session.", - "version": "4.0.0", + "version": "4.0.1", "private": true, "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-mock/package.json b/packages/wallet-plugin-mock/package.json index c51e2ab2..6f0d1972 100644 --- a/packages/wallet-plugin-mock/package.json +++ b/packages/wallet-plugin-mock/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/wallet-plugin-mock", "description": "A mock wallet for developers to use while building web applications.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/wallet-plugin-mock", "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-paycash/package.json b/packages/wallet-plugin-paycash/package.json index 1a0dc2ff..777e12ab 100644 --- a/packages/wallet-plugin-paycash/package.json +++ b/packages/wallet-plugin-paycash/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/wallet-plugin-paycash", "description": "A Wharf wallet plugin for the PayCash wallet", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/wallet-plugin-paycash", "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-privatekey/package.json b/packages/wallet-plugin-privatekey/package.json index b157b2de..0be3530c 100644 --- a/packages/wallet-plugin-privatekey/package.json +++ b/packages/wallet-plugin-privatekey/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/wallet-plugin-privatekey", "description": "A template to create wallet plugins for use with @wharfkit/session.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/wallet-plugin-privatekey", "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-scatter/package.json b/packages/wallet-plugin-scatter/package.json index 4cd9f258..603188ce 100644 --- a/packages/wallet-plugin-scatter/package.json +++ b/packages/wallet-plugin-scatter/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/wallet-plugin-scatter", "description": "A WalletPlugin for the Scatter wallet for use within the @wharfkit/session.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/wallet-plugin-scatter", "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-tacklebox/package.json b/packages/wallet-plugin-tacklebox/package.json index 2dcb8c0a..c7b14f9d 100644 --- a/packages/wallet-plugin-tacklebox/package.json +++ b/packages/wallet-plugin-tacklebox/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/wallet-plugin-tacklebox", "description": "A TackleBox plugin for use with @wharfkit/session.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/wallet-plugin-tacklebox", "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-tokenpocket/package.json b/packages/wallet-plugin-tokenpocket/package.json index f3523d06..eabe13f7 100644 --- a/packages/wallet-plugin-tokenpocket/package.json +++ b/packages/wallet-plugin-tokenpocket/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/wallet-plugin-tokenpocket", "description": "A WalletPlugin for the TokenPocket wallet for use within the @wharfkit/session.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/wallet-plugin-tokenpocket", "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-web-authenticator/package.json b/packages/wallet-plugin-web-authenticator/package.json index 7c2b98a5..1d0b9b4f 100644 --- a/packages/wallet-plugin-web-authenticator/package.json +++ b/packages/wallet-plugin-web-authenticator/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/wallet-plugin-web-authenticator", "description": "A Web Authenticator wallet plugin for use with @wharfkit/session.", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com/plugins/wallet-plugin-web-authenticator", "license": "BSD-3-Clause", "engines": { diff --git a/packages/web-renderer/package.json b/packages/web-renderer/package.json index f283f401..ac1b6993 100644 --- a/packages/web-renderer/package.json +++ b/packages/web-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@wharfkit/web-renderer", - "version": "4.0.0", + "version": "4.0.1", "description": "", "license": "BSD-3-Clause", "engines": { diff --git a/packages/web-ui/package.json b/packages/web-ui/package.json index 3d5f2f92..4cd63565 100644 --- a/packages/web-ui/package.json +++ b/packages/web-ui/package.json @@ -1,6 +1,6 @@ { "name": "@wharfkit/web-ui", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "description": "Modern embedded UI renderer for WharfKit SessionKit", "type": "module", diff --git a/packages/webauthn/package.json b/packages/webauthn/package.json index 38d29700..5e66dc39 100644 --- a/packages/webauthn/package.json +++ b/packages/webauthn/package.json @@ -1,7 +1,7 @@ { "name": "@wharfkit/webauthn", "description": "WebAuthn helpers for antelope core", - "version": "4.0.0", + "version": "4.0.1", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": {