diff --git a/UPGRADING.md b/UPGRADING.md index 9dee4337..798416ae 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +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: -- [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.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. 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/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/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/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/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' ) diff --git a/packages/antelope/test/serializer.ts b/packages/antelope/test/serializer.ts index 7f1c0302..81a07202 100644 --- a/packages/antelope/test/serializer.ts +++ b/packages/antelope/test/serializer.ts @@ -1222,6 +1222,108 @@ 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('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 ', 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. 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/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/types.ts b/packages/atomicassets/src/endpoints/market/types.ts index 17a3fabf..b8e0f73d 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, @@ -103,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 @@ -303,3 +334,33 @@ export class GetStatsGraphResponse extends ResponseStruct { 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 +} + +@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..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[] @@ -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) {} @@ -230,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, }) @@ -238,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, }) @@ -257,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'}, @@ -277,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'}, @@ -331,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, }) @@ -350,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'}, @@ -395,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, }) @@ -414,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'}, @@ -490,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, }) @@ -509,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'}, @@ -546,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, }) @@ -565,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'}, @@ -605,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, }) @@ -624,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'}, @@ -642,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, }) @@ -769,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'}, @@ -813,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'}, @@ -855,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'}, @@ -878,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'}, @@ -946,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', @@ -953,4 +997,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/${pathSegment(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/${pathSegment(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/${pathSegment(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/${pathSegment(account)}`, + method: 'POST', + params: bodyParams, + headers: {'Content-Type': 'application/json'}, + responseType: Market.GetRoyaltyAccountResponse, + }) + } } 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/types.ts b/packages/atomicassets/src/endpoints/types.ts index 660692e6..c85631c8 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 @@ -339,7 +369,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 +410,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 +437,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 +463,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') @@ -429,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/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/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/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/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/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/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 b40eab25..b5bc81e3 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,38 @@ 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 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/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 '\.\.'/) + }) +}) diff --git a/packages/atomicassets/test/v2-api.ts b/packages/atomicassets/test/v2-api.ts index a85c7740..13edc783 100644 --- a/packages/atomicassets/test/v2-api.ts +++ b/packages/atomicassets/test/v2-api.ts @@ -66,4 +66,265 @@ 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) + }) + + 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) + }) + + 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')) + }) + + 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) + }) }) 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/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') + }) }) }) 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/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) + }) + }) }) 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/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..1afd1667 100644 --- a/packages/session/test/tests/session.ts +++ b/packages/session/test/tests/session.ts @@ -1,20 +1,12 @@ import {assert} from 'chai' -import SessionKit, { - BaseTransactPlugin, - ChainDefinition, - Session, - SessionOptions, - URLEncodedSession, -} from '$lib' +import SessionKit, {BaseTransactPlugin, ChainDefinition, Session, SessionOptions} from '$lib' import { ABI, ABIDef, - Bytes, FetchProvider, Name, PermissionLevel, - Serializer, Signature, TimePointSec, } from '@wharfkit/antelope' @@ -45,7 +37,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 +544,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')) - }) - }) }) 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/.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) 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": { 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`)