diff --git a/.prerelease-only b/.prerelease-only deleted file mode 100644 index 23de9361..00000000 --- a/.prerelease-only +++ /dev/null @@ -1,3 +0,0 @@ -Stable releases are blocked while this file exists. scripts/release.ts refuses to bump or publish any version without an rc suffix, so every release lands on the npm dist-tag "next" and no package's "latest" pointer moves. - -Remove this file in a reviewed PR when the go/no-go checkpoint approves the first stable release (4.0.0 to "latest"). diff --git a/README.md b/README.md index 60a2899a..be0d763f 100644 --- a/README.md +++ b/README.md @@ -35,4 +35,4 @@ Inside a member directory, `make` builds it, `make test` runs its tests, and `ma ## Documentation -Consumer documentation is on [wharfkit.com](https://wharfkit.com). API documentation, coverage reports, and browser test suites for every member are published from `master` to [wharfkit.github.io/js](https://wharfkit.github.io/js/), one directory per package. +Consumer documentation is on [wharfkit.com](https://wharfkit.com), including the [release notes](https://wharfkit.com/docs/releases) and [UPGRADING.md](UPGRADING.md) for moving a project to 4.0.0. API documentation, coverage reports, and browser test suites for every member are published from `master` to [wharfkit.github.io/js](https://wharfkit.github.io/js/), one directory per package. diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 00000000..9dee4337 --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,6 @@ +# Upgrading + +Moving a project to `@wharfkit/*` 4.0.0 is covered on the website: + +- [Upgrading to WharfKit 4.0.0](https://wharfkit.com/docs/releases/upgrading-to-4-0-0), the procedure, the version each package comes from, and a checklist. +- [Wharf 4.0.0 release notes](https://wharfkit.com/docs/releases/4-0-0), what each package gained. diff --git a/browser-test.rolldown.config.mjs b/browser-test.rolldown.config.mjs index d192a21e..3540154f 100644 --- a/browser-test.rolldown.config.mjs +++ b/browser-test.rolldown.config.mjs @@ -21,6 +21,7 @@ const MEMBERS = { common: {lib: false}, contract: {dataDir: 'data/requests'}, hyperion: {testsDir: '.'}, + 'light-api': {testsDir: '.'}, msigs: {testsDir: '.'}, 'protocol-esr': {browserFetch: true}, 'protocol-scatter': { diff --git a/bun.lock b/bun.lock index b4e2528d..c7ac3e36 100644 --- a/bun.lock +++ b/bun.lock @@ -32,7 +32,7 @@ }, "packages/abicache": { "name": "@wharfkit/abicache", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/antelope": "workspace:*", "@wharfkit/signing-request": "workspace:*", @@ -44,7 +44,7 @@ }, "packages/account": { "name": "@wharfkit/account", - "version": "4.0.0-rc7", + "version": "4.0.0", "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-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -71,7 +71,7 @@ }, "packages/account-creation-plugin-jungle4": { "name": "@wharfkit/account-creation-plugin-jungle4", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -82,7 +82,7 @@ }, "packages/account-creation-plugin-metamask": { "name": "@wharfkit/account-creation-plugin-metamask", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@metamask/providers": "^17.0.0", "@wharfkit/session": "workspace:*", @@ -94,7 +94,7 @@ }, "packages/actionstream": { "name": "@wharfkit/actionstream", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/antelope": "workspace:*", }, @@ -105,7 +105,7 @@ }, "packages/antelope": { "name": "@wharfkit/antelope", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@noble/curves": "^2.2.0", "@noble/hashes": "^2.2.0", @@ -115,7 +115,7 @@ }, "packages/atomicassets": { "name": "@wharfkit/atomicassets", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/antelope": "workspace:*", "@wharfkit/common": "workspace:*", @@ -128,7 +128,7 @@ }, "packages/bundle": { "name": "@wharfkit/bundle", - "version": "4.0.0-rc7", + "version": "4.0.0", "devDependencies": { "@wharfkit/account": "workspace:*", "@wharfkit/antelope": "workspace:*", @@ -154,7 +154,7 @@ }, "packages/cli": { "name": "@wharfkit/cli", - "version": "4.0.0-rc7", + "version": "4.0.0", "bin": { "wharfkit": "./lib/cli.js", }, @@ -178,7 +178,7 @@ }, "packages/common": { "name": "@wharfkit/common", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/antelope": "workspace:*", }, @@ -189,7 +189,7 @@ }, "packages/conformance": { "name": "@wharfkit/conformance", - "version": "4.0.0-rc7", + "version": "4.0.0", "devDependencies": { "@greymass/vert": "^3.0.0", "@types/bun": "^1.0.4", @@ -200,7 +200,7 @@ }, "packages/contract": { "name": "@wharfkit/contract", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/abicache": "workspace:*", "@wharfkit/antelope": "workspace:*", @@ -214,7 +214,17 @@ }, "packages/hyperion": { "name": "@wharfkit/hyperion", - "version": "4.0.0-rc7", + "version": "4.0.0", + "dependencies": { + "@wharfkit/antelope": "workspace:*", + }, + "devDependencies": { + "@wharfkit/mock-data": "workspace:*", + }, + }, + "packages/light-api": { + "name": "@wharfkit/light-api", + "version": "4.0.0", "dependencies": { "@wharfkit/antelope": "workspace:*", }, @@ -224,7 +234,7 @@ }, "packages/mock-data": { "name": "@wharfkit/mock-data", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/antelope": "workspace:*", "@wharfkit/session": "workspace:*", @@ -233,7 +243,7 @@ }, "packages/msigs": { "name": "@wharfkit/msigs", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/antelope": "workspace:*", }, @@ -243,7 +253,7 @@ }, "packages/protocol-esr": { "name": "@wharfkit/protocol-esr", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@greymass/buoy": "catalog:", "@wharfkit/sealed-messages": "workspace:*", @@ -262,7 +272,7 @@ }, "packages/protocol-scatter": { "name": "@wharfkit/protocol-scatter", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/antelope": "workspace:*", "@wharfkit/session": "workspace:*", @@ -277,7 +287,7 @@ }, "packages/resources": { "name": "@wharfkit/resources", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/antelope": "workspace:*", "bn.js": "catalog:", @@ -289,7 +299,7 @@ }, "packages/roborovski": { "name": "@wharfkit/roborovski", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/antelope": "workspace:*", }, @@ -299,7 +309,7 @@ }, "packages/sealed-messages": { "name": "@wharfkit/sealed-messages", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@greymass/miniaes": "^1.0.0", "@wharfkit/antelope": "workspace:*", @@ -310,7 +320,7 @@ }, "packages/session": { "name": "@wharfkit/session", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/abicache": "workspace:*", "@wharfkit/antelope": "workspace:*", @@ -327,14 +337,14 @@ }, "packages/signing-request": { "name": "@wharfkit/signing-request", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/antelope": "workspace:*", }, }, "packages/svelte-components": { "name": "@wharfkit/svelte-components", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@lucide/svelte": "^0.516.0", "@melt-ui/svelte": "^0.86.6", @@ -381,7 +391,7 @@ }, "packages/token": { "name": "@wharfkit/token", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/antelope": "workspace:*", "@wharfkit/contract": "workspace:*", @@ -392,7 +402,7 @@ }, "packages/transact-plugin-autocorrect": { "name": "@wharfkit/transact-plugin-autocorrect", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/resources": "workspace:*", "@wharfkit/session": "workspace:*", @@ -405,7 +415,7 @@ }, "packages/transact-plugin-cosigner": { "name": "@wharfkit/transact-plugin-cosigner", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -417,7 +427,7 @@ }, "packages/transact-plugin-explorerlink": { "name": "@wharfkit/transact-plugin-explorerlink", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -429,7 +439,7 @@ }, "packages/transact-plugin-finality-callback": { "name": "@wharfkit/transact-plugin-finality-callback", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -440,7 +450,7 @@ }, "packages/transact-plugin-finality-checker": { "name": "@wharfkit/transact-plugin-finality-checker", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -452,7 +462,7 @@ }, "packages/transact-plugin-mock": { "name": "@wharfkit/transact-plugin-mock", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -464,7 +474,7 @@ }, "packages/transact-plugin-msig-propose": { "name": "@wharfkit/transact-plugin-msig-propose", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -476,7 +486,7 @@ }, "packages/transact-plugin-resource-provider": { "name": "@wharfkit/transact-plugin-resource-provider", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/resources": "workspace:*", "@wharfkit/session": "workspace:*", @@ -491,7 +501,7 @@ }, "packages/wallet-plugin-anchor": { "name": "@wharfkit/wallet-plugin-anchor", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@greymass/buoy": "catalog:", "@wharfkit/antelope": "workspace:*", @@ -512,7 +522,7 @@ }, "packages/wallet-plugin-cleos": { "name": "@wharfkit/wallet-plugin-cleos", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -523,7 +533,7 @@ }, "packages/wallet-plugin-cloudwallet": { "name": "@wharfkit/wallet-plugin-cloudwallet", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -534,7 +544,7 @@ }, "packages/wallet-plugin-gatewallet": { "name": "@wharfkit/wallet-plugin-gatewallet", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/protocol-scatter": "workspace:*", "@wharfkit/session": "workspace:*", @@ -547,7 +557,7 @@ }, "packages/wallet-plugin-imtoken": { "name": "@wharfkit/wallet-plugin-imtoken", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/protocol-scatter": "workspace:*", "@wharfkit/session": "workspace:*", @@ -560,7 +570,7 @@ }, "packages/wallet-plugin-metamask": { "name": "@wharfkit/wallet-plugin-metamask", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@metamask/providers": "^17.0.0", "@wharfkit/session": "workspace:*", @@ -572,7 +582,7 @@ }, "packages/wallet-plugin-mimic": { "name": "@wharfkit/wallet-plugin-mimic", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -583,7 +593,7 @@ }, "packages/wallet-plugin-mock": { "name": "@wharfkit/wallet-plugin-mock", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -595,7 +605,7 @@ }, "packages/wallet-plugin-paycash": { "name": "@wharfkit/wallet-plugin-paycash", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/protocol-esr": "workspace:*", "@wharfkit/session": "workspace:*", @@ -607,7 +617,7 @@ }, "packages/wallet-plugin-privatekey": { "name": "@wharfkit/wallet-plugin-privatekey", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -619,7 +629,7 @@ }, "packages/wallet-plugin-scatter": { "name": "@wharfkit/wallet-plugin-scatter", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/protocol-scatter": "workspace:*", "@wharfkit/session": "workspace:*", @@ -629,9 +639,24 @@ "@wharfkit/session": "workspace:*", }, }, + "packages/wallet-plugin-tacklebox": { + "name": "@wharfkit/wallet-plugin-tacklebox", + "version": "4.0.0", + "dependencies": { + "@greymass/buoy": "catalog:", + "@wharfkit/protocol-esr": "workspace:*", + "@wharfkit/session": "workspace:*", + "isomorphic-ws": "catalog:", + }, + "devDependencies": { + "@wharfkit/antelope": "workspace:*", + "@wharfkit/mock-data": "workspace:*", + "pako": "catalog:", + }, + }, "packages/wallet-plugin-tokenpocket": { "name": "@wharfkit/wallet-plugin-tokenpocket", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/protocol-scatter": "workspace:*", "@wharfkit/session": "workspace:*", @@ -643,7 +668,7 @@ }, "packages/wallet-plugin-web-authenticator": { "name": "@wharfkit/wallet-plugin-web-authenticator", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@greymass/buoy": "catalog:", "@wharfkit/antelope": "workspace:*", @@ -662,7 +687,7 @@ }, "packages/web-renderer": { "name": "@wharfkit/web-renderer", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/session": "workspace:*", }, @@ -713,7 +738,7 @@ }, "packages/web-ui": { "name": "@wharfkit/web-ui", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/common": "workspace:*", "@wharfkit/session": "workspace:*", @@ -745,7 +770,7 @@ }, "packages/webauthn": { "name": "@wharfkit/webauthn", - "version": "4.0.0-rc7", + "version": "4.0.0", "dependencies": { "@wharfkit/antelope": "workspace:*", "cborg": "^4.5.8", @@ -1436,6 +1461,8 @@ "@wharfkit/hyperion": ["@wharfkit/hyperion@workspace:packages/hyperion"], + "@wharfkit/light-api": ["@wharfkit/light-api@workspace:packages/light-api"], + "@wharfkit/mock-data": ["@wharfkit/mock-data@workspace:packages/mock-data"], "@wharfkit/msigs": ["@wharfkit/msigs@workspace:packages/msigs"], @@ -1496,6 +1523,8 @@ "@wharfkit/wallet-plugin-scatter": ["@wharfkit/wallet-plugin-scatter@workspace:packages/wallet-plugin-scatter"], + "@wharfkit/wallet-plugin-tacklebox": ["@wharfkit/wallet-plugin-tacklebox@workspace:packages/wallet-plugin-tacklebox"], + "@wharfkit/wallet-plugin-tokenpocket": ["@wharfkit/wallet-plugin-tokenpocket@workspace:packages/wallet-plugin-tokenpocket"], "@wharfkit/wallet-plugin-web-authenticator": ["@wharfkit/wallet-plugin-web-authenticator@workspace:packages/wallet-plugin-web-authenticator"], diff --git a/package.json b/package.json index 24cfb6a7..071b2b0c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "wharfkit-js", "private": true, - "version": "4.0.0-rc7", + "version": "4.0.0", "workspaces": { "packages": [ "packages/*" diff --git a/packages/abicache/package.json b/packages/abicache/package.json index 410651f7..d26e717e 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-rc7", + "version": "4.0.0", "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 97171dfe..5bc7408e 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-rc7", + "version": "4.0.0", "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 f959a2fa..2e0f7c54 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-rc7", + "version": "4.0.0", "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 be71cfaf..d8d72629 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-rc7", + "version": "4.0.0", "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 1e6e437d..5aaab8cc 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-rc7", + "version": "4.0.0", "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 9eb655d4..8d7b7d89 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/antelope/package.json b/packages/antelope/package.json index 6b7de774..a3959b06 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com/docs/antelope", "license": "BSD-3-Clause", "engines": { diff --git a/packages/atomicassets/package.json b/packages/atomicassets/package.json index 27370830..d493bba3 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/bundle/README.md b/packages/bundle/README.md index e1a0fc8c..8381b755 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-rc7?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-rc7` 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.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. 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-rc7/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.0/dist/bundle.html` and `.../dist/esm.html`. ## Types diff --git a/packages/bundle/package.json b/packages/bundle/package.json index 2a40cb60..762ae55a 100644 --- a/packages/bundle/package.json +++ b/packages/bundle/package.json @@ -1,6 +1,6 @@ { "name": "@wharfkit/bundle", - "version": "4.0.0-rc7", + "version": "4.0.0", "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 88a73c37..00f0880f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wharfkit/cli", - "version": "4.0.0-rc7", + "version": "4.0.0", "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 8e280c62..435bd68f 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-rc7", + "version": "4.0.0", "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 881a7639..69bd7d33 100644 --- a/packages/conformance/package.json +++ b/packages/conformance/package.json @@ -1,6 +1,6 @@ { "name": "@wharfkit/conformance", - "version": "4.0.0-rc7", + "version": "4.0.0", "type": "module", "license": "BSD-3-Clause", "engines": { diff --git a/packages/contract/package.json b/packages/contract/package.json index f3c9f048..4726b64f 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com/docs/contract-kit", "license": "BSD-3-Clause", "engines": { diff --git a/packages/hyperion/package.json b/packages/hyperion/package.json index 4f27e515..b97ebaa8 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/light-api/.editorconfig b/packages/light-api/.editorconfig new file mode 100644 index 00000000..779f99a1 --- /dev/null +++ b/packages/light-api/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/packages/light-api/.gitignore b/packages/light-api/.gitignore new file mode 100644 index 00000000..22485cbc --- /dev/null +++ b/packages/light-api/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +lib/ +coverage/ +.nyc_output/ +test/browser.html +site/ +docs_build/ diff --git a/packages/light-api/LICENSE b/packages/light-api/LICENSE new file mode 100644 index 00000000..82960d4d --- /dev/null +++ b/packages/light-api/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2026 includenull & Greymass Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/light-api/Makefile b/packages/light-api/Makefile new file mode 100644 index 00000000..5f989bd5 --- /dev/null +++ b/packages/light-api/Makefile @@ -0,0 +1,4 @@ +TEST_FILES := $(wildcard test/*.ts) +MOCK_DIR := ./test/data + +include ../../common.mk diff --git a/packages/light-api/README.md b/packages/light-api/README.md new file mode 100644 index 00000000..d1488447 --- /dev/null +++ b/packages/light-api/README.md @@ -0,0 +1,38 @@ +# @wharfkit/light-api + +API client for the Light API service. + +## Installing + +``` +yarn add @wharfkit/light-api +``` + +## Usage + +```ts +import {APIClient, FetchProvider} from '@wharfkit/antelope' +import {LightAPIClient} from '@wharfkit/light-api' + +const client = new APIClient({ + provider: new FetchProvider('https://eos.light-api.net'), +}) + +const lightapi = new LightAPIClient(client) + +const account = await lightapi.get_account('eos', 'teamgreymass') +const balances = await lightapi.get_balances('eos', 'teamgreymass') +const status = await lightapi.get_status() +``` + +## Running Tests + +``` +make test +``` + +Running tests against a live Light API server: + +``` +MOCK='overwrite' make test +``` diff --git a/packages/light-api/package.json b/packages/light-api/package.json new file mode 100644 index 00000000..73be129d --- /dev/null +++ b/packages/light-api/package.json @@ -0,0 +1,34 @@ +{ + "name": "@wharfkit/light-api", + "description": "API Client to access Light API endpoints", + "version": "4.0.0", + "homepage": "https://wharfkit.com", + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.19.0" + }, + "main": "lib/light-api.js", + "module": "lib/light-api.m.js", + "types": "lib/light-api.d.ts", + "browser": { + "buffer": false, + "crypto": false + }, + "sideEffects": false, + "files": [ + "lib/*", + "src/*" + ], + "scripts": {}, + "dependencies": { + "@wharfkit/antelope": "workspace:*" + }, + "devDependencies": { + "@wharfkit/mock-data": "workspace:*" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/wharfkit/js.git", + "directory": "packages/light-api" + } +} diff --git a/packages/light-api/src/endpoints.ts b/packages/light-api/src/endpoints.ts new file mode 100644 index 00000000..766a5f25 --- /dev/null +++ b/packages/light-api/src/endpoints.ts @@ -0,0 +1,132 @@ +import {ABISerializableConstructor, APIClient, NameType} from '@wharfkit/antelope' +import * as Types from './types' + +export class LightAPIClient { + constructor(private client: APIClient) {} + + get_networks() { + return this.callJson('/api/networks') + } + + get_account(network: string, account: NameType | string) { + return this.callStruct( + `/api/account/${this.encodeSegment(network)}/${this.encodeSegment(account)}`, + Types.AccountResponse + ) + } + + get_accinfo(network: string, account: NameType | string) { + return this.callStruct( + `/api/accinfo/${this.encodeSegment(network)}/${this.encodeSegment(account)}`, + Types.AccountInfoResponse + ) + } + + get_balances(network: string, account: NameType | string) { + return this.callStruct( + `/api/balances/${this.encodeSegment(network)}/${this.encodeSegment(account)}`, + Types.BalancesResponse + ) + } + + get_rexbalance(network: string, account: NameType | string) { + return this.callStruct( + `/api/rexbalance/${this.encodeSegment(network)}/${this.encodeSegment(account)}`, + Types.RexBalanceResponse + ) + } + + get_rexraw(network: string, account: NameType | string) { + return this.callStruct( + `/api/rexraw/${this.encodeSegment(network)}/${this.encodeSegment(account)}`, + Types.RexRawResponse + ) + } + + get_tokenbalance( + network: string, + account: NameType | string, + contract: NameType | string, + token: string + ) { + return this.callText( + `/api/tokenbalance/${this.encodeSegment(network)}/${this.encodeSegment( + account + )}/${this.encodeSegment(contract)}/${this.encodeSegment(token)}` + ) + } + + get_key(key: string) { + return this.callJson(`/api/key/${this.encodeSegment(key)}`) + } + + get_codehash(hash: string) { + return this.callJson(`/api/codehash/${this.encodeSegment(hash)}`) + } + + get_topholders(network: string, contract: NameType | string, token: string, count: number) { + return this.callJson( + `/api/topholders/${this.encodeSegment(network)}/${this.encodeSegment( + contract + )}/${this.encodeSegment(token)}/${this.encodeSegment(count)}` + ) + } + + get_holdercount(network: string, contract: NameType | string, token: string) { + return this.callText( + `/api/holdercount/${this.encodeSegment(network)}/${this.encodeSegment( + contract + )}/${this.encodeSegment(token)}` + ) + } + + get_usercount(network: string) { + return this.callText(`/api/usercount/${this.encodeSegment(network)}`) + } + + get_topram(network: string, count: number) { + return this.callJson( + `/api/topram/${this.encodeSegment(network)}/${this.encodeSegment(count)}` + ) + } + + get_topstake(network: string, count: number) { + return this.callJson( + `/api/topstake/${this.encodeSegment(network)}/${this.encodeSegment(count)}` + ) + } + + get_sync(network: string) { + return this.callText(`/api/sync/${this.encodeSegment(network)}`) + } + + get_status() { + return this.callText('/api/status') + } + + private callStruct(path: string, responseType: T) { + return this.client.call({ + path, + method: 'GET', + responseType, + }) + } + + private callJson(path: string) { + return this.client.call({ + path, + method: 'GET', + }) + } + + private callText(path: string) { + return this.client.call({ + path, + method: 'GET', + }) + } + + private encodeSegment(value: string | number | NameType) { + return encodeURIComponent(String(value)) + } +} diff --git a/packages/light-api/src/index.ts b/packages/light-api/src/index.ts new file mode 100644 index 00000000..057cab95 --- /dev/null +++ b/packages/light-api/src/index.ts @@ -0,0 +1,3 @@ +// Export both endpoints and types +export * from './endpoints' +export * as Types from './types' diff --git a/packages/light-api/src/types.ts b/packages/light-api/src/types.ts new file mode 100644 index 00000000..a2694f32 --- /dev/null +++ b/packages/light-api/src/types.ts @@ -0,0 +1,239 @@ +import { + Checksum256, + Int64, + Name, + PublicKey, + Struct, + UInt32, + UInt64, + UInt8, +} from '@wharfkit/antelope' + +export interface ChainInfoLike { + block_num: number + block_time: string + chainid: string + decimals: number + description: string + network: string + production: number + rex_enabled?: number + sync: number + systoken: string +} + +export interface PermissionAuthKeyLike { + public_key: string + pubkey: string + weight: number +} + +export interface PermissionAuthAccountLike { + actor: string + permission: string + weight: number +} + +export interface PermissionAuthLike { + accounts: PermissionAuthAccountLike[] + keys: PermissionAuthKeyLike[] + threshold?: number +} + +export interface PermissionLike { + auth: PermissionAuthLike + perm: string + threshold?: number +} + +export interface CodeHashAccountLike { + account_name: string + code_hash: string +} + +export type NetworkInfo = ChainInfoLike[] +export type TopHolderRow = [string, string] +export type TopRamRow = [string, number] +export type TopStakeRow = [string, number, number] +export type KeyLookupResponse = Record< + string, + { + chain?: ChainInfoLike + accounts?: Record + } +> +export type CodeHashResponse = Record< + string, + { + chain?: ChainInfoLike + accounts?: Record + } +> + +@Struct.type('chain_info') +export class ChainInfo extends Struct { + @Struct.field(UInt64) declare block_num: UInt64 + @Struct.field('string') declare block_time: string + @Struct.field(Checksum256) declare chainid: Checksum256 + @Struct.field(UInt32) declare decimals: UInt32 + @Struct.field('string') declare description: string + @Struct.field('string') declare network: string + @Struct.field(UInt8) declare production: UInt8 + @Struct.field(UInt8, {optional: true}) declare rex_enabled?: UInt8 + @Struct.field(Int64) declare sync: Int64 + @Struct.field('string') declare systoken: string +} + +@Struct.type('permission_auth_key') +export class PermissionAuthKey extends Struct { + @Struct.field(PublicKey) declare public_key: PublicKey + @Struct.field('string') declare pubkey: string + @Struct.field(UInt32) declare weight: UInt32 +} + +@Struct.type('permission_auth_account') +export class PermissionAuthAccount extends Struct { + @Struct.field(Name) declare actor: Name + @Struct.field(Name) declare permission: Name + @Struct.field(UInt32) declare weight: UInt32 +} + +@Struct.type('permission_auth') +export class PermissionAuth extends Struct { + @Struct.field(PermissionAuthAccount, {array: true}) declare accounts: PermissionAuthAccount[] + @Struct.field(PermissionAuthKey, {array: true}) declare keys: PermissionAuthKey[] + @Struct.field(UInt32, {optional: true}) declare threshold?: UInt32 +} + +@Struct.type('permission') +export class Permission extends Struct { + @Struct.field(PermissionAuth) declare auth: PermissionAuth + @Struct.field(Name) declare perm: Name + @Struct.field(UInt32, {optional: true}) declare threshold?: UInt32 +} + +@Struct.type('link_auth') +export class LinkAuth extends Struct { + @Struct.field(Name) declare code: Name + @Struct.field(Name) declare requirement: Name + @Struct.field('string') declare type: string +} + +@Struct.type('delegated_bandwidth_from') +export class DelegatedBandwidthFrom extends Struct { + @Struct.field(Name) declare del_from: Name + @Struct.field(Int64) declare net_weight: Int64 + @Struct.field(Int64) declare cpu_weight: Int64 +} + +@Struct.type('delegated_bandwidth_to') +export class DelegatedBandwidthTo extends Struct { + @Struct.field(Name) declare account_name: Name + @Struct.field(Int64) declare cpu_weight: Int64 + @Struct.field(Int64) declare net_weight: Int64 +} + +@Struct.type('resource_info') +export class ResourceInfo extends Struct { + @Struct.field(Int64) declare cpu_weight: Int64 + @Struct.field(Int64) declare net_weight: Int64 + @Struct.field(UInt64) declare ram_bytes: UInt64 +} + +@Struct.type('balance_entry') +export class BalanceEntry extends Struct { + @Struct.field('string') declare amount: string + @Struct.field('string') declare contract: string + @Struct.field('string') declare currency: string + @Struct.field('string') declare decimals: string +} + +@Struct.type('code_hash_entry') +export class CodeHashEntry extends Struct { + @Struct.field(Checksum256) declare code_hash: Checksum256 +} + +@Struct.type('rex_display_balances') +export class RexDisplayBalances extends Struct { + @Struct.field('string') declare fund: string + @Struct.field('string') declare matured: string + @Struct.field('string') declare maturing: string + @Struct.field('string') declare savings: string +} + +@Struct.type('rex_pool') +export class RexPool extends Struct { + @Struct.field(UInt64) declare loan_num: UInt64 + @Struct.field('string') declare namebid_proceeds: string + @Struct.field('string') declare total_lendable: string + @Struct.field('string') declare total_lent: string + @Struct.field('string') declare total_rex: string + @Struct.field('string') declare total_rent: string + @Struct.field('string') declare total_unlent: string +} + +@Struct.type('rex_raw_balance') +export class RexRawBalance extends Struct { + @Struct.field('string', {optional: true}) declare matured_rex?: string + @Struct.field('string', {optional: true}) declare rex_balance?: string + @Struct.field('any', {optional: true}) declare rex_maturities?: any + @Struct.field('string', {optional: true}) declare vote_stake?: string +} + +@Struct.type('rex_raw') +export class RexRaw extends Struct { + @Struct.field(RexRawBalance, {optional: true}) declare bal?: RexRawBalance + @Struct.field('string') declare fund: string + @Struct.field(RexPool) declare pool: RexPool +} + +@Struct.type('account_info_response') +export class AccountInfoResponse extends Struct { + @Struct.field(Name) declare account_name: Name + @Struct.field(ChainInfo) declare chain: ChainInfo + @Struct.field(DelegatedBandwidthFrom, {array: true}) + declare delegated_from: DelegatedBandwidthFrom[] + @Struct.field(DelegatedBandwidthTo, {array: true}) + declare delegated_to: DelegatedBandwidthTo[] + @Struct.field(LinkAuth, {array: true}) declare linkauth: LinkAuth[] + @Struct.field(Permission, {array: true}) declare permissions: Permission[] + @Struct.field(ResourceInfo) declare resources: ResourceInfo + @Struct.field(CodeHashEntry, {optional: true}) declare code?: CodeHashEntry +} + +@Struct.type('balances_response') +export class BalancesResponse extends Struct { + @Struct.field(Name) declare account_name: Name + @Struct.field(BalanceEntry, {array: true}) declare balances: BalanceEntry[] + @Struct.field(ChainInfo) declare chain: ChainInfo +} + +@Struct.type('rex_balance_response') +export class RexBalanceResponse extends Struct { + @Struct.field(Name) declare account_name: Name + @Struct.field(ChainInfo) declare chain: ChainInfo + @Struct.field(RexDisplayBalances) declare rex: RexDisplayBalances +} + +@Struct.type('rex_raw_response') +export class RexRawResponse extends Struct { + @Struct.field(Name) declare account_name: Name + @Struct.field(ChainInfo) declare chain: ChainInfo + @Struct.field(RexRaw) declare rexraw: RexRaw +} + +@Struct.type('account_response') +export class AccountResponse extends Struct { + @Struct.field(Name) declare account_name: Name + @Struct.field(BalanceEntry, {array: true}) declare balances: BalanceEntry[] + @Struct.field(ChainInfo) declare chain: ChainInfo + @Struct.field(CodeHashEntry, {optional: true}) declare code?: CodeHashEntry + @Struct.field(DelegatedBandwidthFrom, {array: true}) + declare delegated_from: DelegatedBandwidthFrom[] + @Struct.field(DelegatedBandwidthTo, {array: true}) + declare delegated_to: DelegatedBandwidthTo[] + @Struct.field(LinkAuth, {array: true}) declare linkauth: LinkAuth[] + @Struct.field(Permission, {array: true}) declare permissions: Permission[] + @Struct.field(RexDisplayBalances) declare rex: RexDisplayBalances + @Struct.field(ResourceInfo) declare resources: ResourceInfo +} diff --git a/packages/light-api/test/api.ts b/packages/light-api/test/api.ts new file mode 100644 index 00000000..f576f615 --- /dev/null +++ b/packages/light-api/test/api.ts @@ -0,0 +1,182 @@ +import {assert} from 'chai' + +import { + APIClient, + Checksum256, + FetchProvider, + Int64, + Name, + PublicKey, + UInt64, +} from '@wharfkit/antelope' +import {mockFetch} from '@wharfkit/mock-data' + +import {LightAPIClient, Types} from '$lib' + +const client = new APIClient({ + provider: new FetchProvider('https://eos.light-api.net', {fetch: mockFetch}), +}) + +const lightapi = new LightAPIClient(client) + +suite('Light API', function () { + this.slow(200) + this.timeout(10 * 10000) + + test('get_networks', async function () { + const response = await lightapi.get_networks() + + assert.isArray(response) + assert.equal(response.length, 5) + assert.equal(response[0].network, 'eos') + assert.equal(response[0].systoken, 'EOS') + assert.equal( + response[0].chainid, + 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906' + ) + }) + + test('get_account', async function () { + const response = await lightapi.get_account('eos', 'teamgreymass') + + assert.instanceOf(response, Types.AccountResponse) + assert.instanceOf(response.account_name, Name) + assert.equal(String(response.account_name), 'teamgreymass') + assert.instanceOf(response.chain.chainid, Checksum256) + assert.instanceOf(response.permissions[0].perm, Name) + assert.instanceOf(response.permissions[0].auth.keys[0].public_key, PublicKey) + assert.instanceOf(response.resources.ram_bytes, UInt64) + assert.equal(response.balances.length, 45) + assert.equal(response.rex.fund, '0.0000 EOS') + }) + + test('get_accinfo', async function () { + const response = await lightapi.get_accinfo('eos', 'teamgreymass') + + assert.instanceOf(response, Types.AccountInfoResponse) + assert.instanceOf(response.account_name, Name) + assert.equal(String(response.account_name), 'teamgreymass') + assert.instanceOf(response.permissions[0].auth.keys[0].public_key, PublicKey) + assert.instanceOf(response.delegated_from[0].net_weight, Int64) + assert.equal(response.linkauth.length, 12) + assert.equal(String(response.permissions[0].perm), 'active') + }) + + test('get_balances', async function () { + const response = await lightapi.get_balances('eos', 'teamgreymass') + + assert.instanceOf(response, Types.BalancesResponse) + assert.instanceOf(response.account_name, Name) + assert.equal(response.balances.length, 45) + assert.equal(response.balances[14].contract, 'eosio.token') + assert.equal(response.balances[14].amount, '5292.4309') + assert.equal(response.balances[14].decimals, '4') + }) + + test('get_rexbalance', async function () { + const response = await lightapi.get_rexbalance('eos', 'teamgreymass') + + assert.instanceOf(response, Types.RexBalanceResponse) + assert.instanceOf(response.account_name, Name) + assert.equal(response.rex.fund, '0.0000 EOS') + assert.equal(response.rex.maturing, '0.0000 EOS') + assert.instanceOf(response.chain.chainid, Checksum256) + }) + + test('get_rexraw', async function () { + const response = await lightapi.get_rexraw('eos', 'teamgreymass') + + assert.instanceOf(response, Types.RexRawResponse) + assert.instanceOf(response.account_name, Name) + assert.instanceOf(response.rexraw.pool.loan_num, UInt64) + assert.equal(response.rexraw.fund, '0.000000000000000000000000') + assert.equal(response.rexraw.pool.total_rex, '587072315799.259000000000000000000000') + }) + + test('get_tokenbalance', async function () { + const response = await lightapi.get_tokenbalance( + 'eos', + 'teamgreymass', + 'eosio.token', + 'EOS' + ) + + assert.equal(response, '5292.4309') + }) + + test('get_key', async function () { + const response = await lightapi.get_key( + 'PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN' + ) + + assert.equal(response.eos.chain?.network, 'eos') + assert.isArray(response.eos.accounts?.teamgreymass) + assert.equal(response.eos.accounts?.teamgreymass[5].perm, 'owner') + assert.equal( + response.eos.accounts?.teamgreymass[5].auth.keys[0].public_key, + 'PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN' + ) + assert.equal(response.telos.chain?.network, 'telos') + }) + + test('get_codehash', async function () { + const response = await lightapi.get_codehash( + '0a16e1dac533c4558698c8754f41219839ba2a2b75e517e65ea2537f76681f49' + ) + + assert.equal(response.eos.chain?.network, 'eos') + assert.equal(response.eos.accounts?.['eosio.token'].account_name, 'eosio.token') + assert.equal( + response.eos.accounts?.['eosio.token'].code_hash, + '0a16e1dac533c4558698c8754f41219839ba2a2b75e517e65ea2537f76681f49' + ) + }) + + test('get_topholders', async function () { + const response = await lightapi.get_topholders('eos', 'eosio.token', 'EOS', 10) + + assert.isArray(response) + assert.equal(response.length, 10) + assert.deepEqual(response[0], ['core.vaulta', '1035816036.0540']) + }) + + test('get_holdercount', async function () { + const response = await lightapi.get_holdercount('eos', 'eosio.token', 'EOS') + + assert.equal(response, '1653061') + }) + + test('get_usercount', async function () { + const response = await lightapi.get_usercount('eos') + + assert.equal(response, '6404414') + }) + + test('get_topram', async function () { + const response = await lightapi.get_topram('eos', 10) + + assert.isArray(response) + assert.equal(response.length, 10) + assert.deepEqual(response[0], ['utxomng.xsat', 132071612727]) + }) + + test('get_topstake', async function () { + const response = await lightapi.get_topstake('eos', 10) + + assert.isArray(response) + assert.equal(response.length, 10) + assert.deepEqual(response[0], ['eosio.reserv', 363786986211796, 95432101211517]) + }) + + test('get_sync', async function () { + const response = await lightapi.get_sync('eos') + + assert.equal(response, '0 OK') + }) + + test('get_status', async function () { + const response = await lightapi.get_status() + + assert.equal(response, 'OK ') + }) +}) diff --git a/packages/light-api/test/data/149cb3b36356fb75b563d6a5f088431f4c70d21d.json b/packages/light-api/test/data/149cb3b36356fb75b563d6a5f088431f4c70d21d.json new file mode 100644 index 00000000..8acfe0df --- /dev/null +++ b/packages/light-api/test/data/149cb3b36356fb75b563d6a5f088431f4c70d21d.json @@ -0,0 +1,22 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/sync/eos", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "headers": { + "access-control-allow-headers": "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range", + "access-control-allow-methods": "GET", + "access-control-allow-origin": "*", + "access-control-expose-headers": "Content-Length,Content-Range", + "connection": "keep-alive", + "content-type": "text/plain", + "date": "Tue, 24 Mar 2026 19:52:05 GMT", + "server": "nginx/1.18.0 (Ubuntu)", + "transfer-encoding": "chunked" + }, + "text": "0 OK" +} diff --git a/packages/light-api/test/data/22927bdc95fa6a3889434584bafe3e8cb4af4c81.json b/packages/light-api/test/data/22927bdc95fa6a3889434584bafe3e8cb4af4c81.json new file mode 100644 index 00000000..69ab3787 --- /dev/null +++ b/packages/light-api/test/data/22927bdc95fa6a3889434584bafe3e8cb4af4c81.json @@ -0,0 +1,298 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/balances/eos/teamgreymass", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": { + "balances": [ + { + "amount": "45245.6267", + "contract": "betkingtoken", + "decimals": "4", + "currency": "BKT" + }, + { + "contract": "bgbgbgbgbgbg", + "decimals": "4", + "amount": "0.1000", + "currency": "BG" + }, + { + "currency": "BINGO", + "contract": "bingobetoken", + "decimals": "4", + "amount": "1000.0000" + }, + { + "amount": "103.9999", + "contract": "biteyebiteye", + "decimals": "4", + "currency": "BITI" + }, + { + "amount": "0.0012", + "decimals": "4", + "contract": "blockbasetkn", + "currency": "BBT" + }, + { + "currency": "ADE", + "contract": "buildertoken", + "decimals": "4", + "amount": "32351.8580" + }, + { + "currency": "ADE", + "contract": "cadeositoken", + "decimals": "6", + "amount": "32358.858000" + }, + { + "contract": "core.ogx", + "decimals": "8", + "amount": "0.00000100", + "currency": "OBTC" + }, + { + "currency": "A", + "amount": "15004.7481", + "decimals": "4", + "contract": "core.vaulta" + }, + { + "amount": "18.0000", + "contract": "dhbonetokens", + "decimals": "4", + "currency": "DHB" + }, + { + "currency": "EAPP", + "amount": "2.0000", + "contract": "eappeappp.tp", + "decimals": "4" + }, + { + "currency": "EMOON", + "contract": "emoonnetwork", + "decimals": "2", + "amount": "1000010000.00" + }, + { + "amount": "0.0080", + "decimals": "4", + "contract": "eosbtextoken", + "currency": "BT" + }, + { + "currency": "CCC", + "amount": "202.0000", + "decimals": "4", + "contract": "eoscccdotcom" + }, + { + "amount": "5292.4309", + "decimals": "4", + "contract": "eosio.token", + "currency": "EOS" + }, + { + "currency": "POW", + "amount": "3.26194299", + "decimals": "8", + "contract": "eosiopowcoin" + }, + { + "amount": "100.0000", + "decimals": "4", + "contract": "eostory12345", + "currency": "ERCT" + }, + { + "currency": "EPRA", + "decimals": "4", + "contract": "epraofficial", + "amount": "0.0001" + }, + { + "amount": "5000.0000", + "contract": "ethsidechain", + "decimals": "4", + "currency": "EETH" + }, + { + "contract": "everipediaiq", + "decimals": "3", + "amount": "0.001", + "currency": "IQ" + }, + { + "contract": "gamelordcoin", + "decimals": "4", + "amount": "1.0000", + "currency": "LORD" + }, + { + "currency": "HBGO", + "amount": "0.1914", + "decimals": "4", + "contract": "hashbabycoin" + }, + { + "amount": "19908.0137", + "contract": "hirevibeshvt", + "decimals": "4", + "currency": "HVT" + }, + { + "currency": "MVC", + "amount": "0.0001", + "contract": "mvcoin.x", + "decimals": "4" + }, + { + "currency": "CZB", + "amount": "2.00000000", + "contract": "okkkkkkkkkkk", + "decimals": "8" + }, + { + "currency": "LED", + "contract": "okkkkkkkkkkk", + "decimals": "6", + "amount": "53.660000" + }, + { + "amount": "280000.0000", + "contract": "onessusblock", + "decimals": "4", + "currency": "VOID" + }, + { + "currency": "IPOS", + "decimals": "4", + "contract": "oo1122334455", + "amount": "2000.0000" + }, + { + "amount": "479.605", + "contract": "openbrmeos11", + "decimals": "3", + "currency": "BRM" + }, + { + "currency": "PIZZA", + "contract": "pizzatotoken", + "decimals": "4", + "amount": "47.4718" + }, + { + "currency": "PLAY", + "contract": "play.pomelo", + "decimals": "4", + "amount": "100.0000" + }, + { + "currency": "PLO", + "amount": "11438.5283", + "contract": "playeronetkn", + "decimals": "4" + }, + { + "currency": "PUML", + "decimals": "4", + "contract": "pumlhealthio", + "amount": "12.7777" + }, + { + "currency": "RWC", + "decimals": "4", + "contract": "realworldcpn", + "amount": "5.0000" + }, + { + "amount": "1.0000", + "contract": "stipsoculus1", + "decimals": "4", + "currency": "STIPS" + }, + { + "currency": "TOOK", + "amount": "47988.8846", + "contract": "taketooktook", + "decimals": "4" + }, + { + "contract": "tethertether", + "decimals": "4", + "amount": "0.0000", + "currency": "USDT" + }, + { + "amount": "100.0000", + "contract": "theforcegrou", + "decimals": "4", + "currency": "EFOR" + }, + { + "currency": "PEOS", + "amount": "14380.2999", + "decimals": "4", + "contract": "thepeostoken" + }, + { + "currency": "ZEOS", + "amount": "68450.2273", + "decimals": "4", + "contract": "thezeostoken" + }, + { + "contract": "token.rms", + "decimals": "0", + "amount": "1", + "currency": "V" + }, + { + "currency": "LIBRA", + "amount": "16100.0000", + "decimals": "4", + "contract": "usdcoinchain" + }, + { + "contract": "vig111111111", + "decimals": "4", + "amount": "375000.0000", + "currency": "VIG" + }, + { + "currency": "RED", + "amount": "1", + "decimals": "0", + "contract": "visionfintec" + }, + { + "currency": "CDOG", + "amount": "914665.7776", + "contract": "zerotoone.en", + "decimals": "4" + } + ], + "chain": { + "chainid": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906", + "decimals": 4, + "production": 1, + "sync": 0, + "block_time": "2026-03-24 19:56:48", + "description": "EOS Mainnet", + "network": "eos", + "systoken": "EOS", + "rex_enabled": 1, + "block_num": 490174681 + }, + "account_name": "teamgreymass" + }, + "text": "{\"balances\":[{\"amount\":\"45245.6267\",\"contract\":\"betkingtoken\",\"decimals\":\"4\",\"currency\":\"BKT\"},{\"contract\":\"bgbgbgbgbgbg\",\"decimals\":\"4\",\"amount\":\"0.1000\",\"currency\":\"BG\"},{\"currency\":\"BINGO\",\"contract\":\"bingobetoken\",\"decimals\":\"4\",\"amount\":\"1000.0000\"},{\"amount\":\"103.9999\",\"contract\":\"biteyebiteye\",\"decimals\":\"4\",\"currency\":\"BITI\"},{\"amount\":\"0.0012\",\"decimals\":\"4\",\"contract\":\"blockbasetkn\",\"currency\":\"BBT\"},{\"currency\":\"ADE\",\"contract\":\"buildertoken\",\"decimals\":\"4\",\"amount\":\"32351.8580\"},{\"currency\":\"ADE\",\"contract\":\"cadeositoken\",\"decimals\":\"6\",\"amount\":\"32358.858000\"},{\"contract\":\"core.ogx\",\"decimals\":\"8\",\"amount\":\"0.00000100\",\"currency\":\"OBTC\"},{\"currency\":\"A\",\"amount\":\"15004.7481\",\"decimals\":\"4\",\"contract\":\"core.vaulta\"},{\"amount\":\"18.0000\",\"contract\":\"dhbonetokens\",\"decimals\":\"4\",\"currency\":\"DHB\"},{\"currency\":\"EAPP\",\"amount\":\"2.0000\",\"contract\":\"eappeappp.tp\",\"decimals\":\"4\"},{\"currency\":\"EMOON\",\"contract\":\"emoonnetwork\",\"decimals\":\"2\",\"amount\":\"1000010000.00\"},{\"amount\":\"0.0080\",\"decimals\":\"4\",\"contract\":\"eosbtextoken\",\"currency\":\"BT\"},{\"currency\":\"CCC\",\"amount\":\"202.0000\",\"decimals\":\"4\",\"contract\":\"eoscccdotcom\"},{\"amount\":\"5292.4309\",\"decimals\":\"4\",\"contract\":\"eosio.token\",\"currency\":\"EOS\"},{\"currency\":\"POW\",\"amount\":\"3.26194299\",\"decimals\":\"8\",\"contract\":\"eosiopowcoin\"},{\"amount\":\"100.0000\",\"decimals\":\"4\",\"contract\":\"eostory12345\",\"currency\":\"ERCT\"},{\"currency\":\"EPRA\",\"decimals\":\"4\",\"contract\":\"epraofficial\",\"amount\":\"0.0001\"},{\"amount\":\"5000.0000\",\"contract\":\"ethsidechain\",\"decimals\":\"4\",\"currency\":\"EETH\"},{\"contract\":\"everipediaiq\",\"decimals\":\"3\",\"amount\":\"0.001\",\"currency\":\"IQ\"},{\"contract\":\"gamelordcoin\",\"decimals\":\"4\",\"amount\":\"1.0000\",\"currency\":\"LORD\"},{\"currency\":\"HBGO\",\"amount\":\"0.1914\",\"decimals\":\"4\",\"contract\":\"hashbabycoin\"},{\"amount\":\"19908.0137\",\"contract\":\"hirevibeshvt\",\"decimals\":\"4\",\"currency\":\"HVT\"},{\"currency\":\"MVC\",\"amount\":\"0.0001\",\"contract\":\"mvcoin.x\",\"decimals\":\"4\"},{\"currency\":\"CZB\",\"amount\":\"2.00000000\",\"contract\":\"okkkkkkkkkkk\",\"decimals\":\"8\"},{\"currency\":\"LED\",\"contract\":\"okkkkkkkkkkk\",\"decimals\":\"6\",\"amount\":\"53.660000\"},{\"amount\":\"280000.0000\",\"contract\":\"onessusblock\",\"decimals\":\"4\",\"currency\":\"VOID\"},{\"currency\":\"IPOS\",\"decimals\":\"4\",\"contract\":\"oo1122334455\",\"amount\":\"2000.0000\"},{\"amount\":\"479.605\",\"contract\":\"openbrmeos11\",\"decimals\":\"3\",\"currency\":\"BRM\"},{\"currency\":\"PIZZA\",\"contract\":\"pizzatotoken\",\"decimals\":\"4\",\"amount\":\"47.4718\"},{\"currency\":\"PLAY\",\"contract\":\"play.pomelo\",\"decimals\":\"4\",\"amount\":\"100.0000\"},{\"currency\":\"PLO\",\"amount\":\"11438.5283\",\"contract\":\"playeronetkn\",\"decimals\":\"4\"},{\"currency\":\"PUML\",\"decimals\":\"4\",\"contract\":\"pumlhealthio\",\"amount\":\"12.7777\"},{\"currency\":\"RWC\",\"decimals\":\"4\",\"contract\":\"realworldcpn\",\"amount\":\"5.0000\"},{\"amount\":\"1.0000\",\"contract\":\"stipsoculus1\",\"decimals\":\"4\",\"currency\":\"STIPS\"},{\"currency\":\"TOOK\",\"amount\":\"47988.8846\",\"contract\":\"taketooktook\",\"decimals\":\"4\"},{\"contract\":\"tethertether\",\"decimals\":\"4\",\"amount\":\"0.0000\",\"currency\":\"USDT\"},{\"amount\":\"100.0000\",\"contract\":\"theforcegrou\",\"decimals\":\"4\",\"currency\":\"EFOR\"},{\"currency\":\"PEOS\",\"amount\":\"14380.2999\",\"decimals\":\"4\",\"contract\":\"thepeostoken\"},{\"currency\":\"ZEOS\",\"amount\":\"68450.2273\",\"decimals\":\"4\",\"contract\":\"thezeostoken\"},{\"contract\":\"token.rms\",\"decimals\":\"0\",\"amount\":\"1\",\"currency\":\"V\"},{\"currency\":\"LIBRA\",\"amount\":\"16100.0000\",\"decimals\":\"4\",\"contract\":\"usdcoinchain\"},{\"contract\":\"vig111111111\",\"decimals\":\"4\",\"amount\":\"375000.0000\",\"currency\":\"VIG\"},{\"currency\":\"RED\",\"amount\":\"1\",\"decimals\":\"0\",\"contract\":\"visionfintec\"},{\"currency\":\"CDOG\",\"amount\":\"914665.7776\",\"contract\":\"zerotoone.en\",\"decimals\":\"4\"}],\"chain\":{\"chainid\":\"aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906\",\"decimals\":4,\"production\":1,\"sync\":0,\"block_time\":\"2026-03-24 19:56:48\",\"description\":\"EOS Mainnet\",\"network\":\"eos\",\"systoken\":\"EOS\",\"rex_enabled\":1,\"block_num\":490174681},\"account_name\":\"teamgreymass\"}" +} \ No newline at end of file diff --git a/packages/light-api/test/data/2cabe23ce7a56b8c7bf84fcaa5759641deb5e78d.json b/packages/light-api/test/data/2cabe23ce7a56b8c7bf84fcaa5759641deb5e78d.json new file mode 100644 index 00000000..e71f845c --- /dev/null +++ b/packages/light-api/test/data/2cabe23ce7a56b8c7bf84fcaa5759641deb5e78d.json @@ -0,0 +1,22 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/status", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "headers": { + "access-control-allow-headers": "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range", + "access-control-allow-methods": "GET", + "access-control-allow-origin": "*", + "access-control-expose-headers": "Content-Length,Content-Range", + "connection": "keep-alive", + "content-type": "text/plain", + "date": "Tue, 24 Mar 2026 19:52:05 GMT", + "server": "nginx/1.18.0 (Ubuntu)", + "transfer-encoding": "chunked" + }, + "text": "OK " +} diff --git a/packages/light-api/test/data/32301183282b9727c1ff570bbec292ff43db6d10.json b/packages/light-api/test/data/32301183282b9727c1ff570bbec292ff43db6d10.json new file mode 100644 index 00000000..e6bbc8a6 --- /dev/null +++ b/packages/light-api/test/data/32301183282b9727c1ff570bbec292ff43db6d10.json @@ -0,0 +1,12 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/holdercount/eos/eosio.token/EOS", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": 1653061, + "text": "1653061" +} \ No newline at end of file diff --git a/packages/light-api/test/data/34faec66ce3187d286dbfc625c3aa025e5387550.json b/packages/light-api/test/data/34faec66ce3187d286dbfc625c3aa025e5387550.json new file mode 100644 index 00000000..2e070da4 --- /dev/null +++ b/packages/light-api/test/data/34faec66ce3187d286dbfc625c3aa025e5387550.json @@ -0,0 +1,63 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/topstake/eos/10", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": [ + [ + "eosio.reserv", + 363786986211796, + 95432101211517 + ], + [ + "res.xsat", + 3436900878430, + 277254184 + ], + [ + "1aaaann.sat", + 2299999999977, + 114999977 + ], + [ + "marleysprite", + 995852795232, + 458179406 + ], + [ + "dallasarcher", + 995852795232, + 458179406 + ], + [ + "bloodyvanity", + 995852795232, + 458179406 + ], + [ + "suguspowders", + 995852795233, + 458179297 + ], + [ + "eosmandelgod", + 995852795233, + 458179297 + ], + [ + "1aaaaim.sat", + 399999999996, + 19999996 + ], + [ + "bankres.xsat", + 382074128175, + 1254552 + ] + ], + "text": "[[\"eosio.reserv\",363786986211796,95432101211517],[\"res.xsat\",3436900878430,277254184],[\"1aaaann.sat\",2299999999977,114999977],[\"marleysprite\",995852795232,458179406],[\"dallasarcher\",995852795232,458179406],[\"bloodyvanity\",995852795232,458179406],[\"suguspowders\",995852795233,458179297],[\"eosmandelgod\",995852795233,458179297],[\"1aaaaim.sat\",399999999996,19999996],[\"bankres.xsat\",382074128175,1254552]]" +} \ No newline at end of file diff --git a/packages/light-api/test/data/360c0e0b21b9a54be659beeb221221550b135e5f.json b/packages/light-api/test/data/360c0e0b21b9a54be659beeb221221550b135e5f.json new file mode 100644 index 00000000..b32e57a2 --- /dev/null +++ b/packages/light-api/test/data/360c0e0b21b9a54be659beeb221221550b135e5f.json @@ -0,0 +1,254 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/accinfo/eos/teamgreymass", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": { + "chain": { + "chainid": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906", + "decimals": 4, + "production": 1, + "network": "eos", + "description": "EOS Mainnet", + "systoken": "EOS", + "rex_enabled": 1, + "block_time": "2026-03-24 19:56:47", + "sync": 0, + "block_num": 490174680 + }, + "resources": { + "cpu_weight": 0, + "ram_bytes": 818870, + "net_weight": 2 + }, + "delegated_to": [ + { + "account_name": "teamgreymass", + "cpu_weight": 0, + "net_weight": 1 + } + ], + "linkauth": [ + { + "code": "core.vaulta", + "type": "claimrewards", + "requirement": "claim" + }, + { + "type": "transfer", + "requirement": "transfer", + "code": "core.vaulta" + }, + { + "type": "", + "requirement": "decentium", + "code": "decentiumorg" + }, + { + "requirement": "oracle", + "type": "write", + "code": "delphioracle" + }, + { + "code": "eosio", + "type": "claimrewards", + "requirement": "claim" + }, + { + "type": "unregprod", + "requirement": "killswitch", + "code": "eosio" + }, + { + "code": "eosio", + "requirement": "vote", + "type": "voteproducer" + }, + { + "requirement": "claim", + "type": "claimrewards", + "code": "eosio.bpay" + }, + { + "code": "eosio.forum", + "type": "unvote", + "requirement": "voting" + }, + { + "type": "vote", + "requirement": "voting", + "code": "eosio.forum" + }, + { + "requirement": "transfer", + "type": "transfer", + "code": "eosio.token" + }, + { + "code": "producerjson", + "requirement": "producerjson", + "type": "set" + } + ], + "permissions": [ + { + "auth": { + "accounts": [], + "keys": [ + { + "pubkey": "EOS6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQ9kXZK1", + "public_key": "PUB_K1_6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQBdT2GF", + "weight": 1 + } + ] + }, + "threshold": 1, + "perm": "active" + }, + { + "perm": "claim", + "auth": { + "accounts": [], + "keys": [ + { + "weight": 1, + "public_key": "PUB_K1_6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFFzdpw", + "pubkey": "EOS6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFtED4C" + } + ] + }, + "threshold": 1 + }, + { + "perm": "decentium", + "threshold": 1, + "auth": { + "keys": [ + { + "weight": 1, + "pubkey": "EOS7knG7M5TUEdRv1bkVjTPddVoDQnwS7oEZXAgFk3A4hhocA3eJf", + "public_key": "PUB_K1_7knG7M5TUEdRv1bkVjTPddVoDQnwS7oEZXAgFk3A4hhoXjv3GL" + } + ], + "accounts": [] + } + }, + { + "perm": "killswitch", + "auth": { + "keys": [ + { + "weight": 1, + "pubkey": "EOS7CjC7GL71msPzAuAzd2WwiBEAzTcPL47ACrjSuiNmnnGGufYSn", + "public_key": "PUB_K1_7CjC7GL71msPzAuAzd2WwiBEAzTcPL47ACrjSuiNmnnGHEHtup" + } + ], + "accounts": [] + }, + "threshold": 1 + }, + { + "perm": "oracle", + "threshold": 1, + "auth": { + "accounts": [], + "keys": [ + { + "weight": 1, + "pubkey": "EOS88VqmDmJJ9S23eNqdeWYf2zySxv3ckQrWBKy7EvVRCUuhSU4f3", + "public_key": "PUB_K1_88VqmDmJJ9S23eNqdeWYf2zySxv3ckQrWBKy7EvVRCUuhEDJJt" + } + ] + } + }, + { + "auth": { + "accounts": [], + "keys": [ + { + "pubkey": "EOS8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5RgwKN82", + "public_key": "PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN", + "weight": 1 + } + ] + }, + "threshold": 1, + "perm": "owner" + }, + { + "auth": { + "keys": [ + { + "pubkey": "EOS5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrDa6Etp", + "public_key": "PUB_K1_5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrGVwUnM", + "weight": 1 + } + ], + "accounts": [] + }, + "threshold": 1, + "perm": "producerjson" + }, + { + "perm": "transfer", + "auth": { + "accounts": [], + "keys": [ + { + "weight": 1, + "pubkey": "EOS7qZ8nnmn6KBnjQL4oukyZFWCj8DmC9nJE2nkAYAZbwgKhMu8cW", + "public_key": "PUB_K1_7qZ8nnmn6KBnjQL4oukyZFWCj8DmC9nJE2nkAYAZbwgKm7MD7V" + } + ] + }, + "threshold": 1 + }, + { + "perm": "vote", + "auth": { + "accounts": [], + "keys": [ + { + "pubkey": "EOS65NrHPVXaV4voxepQREmYCmnMJm4tAWdxPaK46CbUN1rrVmRzg", + "public_key": "PUB_K1_65NrHPVXaV4voxepQREmYCmnMJm4tAWdxPaK46CbUN1rrkfmPY", + "weight": 1 + } + ] + }, + "threshold": 1 + }, + { + "perm": "voting", + "threshold": 1, + "auth": { + "accounts": [], + "keys": [ + { + "public_key": "PUB_K1_7pn6P5FftyNAKRfx9VcUzBFMvC4UitNbnoKbfxNe8SShE8XTzQ", + "pubkey": "EOS7pn6P5FftyNAKRfx9VcUzBFMvC4UitNbnoKbfxNe8SShELo2it", + "weight": 1 + } + ] + } + } + ], + "account_name": "teamgreymass", + "delegated_from": [ + { + "net_weight": 1, + "del_from": "eos42freedom", + "cpu_weight": 0 + }, + { + "net_weight": 1, + "del_from": "teamgreymass", + "cpu_weight": 0 + } + ] + }, + "text": "{\"chain\":{\"chainid\":\"aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906\",\"decimals\":4,\"production\":1,\"network\":\"eos\",\"description\":\"EOS Mainnet\",\"systoken\":\"EOS\",\"rex_enabled\":1,\"block_time\":\"2026-03-24 19:56:47\",\"sync\":0,\"block_num\":490174680},\"resources\":{\"cpu_weight\":0,\"ram_bytes\":818870,\"net_weight\":2},\"delegated_to\":[{\"account_name\":\"teamgreymass\",\"cpu_weight\":0,\"net_weight\":1}],\"linkauth\":[{\"code\":\"core.vaulta\",\"type\":\"claimrewards\",\"requirement\":\"claim\"},{\"type\":\"transfer\",\"requirement\":\"transfer\",\"code\":\"core.vaulta\"},{\"type\":\"\",\"requirement\":\"decentium\",\"code\":\"decentiumorg\"},{\"requirement\":\"oracle\",\"type\":\"write\",\"code\":\"delphioracle\"},{\"code\":\"eosio\",\"type\":\"claimrewards\",\"requirement\":\"claim\"},{\"type\":\"unregprod\",\"requirement\":\"killswitch\",\"code\":\"eosio\"},{\"code\":\"eosio\",\"requirement\":\"vote\",\"type\":\"voteproducer\"},{\"requirement\":\"claim\",\"type\":\"claimrewards\",\"code\":\"eosio.bpay\"},{\"code\":\"eosio.forum\",\"type\":\"unvote\",\"requirement\":\"voting\"},{\"type\":\"vote\",\"requirement\":\"voting\",\"code\":\"eosio.forum\"},{\"requirement\":\"transfer\",\"type\":\"transfer\",\"code\":\"eosio.token\"},{\"code\":\"producerjson\",\"requirement\":\"producerjson\",\"type\":\"set\"}],\"permissions\":[{\"auth\":{\"accounts\":[],\"keys\":[{\"pubkey\":\"EOS6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQ9kXZK1\",\"public_key\":\"PUB_K1_6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQBdT2GF\",\"weight\":1}]},\"threshold\":1,\"perm\":\"active\"},{\"perm\":\"claim\",\"auth\":{\"accounts\":[],\"keys\":[{\"weight\":1,\"public_key\":\"PUB_K1_6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFFzdpw\",\"pubkey\":\"EOS6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFtED4C\"}]},\"threshold\":1},{\"perm\":\"decentium\",\"threshold\":1,\"auth\":{\"keys\":[{\"weight\":1,\"pubkey\":\"EOS7knG7M5TUEdRv1bkVjTPddVoDQnwS7oEZXAgFk3A4hhocA3eJf\",\"public_key\":\"PUB_K1_7knG7M5TUEdRv1bkVjTPddVoDQnwS7oEZXAgFk3A4hhoXjv3GL\"}],\"accounts\":[]}},{\"perm\":\"killswitch\",\"auth\":{\"keys\":[{\"weight\":1,\"pubkey\":\"EOS7CjC7GL71msPzAuAzd2WwiBEAzTcPL47ACrjSuiNmnnGGufYSn\",\"public_key\":\"PUB_K1_7CjC7GL71msPzAuAzd2WwiBEAzTcPL47ACrjSuiNmnnGHEHtup\"}],\"accounts\":[]},\"threshold\":1},{\"perm\":\"oracle\",\"threshold\":1,\"auth\":{\"accounts\":[],\"keys\":[{\"weight\":1,\"pubkey\":\"EOS88VqmDmJJ9S23eNqdeWYf2zySxv3ckQrWBKy7EvVRCUuhSU4f3\",\"public_key\":\"PUB_K1_88VqmDmJJ9S23eNqdeWYf2zySxv3ckQrWBKy7EvVRCUuhEDJJt\"}]}},{\"auth\":{\"accounts\":[],\"keys\":[{\"pubkey\":\"EOS8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5RgwKN82\",\"public_key\":\"PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN\",\"weight\":1}]},\"threshold\":1,\"perm\":\"owner\"},{\"auth\":{\"keys\":[{\"pubkey\":\"EOS5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrDa6Etp\",\"public_key\":\"PUB_K1_5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrGVwUnM\",\"weight\":1}],\"accounts\":[]},\"threshold\":1,\"perm\":\"producerjson\"},{\"perm\":\"transfer\",\"auth\":{\"accounts\":[],\"keys\":[{\"weight\":1,\"pubkey\":\"EOS7qZ8nnmn6KBnjQL4oukyZFWCj8DmC9nJE2nkAYAZbwgKhMu8cW\",\"public_key\":\"PUB_K1_7qZ8nnmn6KBnjQL4oukyZFWCj8DmC9nJE2nkAYAZbwgKm7MD7V\"}]},\"threshold\":1},{\"perm\":\"vote\",\"auth\":{\"accounts\":[],\"keys\":[{\"pubkey\":\"EOS65NrHPVXaV4voxepQREmYCmnMJm4tAWdxPaK46CbUN1rrVmRzg\",\"public_key\":\"PUB_K1_65NrHPVXaV4voxepQREmYCmnMJm4tAWdxPaK46CbUN1rrkfmPY\",\"weight\":1}]},\"threshold\":1},{\"perm\":\"voting\",\"threshold\":1,\"auth\":{\"accounts\":[],\"keys\":[{\"public_key\":\"PUB_K1_7pn6P5FftyNAKRfx9VcUzBFMvC4UitNbnoKbfxNe8SShE8XTzQ\",\"pubkey\":\"EOS7pn6P5FftyNAKRfx9VcUzBFMvC4UitNbnoKbfxNe8SShELo2it\",\"weight\":1}]}}],\"account_name\":\"teamgreymass\",\"delegated_from\":[{\"net_weight\":1,\"del_from\":\"eos42freedom\",\"cpu_weight\":0},{\"net_weight\":1,\"del_from\":\"teamgreymass\",\"cpu_weight\":0}]}" +} \ No newline at end of file diff --git a/packages/light-api/test/data/3ba4f2ba75bf179e6ca304b0a5331ff89c338d17.json b/packages/light-api/test/data/3ba4f2ba75bf179e6ca304b0a5331ff89c338d17.json new file mode 100644 index 00000000..ce2c21bd --- /dev/null +++ b/packages/light-api/test/data/3ba4f2ba75bf179e6ca304b0a5331ff89c338d17.json @@ -0,0 +1,308 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/key/PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": { + "telos": { + "accounts": { + "locktimerv11": [ + { + "threshold": 1, + "auth": { + "accounts": [], + "keys": [ + { + "pubkey": "EOS8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5RgwKN82", + "public_key": "PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN", + "weight": 1 + } + ] + }, + "perm": "active" + }, + { + "perm": "owner", + "threshold": 1, + "auth": { + "accounts": [], + "keys": [ + { + "weight": 1, + "pubkey": "EOS8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5RgwKN82", + "public_key": "PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN" + } + ] + } + } + ], + "teamgreymass": [ + { + "threshold": 1, + "auth": { + "keys": [ + { + "weight": 1, + "pubkey": "EOS6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQ9kXZK1", + "public_key": "PUB_K1_6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQBdT2GF" + } + ], + "accounts": [] + }, + "perm": "active" + }, + { + "perm": "claim", + "threshold": 1, + "auth": { + "keys": [ + { + "pubkey": "EOS6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFtED4C", + "public_key": "PUB_K1_6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFFzdpw", + "weight": 1 + } + ], + "accounts": [] + } + }, + { + "auth": { + "accounts": [], + "keys": [ + { + "public_key": "PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN", + "pubkey": "EOS8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5RgwKN82", + "weight": 1 + } + ] + }, + "threshold": 1, + "perm": "owner" + }, + { + "auth": { + "keys": [ + { + "weight": 1, + "pubkey": "EOS5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrDa6Etp", + "public_key": "PUB_K1_5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrGVwUnM" + } + ], + "accounts": [] + }, + "threshold": 1, + "perm": "producerjson" + } + ] + }, + "chain": { + "block_num": 458754402, + "sync": 0, + "block_time": "2026-03-24 19:56:52", + "rex_enabled": 1, + "description": "Telos Mainnet", + "network": "telos", + "systoken": "TLOS", + "production": 1, + "decimals": 4, + "chainid": "4667b205c6838ef70ff7988f6e8257e8be0e1284a2f59699054a018f743b1d11" + } + }, + "eos": { + "accounts": { + "teamgreymass": [ + { + "auth": { + "keys": [ + { + "weight": 1, + "pubkey": "EOS6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQ9kXZK1", + "public_key": "PUB_K1_6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQBdT2GF" + } + ], + "accounts": [] + }, + "threshold": 1, + "perm": "active" + }, + { + "threshold": 1, + "auth": { + "accounts": [], + "keys": [ + { + "weight": 1, + "pubkey": "EOS6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFtED4C", + "public_key": "PUB_K1_6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFFzdpw" + } + ] + }, + "perm": "claim" + }, + { + "perm": "decentium", + "auth": { + "keys": [ + { + "pubkey": "EOS7knG7M5TUEdRv1bkVjTPddVoDQnwS7oEZXAgFk3A4hhocA3eJf", + "public_key": "PUB_K1_7knG7M5TUEdRv1bkVjTPddVoDQnwS7oEZXAgFk3A4hhoXjv3GL", + "weight": 1 + } + ], + "accounts": [] + }, + "threshold": 1 + }, + { + "auth": { + "keys": [ + { + "weight": 1, + "pubkey": "EOS7CjC7GL71msPzAuAzd2WwiBEAzTcPL47ACrjSuiNmnnGGufYSn", + "public_key": "PUB_K1_7CjC7GL71msPzAuAzd2WwiBEAzTcPL47ACrjSuiNmnnGHEHtup" + } + ], + "accounts": [] + }, + "threshold": 1, + "perm": "killswitch" + }, + { + "perm": "oracle", + "auth": { + "accounts": [], + "keys": [ + { + "public_key": "PUB_K1_88VqmDmJJ9S23eNqdeWYf2zySxv3ckQrWBKy7EvVRCUuhEDJJt", + "pubkey": "EOS88VqmDmJJ9S23eNqdeWYf2zySxv3ckQrWBKy7EvVRCUuhSU4f3", + "weight": 1 + } + ] + }, + "threshold": 1 + }, + { + "perm": "owner", + "auth": { + "accounts": [], + "keys": [ + { + "weight": 1, + "pubkey": "EOS8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5RgwKN82", + "public_key": "PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN" + } + ] + }, + "threshold": 1 + }, + { + "auth": { + "keys": [ + { + "public_key": "PUB_K1_5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrGVwUnM", + "pubkey": "EOS5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrDa6Etp", + "weight": 1 + } + ], + "accounts": [] + }, + "threshold": 1, + "perm": "producerjson" + }, + { + "threshold": 1, + "auth": { + "accounts": [], + "keys": [ + { + "pubkey": "EOS7qZ8nnmn6KBnjQL4oukyZFWCj8DmC9nJE2nkAYAZbwgKhMu8cW", + "public_key": "PUB_K1_7qZ8nnmn6KBnjQL4oukyZFWCj8DmC9nJE2nkAYAZbwgKm7MD7V", + "weight": 1 + } + ] + }, + "perm": "transfer" + }, + { + "threshold": 1, + "auth": { + "accounts": [], + "keys": [ + { + "weight": 1, + "public_key": "PUB_K1_65NrHPVXaV4voxepQREmYCmnMJm4tAWdxPaK46CbUN1rrkfmPY", + "pubkey": "EOS65NrHPVXaV4voxepQREmYCmnMJm4tAWdxPaK46CbUN1rrVmRzg" + } + ] + }, + "perm": "vote" + }, + { + "perm": "voting", + "auth": { + "accounts": [], + "keys": [ + { + "weight": 1, + "public_key": "PUB_K1_7pn6P5FftyNAKRfx9VcUzBFMvC4UitNbnoKbfxNe8SShE8XTzQ", + "pubkey": "EOS7pn6P5FftyNAKRfx9VcUzBFMvC4UitNbnoKbfxNe8SShELo2it" + } + ] + }, + "threshold": 1 + } + ], + "teamgraymass": [ + { + "auth": { + "accounts": [], + "keys": [ + { + "pubkey": "EOS6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQ9kXZK1", + "public_key": "PUB_K1_6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQBdT2GF", + "weight": 1 + } + ] + }, + "threshold": 1, + "perm": "active" + }, + { + "perm": "owner", + "auth": { + "accounts": [], + "keys": [ + { + "public_key": "PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN", + "pubkey": "EOS8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5RgwKN82", + "weight": 1 + } + ] + }, + "threshold": 1 + } + ] + }, + "chain": { + "network": "eos", + "systoken": "EOS", + "description": "EOS Mainnet", + "rex_enabled": 1, + "sync": 0, + "block_time": "2026-03-24 19:56:52", + "block_num": 490174690, + "chainid": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906", + "decimals": 4, + "production": 1 + } + }, + "wax": { + "accounts": {} + } + }, + "text": "{\"telos\":{\"accounts\":{\"locktimerv11\":[{\"threshold\":1,\"auth\":{\"accounts\":[],\"keys\":[{\"pubkey\":\"EOS8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5RgwKN82\",\"public_key\":\"PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN\",\"weight\":1}]},\"perm\":\"active\"},{\"perm\":\"owner\",\"threshold\":1,\"auth\":{\"accounts\":[],\"keys\":[{\"weight\":1,\"pubkey\":\"EOS8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5RgwKN82\",\"public_key\":\"PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN\"}]}}],\"teamgreymass\":[{\"threshold\":1,\"auth\":{\"keys\":[{\"weight\":1,\"pubkey\":\"EOS6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQ9kXZK1\",\"public_key\":\"PUB_K1_6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQBdT2GF\"}],\"accounts\":[]},\"perm\":\"active\"},{\"perm\":\"claim\",\"threshold\":1,\"auth\":{\"keys\":[{\"pubkey\":\"EOS6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFtED4C\",\"public_key\":\"PUB_K1_6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFFzdpw\",\"weight\":1}],\"accounts\":[]}},{\"auth\":{\"accounts\":[],\"keys\":[{\"public_key\":\"PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN\",\"pubkey\":\"EOS8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5RgwKN82\",\"weight\":1}]},\"threshold\":1,\"perm\":\"owner\"},{\"auth\":{\"keys\":[{\"weight\":1,\"pubkey\":\"EOS5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrDa6Etp\",\"public_key\":\"PUB_K1_5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrGVwUnM\"}],\"accounts\":[]},\"threshold\":1,\"perm\":\"producerjson\"}]},\"chain\":{\"block_num\":458754402,\"sync\":0,\"block_time\":\"2026-03-24 19:56:52\",\"rex_enabled\":1,\"description\":\"Telos Mainnet\",\"network\":\"telos\",\"systoken\":\"TLOS\",\"production\":1,\"decimals\":4,\"chainid\":\"4667b205c6838ef70ff7988f6e8257e8be0e1284a2f59699054a018f743b1d11\"}},\"eos\":{\"accounts\":{\"teamgreymass\":[{\"auth\":{\"keys\":[{\"weight\":1,\"pubkey\":\"EOS6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQ9kXZK1\",\"public_key\":\"PUB_K1_6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQBdT2GF\"}],\"accounts\":[]},\"threshold\":1,\"perm\":\"active\"},{\"threshold\":1,\"auth\":{\"accounts\":[],\"keys\":[{\"weight\":1,\"pubkey\":\"EOS6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFtED4C\",\"public_key\":\"PUB_K1_6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFFzdpw\"}]},\"perm\":\"claim\"},{\"perm\":\"decentium\",\"auth\":{\"keys\":[{\"pubkey\":\"EOS7knG7M5TUEdRv1bkVjTPddVoDQnwS7oEZXAgFk3A4hhocA3eJf\",\"public_key\":\"PUB_K1_7knG7M5TUEdRv1bkVjTPddVoDQnwS7oEZXAgFk3A4hhoXjv3GL\",\"weight\":1}],\"accounts\":[]},\"threshold\":1},{\"auth\":{\"keys\":[{\"weight\":1,\"pubkey\":\"EOS7CjC7GL71msPzAuAzd2WwiBEAzTcPL47ACrjSuiNmnnGGufYSn\",\"public_key\":\"PUB_K1_7CjC7GL71msPzAuAzd2WwiBEAzTcPL47ACrjSuiNmnnGHEHtup\"}],\"accounts\":[]},\"threshold\":1,\"perm\":\"killswitch\"},{\"perm\":\"oracle\",\"auth\":{\"accounts\":[],\"keys\":[{\"public_key\":\"PUB_K1_88VqmDmJJ9S23eNqdeWYf2zySxv3ckQrWBKy7EvVRCUuhEDJJt\",\"pubkey\":\"EOS88VqmDmJJ9S23eNqdeWYf2zySxv3ckQrWBKy7EvVRCUuhSU4f3\",\"weight\":1}]},\"threshold\":1},{\"perm\":\"owner\",\"auth\":{\"accounts\":[],\"keys\":[{\"weight\":1,\"pubkey\":\"EOS8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5RgwKN82\",\"public_key\":\"PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN\"}]},\"threshold\":1},{\"auth\":{\"keys\":[{\"public_key\":\"PUB_K1_5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrGVwUnM\",\"pubkey\":\"EOS5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrDa6Etp\",\"weight\":1}],\"accounts\":[]},\"threshold\":1,\"perm\":\"producerjson\"},{\"threshold\":1,\"auth\":{\"accounts\":[],\"keys\":[{\"pubkey\":\"EOS7qZ8nnmn6KBnjQL4oukyZFWCj8DmC9nJE2nkAYAZbwgKhMu8cW\",\"public_key\":\"PUB_K1_7qZ8nnmn6KBnjQL4oukyZFWCj8DmC9nJE2nkAYAZbwgKm7MD7V\",\"weight\":1}]},\"perm\":\"transfer\"},{\"threshold\":1,\"auth\":{\"accounts\":[],\"keys\":[{\"weight\":1,\"public_key\":\"PUB_K1_65NrHPVXaV4voxepQREmYCmnMJm4tAWdxPaK46CbUN1rrkfmPY\",\"pubkey\":\"EOS65NrHPVXaV4voxepQREmYCmnMJm4tAWdxPaK46CbUN1rrVmRzg\"}]},\"perm\":\"vote\"},{\"perm\":\"voting\",\"auth\":{\"accounts\":[],\"keys\":[{\"weight\":1,\"public_key\":\"PUB_K1_7pn6P5FftyNAKRfx9VcUzBFMvC4UitNbnoKbfxNe8SShE8XTzQ\",\"pubkey\":\"EOS7pn6P5FftyNAKRfx9VcUzBFMvC4UitNbnoKbfxNe8SShELo2it\"}]},\"threshold\":1}],\"teamgraymass\":[{\"auth\":{\"accounts\":[],\"keys\":[{\"pubkey\":\"EOS6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQ9kXZK1\",\"public_key\":\"PUB_K1_6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQBdT2GF\",\"weight\":1}]},\"threshold\":1,\"perm\":\"active\"},{\"perm\":\"owner\",\"auth\":{\"accounts\":[],\"keys\":[{\"public_key\":\"PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN\",\"pubkey\":\"EOS8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5RgwKN82\",\"weight\":1}]},\"threshold\":1}]},\"chain\":{\"network\":\"eos\",\"systoken\":\"EOS\",\"description\":\"EOS Mainnet\",\"rex_enabled\":1,\"sync\":0,\"block_time\":\"2026-03-24 19:56:52\",\"block_num\":490174690,\"chainid\":\"aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906\",\"decimals\":4,\"production\":1}},\"wax\":{\"accounts\":{}}}" +} \ No newline at end of file diff --git a/packages/light-api/test/data/40f27138e2999199dbae79e68222c2eac162a9e8.json b/packages/light-api/test/data/40f27138e2999199dbae79e68222c2eac162a9e8.json new file mode 100644 index 00000000..dde454c1 --- /dev/null +++ b/packages/light-api/test/data/40f27138e2999199dbae79e68222c2eac162a9e8.json @@ -0,0 +1,532 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/account/eos/teamgreymass", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": { + "delegated_from": [ + { + "cpu_weight": 0, + "del_from": "eos42freedom", + "net_weight": 1 + }, + { + "cpu_weight": 0, + "del_from": "teamgreymass", + "net_weight": 1 + } + ], + "balances": [ + { + "currency": "BKT", + "amount": "45245.6267", + "decimals": "4", + "contract": "betkingtoken" + }, + { + "decimals": "4", + "contract": "bgbgbgbgbgbg", + "amount": "0.1000", + "currency": "BG" + }, + { + "amount": "1000.0000", + "contract": "bingobetoken", + "decimals": "4", + "currency": "BINGO" + }, + { + "currency": "BITI", + "amount": "103.9999", + "decimals": "4", + "contract": "biteyebiteye" + }, + { + "currency": "BBT", + "decimals": "4", + "contract": "blockbasetkn", + "amount": "0.0012" + }, + { + "currency": "ADE", + "contract": "buildertoken", + "decimals": "4", + "amount": "32351.8580" + }, + { + "amount": "32358.858000", + "decimals": "6", + "contract": "cadeositoken", + "currency": "ADE" + }, + { + "currency": "OBTC", + "amount": "0.00000100", + "contract": "core.ogx", + "decimals": "8" + }, + { + "amount": "15004.7481", + "contract": "core.vaulta", + "decimals": "4", + "currency": "A" + }, + { + "amount": "18.0000", + "contract": "dhbonetokens", + "decimals": "4", + "currency": "DHB" + }, + { + "currency": "EAPP", + "amount": "2.0000", + "decimals": "4", + "contract": "eappeappp.tp" + }, + { + "currency": "EMOON", + "amount": "1000010000.00", + "contract": "emoonnetwork", + "decimals": "2" + }, + { + "currency": "BT", + "amount": "0.0080", + "contract": "eosbtextoken", + "decimals": "4" + }, + { + "currency": "CCC", + "amount": "202.0000", + "contract": "eoscccdotcom", + "decimals": "4" + }, + { + "currency": "EOS", + "amount": "5292.4309", + "decimals": "4", + "contract": "eosio.token" + }, + { + "currency": "POW", + "contract": "eosiopowcoin", + "decimals": "8", + "amount": "3.26194299" + }, + { + "currency": "ERCT", + "decimals": "4", + "contract": "eostory12345", + "amount": "100.0000" + }, + { + "amount": "0.0001", + "decimals": "4", + "contract": "epraofficial", + "currency": "EPRA" + }, + { + "currency": "EETH", + "amount": "5000.0000", + "decimals": "4", + "contract": "ethsidechain" + }, + { + "amount": "0.001", + "decimals": "3", + "contract": "everipediaiq", + "currency": "IQ" + }, + { + "currency": "LORD", + "contract": "gamelordcoin", + "decimals": "4", + "amount": "1.0000" + }, + { + "amount": "0.1914", + "decimals": "4", + "contract": "hashbabycoin", + "currency": "HBGO" + }, + { + "currency": "HVT", + "amount": "19908.0137", + "decimals": "4", + "contract": "hirevibeshvt" + }, + { + "currency": "MVC", + "amount": "0.0001", + "decimals": "4", + "contract": "mvcoin.x" + }, + { + "decimals": "8", + "contract": "okkkkkkkkkkk", + "amount": "2.00000000", + "currency": "CZB" + }, + { + "contract": "okkkkkkkkkkk", + "decimals": "6", + "amount": "53.660000", + "currency": "LED" + }, + { + "amount": "280000.0000", + "contract": "onessusblock", + "decimals": "4", + "currency": "VOID" + }, + { + "decimals": "4", + "contract": "oo1122334455", + "amount": "2000.0000", + "currency": "IPOS" + }, + { + "currency": "BRM", + "amount": "479.605", + "decimals": "3", + "contract": "openbrmeos11" + }, + { + "currency": "PIZZA", + "amount": "47.4718", + "contract": "pizzatotoken", + "decimals": "4" + }, + { + "currency": "PLAY", + "amount": "100.0000", + "contract": "play.pomelo", + "decimals": "4" + }, + { + "amount": "11438.5283", + "decimals": "4", + "contract": "playeronetkn", + "currency": "PLO" + }, + { + "decimals": "4", + "contract": "pumlhealthio", + "amount": "12.7777", + "currency": "PUML" + }, + { + "currency": "RWC", + "amount": "5.0000", + "decimals": "4", + "contract": "realworldcpn" + }, + { + "amount": "1.0000", + "contract": "stipsoculus1", + "decimals": "4", + "currency": "STIPS" + }, + { + "currency": "TOOK", + "amount": "47988.8846", + "decimals": "4", + "contract": "taketooktook" + }, + { + "amount": "0.0000", + "decimals": "4", + "contract": "tethertether", + "currency": "USDT" + }, + { + "currency": "EFOR", + "contract": "theforcegrou", + "decimals": "4", + "amount": "100.0000" + }, + { + "decimals": "4", + "contract": "thepeostoken", + "amount": "14380.2999", + "currency": "PEOS" + }, + { + "currency": "ZEOS", + "amount": "68450.2273", + "contract": "thezeostoken", + "decimals": "4" + }, + { + "amount": "1", + "contract": "token.rms", + "decimals": "0", + "currency": "V" + }, + { + "currency": "LIBRA", + "contract": "usdcoinchain", + "decimals": "4", + "amount": "16100.0000" + }, + { + "currency": "VIG", + "amount": "375000.0000", + "contract": "vig111111111", + "decimals": "4" + }, + { + "decimals": "0", + "contract": "visionfintec", + "amount": "1", + "currency": "RED" + }, + { + "amount": "914665.7776", + "decimals": "4", + "contract": "zerotoone.en", + "currency": "CDOG" + } + ], + "linkauth": [ + { + "type": "claimrewards", + "requirement": "claim", + "code": "core.vaulta" + }, + { + "code": "core.vaulta", + "type": "transfer", + "requirement": "transfer" + }, + { + "code": "decentiumorg", + "requirement": "decentium", + "type": "" + }, + { + "requirement": "oracle", + "type": "write", + "code": "delphioracle" + }, + { + "type": "claimrewards", + "requirement": "claim", + "code": "eosio" + }, + { + "code": "eosio", + "requirement": "killswitch", + "type": "unregprod" + }, + { + "requirement": "vote", + "type": "voteproducer", + "code": "eosio" + }, + { + "type": "claimrewards", + "requirement": "claim", + "code": "eosio.bpay" + }, + { + "requirement": "voting", + "type": "unvote", + "code": "eosio.forum" + }, + { + "type": "vote", + "requirement": "voting", + "code": "eosio.forum" + }, + { + "code": "eosio.token", + "requirement": "transfer", + "type": "transfer" + }, + { + "type": "set", + "requirement": "producerjson", + "code": "producerjson" + } + ], + "permissions": [ + { + "auth": { + "keys": [ + { + "pubkey": "EOS6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQ9kXZK1", + "public_key": "PUB_K1_6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQBdT2GF", + "weight": 1 + } + ], + "accounts": [] + }, + "threshold": 1, + "perm": "active" + }, + { + "auth": { + "accounts": [], + "keys": [ + { + "public_key": "PUB_K1_6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFFzdpw", + "pubkey": "EOS6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFtED4C", + "weight": 1 + } + ] + }, + "threshold": 1, + "perm": "claim" + }, + { + "perm": "decentium", + "threshold": 1, + "auth": { + "keys": [ + { + "public_key": "PUB_K1_7knG7M5TUEdRv1bkVjTPddVoDQnwS7oEZXAgFk3A4hhoXjv3GL", + "pubkey": "EOS7knG7M5TUEdRv1bkVjTPddVoDQnwS7oEZXAgFk3A4hhocA3eJf", + "weight": 1 + } + ], + "accounts": [] + } + }, + { + "perm": "killswitch", + "auth": { + "accounts": [], + "keys": [ + { + "weight": 1, + "pubkey": "EOS7CjC7GL71msPzAuAzd2WwiBEAzTcPL47ACrjSuiNmnnGGufYSn", + "public_key": "PUB_K1_7CjC7GL71msPzAuAzd2WwiBEAzTcPL47ACrjSuiNmnnGHEHtup" + } + ] + }, + "threshold": 1 + }, + { + "perm": "oracle", + "auth": { + "keys": [ + { + "weight": 1, + "public_key": "PUB_K1_88VqmDmJJ9S23eNqdeWYf2zySxv3ckQrWBKy7EvVRCUuhEDJJt", + "pubkey": "EOS88VqmDmJJ9S23eNqdeWYf2zySxv3ckQrWBKy7EvVRCUuhSU4f3" + } + ], + "accounts": [] + }, + "threshold": 1 + }, + { + "auth": { + "keys": [ + { + "weight": 1, + "pubkey": "EOS8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5RgwKN82", + "public_key": "PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN" + } + ], + "accounts": [] + }, + "threshold": 1, + "perm": "owner" + }, + { + "auth": { + "accounts": [], + "keys": [ + { + "weight": 1, + "public_key": "PUB_K1_5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrGVwUnM", + "pubkey": "EOS5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrDa6Etp" + } + ] + }, + "threshold": 1, + "perm": "producerjson" + }, + { + "perm": "transfer", + "auth": { + "accounts": [], + "keys": [ + { + "weight": 1, + "public_key": "PUB_K1_7qZ8nnmn6KBnjQL4oukyZFWCj8DmC9nJE2nkAYAZbwgKm7MD7V", + "pubkey": "EOS7qZ8nnmn6KBnjQL4oukyZFWCj8DmC9nJE2nkAYAZbwgKhMu8cW" + } + ] + }, + "threshold": 1 + }, + { + "threshold": 1, + "auth": { + "keys": [ + { + "weight": 1, + "public_key": "PUB_K1_65NrHPVXaV4voxepQREmYCmnMJm4tAWdxPaK46CbUN1rrkfmPY", + "pubkey": "EOS65NrHPVXaV4voxepQREmYCmnMJm4tAWdxPaK46CbUN1rrVmRzg" + } + ], + "accounts": [] + }, + "perm": "vote" + }, + { + "perm": "voting", + "auth": { + "accounts": [], + "keys": [ + { + "public_key": "PUB_K1_7pn6P5FftyNAKRfx9VcUzBFMvC4UitNbnoKbfxNe8SShE8XTzQ", + "pubkey": "EOS7pn6P5FftyNAKRfx9VcUzBFMvC4UitNbnoKbfxNe8SShELo2it", + "weight": 1 + } + ] + }, + "threshold": 1 + } + ], + "account_name": "teamgreymass", + "delegated_to": [ + { + "net_weight": 1, + "account_name": "teamgreymass", + "cpu_weight": 0 + } + ], + "rex": { + "fund": "0.0000 EOS", + "matured": "0.0000 EOS", + "savings": "0.0000 EOS", + "maturing": "0.0000 EOS" + }, + "chain": { + "chainid": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906", + "decimals": 4, + "production": 1, + "description": "EOS Mainnet", + "systoken": "EOS", + "network": "eos", + "rex_enabled": 1, + "block_time": "2026-03-24 19:56:46", + "sync": -1, + "block_num": 490174677 + }, + "resources": { + "net_weight": 2, + "ram_bytes": 818870, + "cpu_weight": 0 + } + }, + "text": "{\"delegated_from\":[{\"cpu_weight\":0,\"del_from\":\"eos42freedom\",\"net_weight\":1},{\"cpu_weight\":0,\"del_from\":\"teamgreymass\",\"net_weight\":1}],\"balances\":[{\"currency\":\"BKT\",\"amount\":\"45245.6267\",\"decimals\":\"4\",\"contract\":\"betkingtoken\"},{\"decimals\":\"4\",\"contract\":\"bgbgbgbgbgbg\",\"amount\":\"0.1000\",\"currency\":\"BG\"},{\"amount\":\"1000.0000\",\"contract\":\"bingobetoken\",\"decimals\":\"4\",\"currency\":\"BINGO\"},{\"currency\":\"BITI\",\"amount\":\"103.9999\",\"decimals\":\"4\",\"contract\":\"biteyebiteye\"},{\"currency\":\"BBT\",\"decimals\":\"4\",\"contract\":\"blockbasetkn\",\"amount\":\"0.0012\"},{\"currency\":\"ADE\",\"contract\":\"buildertoken\",\"decimals\":\"4\",\"amount\":\"32351.8580\"},{\"amount\":\"32358.858000\",\"decimals\":\"6\",\"contract\":\"cadeositoken\",\"currency\":\"ADE\"},{\"currency\":\"OBTC\",\"amount\":\"0.00000100\",\"contract\":\"core.ogx\",\"decimals\":\"8\"},{\"amount\":\"15004.7481\",\"contract\":\"core.vaulta\",\"decimals\":\"4\",\"currency\":\"A\"},{\"amount\":\"18.0000\",\"contract\":\"dhbonetokens\",\"decimals\":\"4\",\"currency\":\"DHB\"},{\"currency\":\"EAPP\",\"amount\":\"2.0000\",\"decimals\":\"4\",\"contract\":\"eappeappp.tp\"},{\"currency\":\"EMOON\",\"amount\":\"1000010000.00\",\"contract\":\"emoonnetwork\",\"decimals\":\"2\"},{\"currency\":\"BT\",\"amount\":\"0.0080\",\"contract\":\"eosbtextoken\",\"decimals\":\"4\"},{\"currency\":\"CCC\",\"amount\":\"202.0000\",\"contract\":\"eoscccdotcom\",\"decimals\":\"4\"},{\"currency\":\"EOS\",\"amount\":\"5292.4309\",\"decimals\":\"4\",\"contract\":\"eosio.token\"},{\"currency\":\"POW\",\"contract\":\"eosiopowcoin\",\"decimals\":\"8\",\"amount\":\"3.26194299\"},{\"currency\":\"ERCT\",\"decimals\":\"4\",\"contract\":\"eostory12345\",\"amount\":\"100.0000\"},{\"amount\":\"0.0001\",\"decimals\":\"4\",\"contract\":\"epraofficial\",\"currency\":\"EPRA\"},{\"currency\":\"EETH\",\"amount\":\"5000.0000\",\"decimals\":\"4\",\"contract\":\"ethsidechain\"},{\"amount\":\"0.001\",\"decimals\":\"3\",\"contract\":\"everipediaiq\",\"currency\":\"IQ\"},{\"currency\":\"LORD\",\"contract\":\"gamelordcoin\",\"decimals\":\"4\",\"amount\":\"1.0000\"},{\"amount\":\"0.1914\",\"decimals\":\"4\",\"contract\":\"hashbabycoin\",\"currency\":\"HBGO\"},{\"currency\":\"HVT\",\"amount\":\"19908.0137\",\"decimals\":\"4\",\"contract\":\"hirevibeshvt\"},{\"currency\":\"MVC\",\"amount\":\"0.0001\",\"decimals\":\"4\",\"contract\":\"mvcoin.x\"},{\"decimals\":\"8\",\"contract\":\"okkkkkkkkkkk\",\"amount\":\"2.00000000\",\"currency\":\"CZB\"},{\"contract\":\"okkkkkkkkkkk\",\"decimals\":\"6\",\"amount\":\"53.660000\",\"currency\":\"LED\"},{\"amount\":\"280000.0000\",\"contract\":\"onessusblock\",\"decimals\":\"4\",\"currency\":\"VOID\"},{\"decimals\":\"4\",\"contract\":\"oo1122334455\",\"amount\":\"2000.0000\",\"currency\":\"IPOS\"},{\"currency\":\"BRM\",\"amount\":\"479.605\",\"decimals\":\"3\",\"contract\":\"openbrmeos11\"},{\"currency\":\"PIZZA\",\"amount\":\"47.4718\",\"contract\":\"pizzatotoken\",\"decimals\":\"4\"},{\"currency\":\"PLAY\",\"amount\":\"100.0000\",\"contract\":\"play.pomelo\",\"decimals\":\"4\"},{\"amount\":\"11438.5283\",\"decimals\":\"4\",\"contract\":\"playeronetkn\",\"currency\":\"PLO\"},{\"decimals\":\"4\",\"contract\":\"pumlhealthio\",\"amount\":\"12.7777\",\"currency\":\"PUML\"},{\"currency\":\"RWC\",\"amount\":\"5.0000\",\"decimals\":\"4\",\"contract\":\"realworldcpn\"},{\"amount\":\"1.0000\",\"contract\":\"stipsoculus1\",\"decimals\":\"4\",\"currency\":\"STIPS\"},{\"currency\":\"TOOK\",\"amount\":\"47988.8846\",\"decimals\":\"4\",\"contract\":\"taketooktook\"},{\"amount\":\"0.0000\",\"decimals\":\"4\",\"contract\":\"tethertether\",\"currency\":\"USDT\"},{\"currency\":\"EFOR\",\"contract\":\"theforcegrou\",\"decimals\":\"4\",\"amount\":\"100.0000\"},{\"decimals\":\"4\",\"contract\":\"thepeostoken\",\"amount\":\"14380.2999\",\"currency\":\"PEOS\"},{\"currency\":\"ZEOS\",\"amount\":\"68450.2273\",\"contract\":\"thezeostoken\",\"decimals\":\"4\"},{\"amount\":\"1\",\"contract\":\"token.rms\",\"decimals\":\"0\",\"currency\":\"V\"},{\"currency\":\"LIBRA\",\"contract\":\"usdcoinchain\",\"decimals\":\"4\",\"amount\":\"16100.0000\"},{\"currency\":\"VIG\",\"amount\":\"375000.0000\",\"contract\":\"vig111111111\",\"decimals\":\"4\"},{\"decimals\":\"0\",\"contract\":\"visionfintec\",\"amount\":\"1\",\"currency\":\"RED\"},{\"amount\":\"914665.7776\",\"decimals\":\"4\",\"contract\":\"zerotoone.en\",\"currency\":\"CDOG\"}],\"linkauth\":[{\"type\":\"claimrewards\",\"requirement\":\"claim\",\"code\":\"core.vaulta\"},{\"code\":\"core.vaulta\",\"type\":\"transfer\",\"requirement\":\"transfer\"},{\"code\":\"decentiumorg\",\"requirement\":\"decentium\",\"type\":\"\"},{\"requirement\":\"oracle\",\"type\":\"write\",\"code\":\"delphioracle\"},{\"type\":\"claimrewards\",\"requirement\":\"claim\",\"code\":\"eosio\"},{\"code\":\"eosio\",\"requirement\":\"killswitch\",\"type\":\"unregprod\"},{\"requirement\":\"vote\",\"type\":\"voteproducer\",\"code\":\"eosio\"},{\"type\":\"claimrewards\",\"requirement\":\"claim\",\"code\":\"eosio.bpay\"},{\"requirement\":\"voting\",\"type\":\"unvote\",\"code\":\"eosio.forum\"},{\"type\":\"vote\",\"requirement\":\"voting\",\"code\":\"eosio.forum\"},{\"code\":\"eosio.token\",\"requirement\":\"transfer\",\"type\":\"transfer\"},{\"type\":\"set\",\"requirement\":\"producerjson\",\"code\":\"producerjson\"}],\"permissions\":[{\"auth\":{\"keys\":[{\"pubkey\":\"EOS6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQ9kXZK1\",\"public_key\":\"PUB_K1_6gqJ7sdPgjHLFLtks9cRPs5qYHa9U3CwK4P2JasTLWKQBdT2GF\",\"weight\":1}],\"accounts\":[]},\"threshold\":1,\"perm\":\"active\"},{\"auth\":{\"accounts\":[],\"keys\":[{\"public_key\":\"PUB_K1_6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFFzdpw\",\"pubkey\":\"EOS6DLD9HxMcwn73U41jjdGsNe9vDFRKB26um6qTAqrtYcJFtED4C\",\"weight\":1}]},\"threshold\":1,\"perm\":\"claim\"},{\"perm\":\"decentium\",\"threshold\":1,\"auth\":{\"keys\":[{\"public_key\":\"PUB_K1_7knG7M5TUEdRv1bkVjTPddVoDQnwS7oEZXAgFk3A4hhoXjv3GL\",\"pubkey\":\"EOS7knG7M5TUEdRv1bkVjTPddVoDQnwS7oEZXAgFk3A4hhocA3eJf\",\"weight\":1}],\"accounts\":[]}},{\"perm\":\"killswitch\",\"auth\":{\"accounts\":[],\"keys\":[{\"weight\":1,\"pubkey\":\"EOS7CjC7GL71msPzAuAzd2WwiBEAzTcPL47ACrjSuiNmnnGGufYSn\",\"public_key\":\"PUB_K1_7CjC7GL71msPzAuAzd2WwiBEAzTcPL47ACrjSuiNmnnGHEHtup\"}]},\"threshold\":1},{\"perm\":\"oracle\",\"auth\":{\"keys\":[{\"weight\":1,\"public_key\":\"PUB_K1_88VqmDmJJ9S23eNqdeWYf2zySxv3ckQrWBKy7EvVRCUuhEDJJt\",\"pubkey\":\"EOS88VqmDmJJ9S23eNqdeWYf2zySxv3ckQrWBKy7EvVRCUuhSU4f3\"}],\"accounts\":[]},\"threshold\":1},{\"auth\":{\"keys\":[{\"weight\":1,\"pubkey\":\"EOS8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5RgwKN82\",\"public_key\":\"PUB_K1_8QzGtCea2thiqcTVeXGdyRZpdKYptQznbcWSMj73FD5Rgra4mN\"}],\"accounts\":[]},\"threshold\":1,\"perm\":\"owner\"},{\"auth\":{\"accounts\":[],\"keys\":[{\"weight\":1,\"public_key\":\"PUB_K1_5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrGVwUnM\",\"pubkey\":\"EOS5JCEciUdfXnQmTyj85T98bXTAZZ1g7Nmajseu7ZWB8DrDa6Etp\"}]},\"threshold\":1,\"perm\":\"producerjson\"},{\"perm\":\"transfer\",\"auth\":{\"accounts\":[],\"keys\":[{\"weight\":1,\"public_key\":\"PUB_K1_7qZ8nnmn6KBnjQL4oukyZFWCj8DmC9nJE2nkAYAZbwgKm7MD7V\",\"pubkey\":\"EOS7qZ8nnmn6KBnjQL4oukyZFWCj8DmC9nJE2nkAYAZbwgKhMu8cW\"}]},\"threshold\":1},{\"threshold\":1,\"auth\":{\"keys\":[{\"weight\":1,\"public_key\":\"PUB_K1_65NrHPVXaV4voxepQREmYCmnMJm4tAWdxPaK46CbUN1rrkfmPY\",\"pubkey\":\"EOS65NrHPVXaV4voxepQREmYCmnMJm4tAWdxPaK46CbUN1rrVmRzg\"}],\"accounts\":[]},\"perm\":\"vote\"},{\"perm\":\"voting\",\"auth\":{\"accounts\":[],\"keys\":[{\"public_key\":\"PUB_K1_7pn6P5FftyNAKRfx9VcUzBFMvC4UitNbnoKbfxNe8SShE8XTzQ\",\"pubkey\":\"EOS7pn6P5FftyNAKRfx9VcUzBFMvC4UitNbnoKbfxNe8SShELo2it\",\"weight\":1}]},\"threshold\":1}],\"account_name\":\"teamgreymass\",\"delegated_to\":[{\"net_weight\":1,\"account_name\":\"teamgreymass\",\"cpu_weight\":0}],\"rex\":{\"fund\":\"0.0000 EOS\",\"matured\":\"0.0000 EOS\",\"savings\":\"0.0000 EOS\",\"maturing\":\"0.0000 EOS\"},\"chain\":{\"chainid\":\"aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906\",\"decimals\":4,\"production\":1,\"description\":\"EOS Mainnet\",\"systoken\":\"EOS\",\"network\":\"eos\",\"rex_enabled\":1,\"block_time\":\"2026-03-24 19:56:46\",\"sync\":-1,\"block_num\":490174677},\"resources\":{\"net_weight\":2,\"ram_bytes\":818870,\"cpu_weight\":0}}" +} \ No newline at end of file diff --git a/packages/light-api/test/data/5529e20488f6064fd59e355db1f640baa491b34e.json b/packages/light-api/test/data/5529e20488f6064fd59e355db1f640baa491b34e.json new file mode 100644 index 00000000..17993445 --- /dev/null +++ b/packages/light-api/test/data/5529e20488f6064fd59e355db1f640baa491b34e.json @@ -0,0 +1,32 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/rexbalance/eos/teamgreymass", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": { + "account_name": "teamgreymass", + "chain": { + "chainid": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906", + "production": 1, + "decimals": 4, + "block_num": 490174681, + "description": "EOS Mainnet", + "network": "eos", + "systoken": "EOS", + "rex_enabled": 1, + "block_time": "2026-03-24 19:56:48", + "sync": 0 + }, + "rex": { + "maturing": "0.0000 EOS", + "matured": "0.0000 EOS", + "savings": "0.0000 EOS", + "fund": "0.0000 EOS" + } + }, + "text": "{\"account_name\":\"teamgreymass\",\"chain\":{\"chainid\":\"aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906\",\"production\":1,\"decimals\":4,\"block_num\":490174681,\"description\":\"EOS Mainnet\",\"network\":\"eos\",\"systoken\":\"EOS\",\"rex_enabled\":1,\"block_time\":\"2026-03-24 19:56:48\",\"sync\":0},\"rex\":{\"maturing\":\"0.0000 EOS\",\"matured\":\"0.0000 EOS\",\"savings\":\"0.0000 EOS\",\"fund\":\"0.0000 EOS\"}}" +} \ No newline at end of file diff --git a/packages/light-api/test/data/6785bb48e9f50a77651818282bbc44930b7cf26a.json b/packages/light-api/test/data/6785bb48e9f50a77651818282bbc44930b7cf26a.json new file mode 100644 index 00000000..1746665d --- /dev/null +++ b/packages/light-api/test/data/6785bb48e9f50a77651818282bbc44930b7cf26a.json @@ -0,0 +1,33 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/codehash/0a16e1dac533c4558698c8754f41219839ba2a2b75e517e65ea2537f76681f49", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": { + "eos": { + "accounts": { + "eosio.token": { + "account_name": "eosio.token", + "code_hash": "0a16e1dac533c4558698c8754f41219839ba2a2b75e517e65ea2537f76681f49" + } + }, + "chain": { + "chainid": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906", + "production": 1, + "decimals": 4, + "block_num": 490174690, + "block_time": "2026-03-24 19:56:52", + "sync": 0, + "systoken": "EOS", + "description": "EOS Mainnet", + "rex_enabled": 1, + "network": "eos" + } + } + }, + "text": "{\"eos\":{\"accounts\":{\"eosio.token\":{\"account_name\":\"eosio.token\",\"code_hash\":\"0a16e1dac533c4558698c8754f41219839ba2a2b75e517e65ea2537f76681f49\"}},\"chain\":{\"chainid\":\"aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906\",\"production\":1,\"decimals\":4,\"block_num\":490174690,\"block_time\":\"2026-03-24 19:56:52\",\"sync\":0,\"systoken\":\"EOS\",\"description\":\"EOS Mainnet\",\"rex_enabled\":1,\"network\":\"eos\"}}}" +} \ No newline at end of file diff --git a/packages/light-api/test/data/6c221bb81dca5afcffe02c519146744e0a739a55.json b/packages/light-api/test/data/6c221bb81dca5afcffe02c519146744e0a739a55.json new file mode 100644 index 00000000..d80f2a37 --- /dev/null +++ b/packages/light-api/test/data/6c221bb81dca5afcffe02c519146744e0a739a55.json @@ -0,0 +1,53 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/topholders/eos/eosio.token/EOS/10", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": [ + [ + "core.vaulta", + "1035816036.0540" + ], + [ + "eosio", + "469320647.0673" + ], + [ + "eosio.rex", + "273740418.5797" + ], + [ + "eosio.stake", + "109961016.8048" + ], + [ + "aslkggpoerss", + "29705844.7001" + ], + [ + "kjhbgvcfghfd", + "27249789.7943" + ], + [ + "eosio.ram", + "23933530.1365" + ], + [ + "vcxydi5dfufx", + "15874425.2354" + ], + [ + "ixhu2a4twcnu", + "11526480.4604" + ], + [ + "csg2ys5bznni", + "11059379.9642" + ] + ], + "text": "[[\"core.vaulta\",\"1035816036.0540\"],[\"eosio\",\"469320647.0673\"],[\"eosio.rex\",\"273740418.5797\"],[\"eosio.stake\",\"109961016.8048\"],[\"aslkggpoerss\",\"29705844.7001\"],[\"kjhbgvcfghfd\",\"27249789.7943\"],[\"eosio.ram\",\"23933530.1365\"],[\"vcxydi5dfufx\",\"15874425.2354\"],[\"ixhu2a4twcnu\",\"11526480.4604\"],[\"csg2ys5bznni\",\"11059379.9642\"]]" +} \ No newline at end of file diff --git a/packages/light-api/test/data/8aaccf7201f03407312a7ef95d40fe838c235c74.json b/packages/light-api/test/data/8aaccf7201f03407312a7ef95d40fe838c235c74.json new file mode 100644 index 00000000..db7e183d --- /dev/null +++ b/packages/light-api/test/data/8aaccf7201f03407312a7ef95d40fe838c235c74.json @@ -0,0 +1,12 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/usercount/eos", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": 6404414, + "text": "6404414" +} \ No newline at end of file diff --git a/packages/light-api/test/data/abd4daa214c5daf5b52e7935e6fdc1b809087b37.json b/packages/light-api/test/data/abd4daa214c5daf5b52e7935e6fdc1b809087b37.json new file mode 100644 index 00000000..63e8deea --- /dev/null +++ b/packages/light-api/test/data/abd4daa214c5daf5b52e7935e6fdc1b809087b37.json @@ -0,0 +1,68 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/networks", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": [ + { + "network": "eos", + "description": "EOS Mainnet", + "systoken": "EOS", + "sync": 0, + "block_time": "2026-03-24 19:56:45", + "block_num": 490174676, + "decimals": 4, + "production": 1, + "chainid": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906" + }, + { + "production": 1, + "decimals": 4, + "chainid": "38b1d7815474d0c60683ecbea321d723e83f5da6ae5f1c1f9fecc69d9ba96465", + "block_num": 234577035, + "block_time": "2026-03-24 19:56:46", + "sync": -1, + "systoken": "LIBRE", + "description": "Libre", + "network": "libre" + }, + { + "block_time": "2026-03-24 19:56:46", + "sync": -1, + "description": "Proton", + "systoken": "SYS", + "network": "proton", + "block_num": 373393006, + "decimals": 4, + "production": 1, + "chainid": "384da888112027f0321850a169f737c33e53b388aad48b5adace4bab97f437e0" + }, + { + "production": 1, + "decimals": 4, + "chainid": "4667b205c6838ef70ff7988f6e8257e8be0e1284a2f59699054a018f743b1d11", + "block_num": 458754388, + "network": "telos", + "description": "Telos Mainnet", + "systoken": "TLOS", + "block_time": "2026-03-24 19:56:45", + "sync": 0 + }, + { + "block_num": 425800206, + "description": "WAX", + "systoken": "WAX", + "network": "wax", + "block_time": "2026-03-24 19:56:46", + "sync": -1, + "production": 1, + "decimals": 8, + "chainid": "1064487b3cd1a897ce03ae5b6a865651747e2e152090f99c1d19d44e01aea5a4" + } + ], + "text": "[{\"network\":\"eos\",\"description\":\"EOS Mainnet\",\"systoken\":\"EOS\",\"sync\":0,\"block_time\":\"2026-03-24 19:56:45\",\"block_num\":490174676,\"decimals\":4,\"production\":1,\"chainid\":\"aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906\"},{\"production\":1,\"decimals\":4,\"chainid\":\"38b1d7815474d0c60683ecbea321d723e83f5da6ae5f1c1f9fecc69d9ba96465\",\"block_num\":234577035,\"block_time\":\"2026-03-24 19:56:46\",\"sync\":-1,\"systoken\":\"LIBRE\",\"description\":\"Libre\",\"network\":\"libre\"},{\"block_time\":\"2026-03-24 19:56:46\",\"sync\":-1,\"description\":\"Proton\",\"systoken\":\"SYS\",\"network\":\"proton\",\"block_num\":373393006,\"decimals\":4,\"production\":1,\"chainid\":\"384da888112027f0321850a169f737c33e53b388aad48b5adace4bab97f437e0\"},{\"production\":1,\"decimals\":4,\"chainid\":\"4667b205c6838ef70ff7988f6e8257e8be0e1284a2f59699054a018f743b1d11\",\"block_num\":458754388,\"network\":\"telos\",\"description\":\"Telos Mainnet\",\"systoken\":\"TLOS\",\"block_time\":\"2026-03-24 19:56:45\",\"sync\":0},{\"block_num\":425800206,\"description\":\"WAX\",\"systoken\":\"WAX\",\"network\":\"wax\",\"block_time\":\"2026-03-24 19:56:46\",\"sync\":-1,\"production\":1,\"decimals\":8,\"chainid\":\"1064487b3cd1a897ce03ae5b6a865651747e2e152090f99c1d19d44e01aea5a4\"}]" +} \ No newline at end of file diff --git a/packages/light-api/test/data/dcbc5198194b7a845ea8ab791294b081b4a93b10.json b/packages/light-api/test/data/dcbc5198194b7a845ea8ab791294b081b4a93b10.json new file mode 100644 index 00000000..8555a7ec --- /dev/null +++ b/packages/light-api/test/data/dcbc5198194b7a845ea8ab791294b081b4a93b10.json @@ -0,0 +1,53 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/topram/eos/10", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": [ + [ + "utxomng.xsat", + 132071612727 + ], + [ + "bank.rms", + 68719482353 + ], + [ + "fund.wram", + 45181398797 + ], + [ + "eosiseosbest", + 8724350455 + ], + [ + "eosholdereos", + 4626710307 + ], + [ + "eosio.evm", + 3382328498 + ], + [ + "honeybull.gm", + 2827247853 + ], + [ + "admin.grants", + 2812889744 + ], + [ + "he1lexwltone", + 2034954695 + ], + [ + "bitbnsglobal", + 1799117676 + ] + ], + "text": "[[\"utxomng.xsat\",132071612727],[\"bank.rms\",68719482353],[\"fund.wram\",45181398797],[\"eosiseosbest\",8724350455],[\"eosholdereos\",4626710307],[\"eosio.evm\",3382328498],[\"honeybull.gm\",2827247853],[\"admin.grants\",2812889744],[\"he1lexwltone\",2034954695],[\"bitbnsglobal\",1799117676]]" +} \ No newline at end of file diff --git a/packages/light-api/test/data/dea05f9de102415a27ac61c316fee37de0c03535.json b/packages/light-api/test/data/dea05f9de102415a27ac61c316fee37de0c03535.json new file mode 100644 index 00000000..b5cadea7 --- /dev/null +++ b/packages/light-api/test/data/dea05f9de102415a27ac61c316fee37de0c03535.json @@ -0,0 +1,12 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/tokenbalance/eos/teamgreymass/eosio.token/EOS", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": 5292.4309, + "text": "5292.4309" +} \ No newline at end of file diff --git a/packages/light-api/test/data/e2123eace094f9c3dd9713915c5d4550e361cc72.json b/packages/light-api/test/data/e2123eace094f9c3dd9713915c5d4550e361cc72.json new file mode 100644 index 00000000..9061121a --- /dev/null +++ b/packages/light-api/test/data/e2123eace094f9c3dd9713915c5d4550e361cc72.json @@ -0,0 +1,38 @@ +{ + "request": { + "path": "https://eos.light-api.net/api/rexraw/eos/teamgreymass", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": { + "rexraw": { + "fund": "0.000000000000000000000000", + "pool": { + "total_rent": "3796.235300000000000000000000", + "namebid_proceeds": "0.000000000000000000000000", + "total_lendable": "59394726.320200000000000000000000", + "total_rex": "587072315799.259000000000000000000000", + "total_lent": "7435673.204700000000000000000000", + "total_unlent": "51959053.115500000000000000000000", + "loan_num": 496487 + } + }, + "chain": { + "chainid": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906", + "production": 1, + "decimals": 4, + "block_num": 490174685, + "sync": 0, + "block_time": "2026-03-24 19:56:50", + "rex_enabled": 1, + "network": "eos", + "systoken": "EOS", + "description": "EOS Mainnet" + }, + "account_name": "teamgreymass" + }, + "text": "{\"rexraw\":{\"fund\":\"0.000000000000000000000000\",\"pool\":{\"total_rent\":\"3796.235300000000000000000000\",\"namebid_proceeds\":\"0.000000000000000000000000\",\"total_lendable\":\"59394726.320200000000000000000000\",\"total_rex\":\"587072315799.259000000000000000000000\",\"total_lent\":\"7435673.204700000000000000000000\",\"total_unlent\":\"51959053.115500000000000000000000\",\"loan_num\":496487}},\"chain\":{\"chainid\":\"aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906\",\"production\":1,\"decimals\":4,\"block_num\":490174685,\"sync\":0,\"block_time\":\"2026-03-24 19:56:50\",\"rex_enabled\":1,\"network\":\"eos\",\"systoken\":\"EOS\",\"description\":\"EOS Mainnet\"},\"account_name\":\"teamgreymass\"}" +} \ No newline at end of file diff --git a/packages/light-api/test/tsconfig.json b/packages/light-api/test/tsconfig.json new file mode 100644 index 00000000..472a9a18 --- /dev/null +++ b/packages/light-api/test/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": [ + "../tsconfig.json", + "../../../tsconfig.test.json" + ], + "compilerOptions": { + "baseUrl": "..", + "paths": { + "$lib": [ + "src" + ], + "$test": [ + "test" + ], + "$test/*": [ + "test/*" + ] + } + }, + "include": [ + "*.ts", + "**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/light-api/tsconfig.json b/packages/light-api/tsconfig.json new file mode 100644 index 00000000..4e6c6ce1 --- /dev/null +++ b/packages/light-api/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [ + "src/**/*" + ] +} diff --git a/packages/mock-data/package.json b/packages/mock-data/package.json index ad5bf278..4e98cb0c 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/msigs/package.json b/packages/msigs/package.json index abf47b72..46849f61 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/protocol-esr/package.json b/packages/protocol-esr/package.json index 046adf6f..ade28985 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/protocol-scatter/package.json b/packages/protocol-scatter/package.json index 0d1da70f..6d20891c 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/resources/package.json b/packages/resources/package.json index 51408eab..b07d38d2 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/roborovski/package.json b/packages/roborovski/package.json index 2ef406a6..361b8f33 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/sealed-messages/package.json b/packages/sealed-messages/package.json index b22c8582..b49dcdcf 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/packages/session/package.json b/packages/session/package.json index 1f1a7f98..0a53693c 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com/docs/session-kit", "license": "BSD-3-Clause", "engines": { diff --git a/packages/session/src/kit.ts b/packages/session/src/kit.ts index 696e7ec6..c5f5a133 100644 --- a/packages/session/src/kit.ts +++ b/packages/session/src/kit.ts @@ -622,6 +622,13 @@ export class SessionKit { } } + /** + * Log a session out, or every session when none is named. + * + * Logging out a session affects only that session: other sessions keep their + * `default` flags, and the default `session` key is cleared only when it held + * the session being logged out. Nothing is promoted in its place. + */ async logout(session?: SessionType, options: LogoutOptions = {}) { if (!this.storage) { throw new Error('An instance of Storage must be provided to utilize the logout method.') @@ -634,12 +641,16 @@ export class SessionKit { await walletPlugin.logout(this.logoutParams(session, walletPlugin)) } - await this.storage.remove('session') + const equalityFn = options.equalityFn || this.equalityFn + + const current = await this.storage.read('session') + if (current && equalityFn(JSON.parse(current), session)) { + await this.storage.remove('session') + } // Every session, not getSessions(): its plugin filter would drop unregistered ones here const sessions = await this.readAllSessions() if (sessions.length) { - const equalityFn = options.equalityFn || this.equalityFn const other = sessions.filter((s) => !equalityFn(s, session)) await this.storage.write('sessions', JSON.stringify(other)) } diff --git a/packages/session/test/tests/kit.ts b/packages/session/test/tests/kit.ts index 23e45e7e..4c4f7da4 100644 --- a/packages/session/test/tests/kit.ts +++ b/packages/session/test/tests/kit.ts @@ -442,6 +442,98 @@ suite('kit', function () { const sessionsAfterLogout = await sessionKit.getSessions() assert.lengthOf(sessionsAfterLogout, 0) }) + test('logging out another session keeps the default restorable', async function () { + const storage = new MockStorage() + const kit = new SessionKit(mockSessionKitArgs, {...mockSessionKitOptions, storage}) + + const defaultSession = makeSession('aaa') + await kit.persistSession(defaultSession) + const other = makeSession('zzz') + await kit.persistSession(other, {setAsDefault: false}) + + await kit.logout(other) + + const restored = await kit.restore() + assert.isDefined(restored) + assert.equal(String(restored?.actor), 'aaa') + + const remaining = JSON.parse(String(await storage.read('sessions'))) + assert.lengthOf(remaining, 1) + assert.equal(remaining[0].actor, 'aaa') + assert.isTrue(remaining[0].default) + }) + test('logging out a session on another chain keeps the default restorable', async function () { + const storage = new MockStorage() + const kit = new SessionKit(mockSessionKitArgs, {...mockSessionKitOptions, storage}) + + const defaultSession = makeSession('aaa') + await kit.persistSession(defaultSession) + const elsewhere = makeSession('zzz', {chain: Chains.EOS}) + await kit.persistSession(elsewhere, {setAsDefault: false}) + + await kit.logout(elsewhere) + + const restored = await kit.restore() + assert.isDefined(restored) + assert.equal(String(restored?.actor), 'aaa') + }) + test('logging out the default clears it without promoting another', async function () { + const storage = new MockStorage() + const kit = new SessionKit(mockSessionKitArgs, {...mockSessionKitOptions, storage}) + + const defaultSession = makeSession('aaa') + await kit.persistSession(defaultSession) + const other = makeSession('zzz') + await kit.persistSession(other, {setAsDefault: false}) + + await kit.logout(defaultSession) + + assert.isNotOk(await storage.read('session')) + + const remaining = JSON.parse(String(await storage.read('sessions'))) + assert.lengthOf(remaining, 1) + assert.equal(remaining[0].actor, 'zzz') + assert.isFalse(remaining[0].default) + }) + test('the default key and the list flags agree after every logout path', async function () { + const storage = new MockStorage() + const kit = new SessionKit(mockSessionKitArgs, {...mockSessionKitOptions, storage}) + + const assertAgreement = async function () { + const stored: any[] = JSON.parse(String((await storage.read('sessions')) || '[]')) + const key = await storage.read('session') + if (key) { + const held = JSON.parse(String(key)) + const match = stored.find( + (s) => + s.chain === held.chain && + s.actor === held.actor && + s.permission === held.permission + ) + assert.isDefined(match, 'the default key names a session still in the list') + assert.isTrue(match.default, 'the session the key names is flagged default') + } + for (const chain of new Set(stored.map((s) => s.chain))) { + const defaults = stored.filter((s) => s.chain === chain && s.default) + assert.isAtMost(defaults.length, 1, `one default at most on chain ${chain}`) + } + } + + const first = makeSession('aaa') + await kit.persistSession(first) + const second = makeSession('zzz') + await kit.persistSession(second, {setAsDefault: false}) + await assertAgreement() + + await kit.logout(second) + await assertAgreement() + + await kit.logout(first) + await assertAgreement() + + await kit.logout() + await assertAgreement() + }) test('retains sessions for unregistered wallet plugins', async function () { const storage = new MockStorage() const sessionKit = new SessionKit(mockSessionKitArgs, { diff --git a/packages/signing-request/package.json b/packages/signing-request/package.json index 5e5c5a01..52f60d98 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-rc7", + "version": "4.0.0", "description": "Signing Request (ESR / EEP-7) encoder and decoder for Antelope blockchains", "homepage": "https://wharfkit.com/docs/utilities/signing-request-library", "license": "BSD-3-Clause", diff --git a/packages/svelte-components/package.json b/packages/svelte-components/package.json index bb96cec5..5f449650 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-rc7", + "version": "4.0.0", "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 a9096610..50979c39 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-rc7", + "version": "4.0.0", "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 7897a63b..66c9398e 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-rc7", + "version": "4.0.0", "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 8118de4b..307449e2 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-rc7", + "version": "4.0.0", "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 deb213e2..4e1f31bf 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-rc7", + "version": "4.0.0", "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 35b00bdb..c98d1e71 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-rc7", + "version": "4.0.0", "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 1d37ce3f..0be048ae 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-rc7", + "version": "4.0.0", "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 6a03ee88..80259835 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-rc7", + "version": "4.0.0", "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 3a4caa96..96510ea3 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-rc7", + "version": "4.0.0", "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 a22a19ec..a96f5938 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-rc7", + "version": "4.0.0", "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 f1f73487..4e5708bc 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-rc7", + "version": "4.0.0", "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 a42ae6d5..e4841e8f 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-rc7", + "version": "4.0.0", "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 3795aaf3..b9503ec8 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-rc7", + "version": "4.0.0", "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 0f893013..bc435e13 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-rc7", + "version": "4.0.0", "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 12cb0cc0..6baa5b27 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-rc7", + "version": "4.0.0", "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 017d7991..84e96311 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-rc7", + "version": "4.0.0", "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 866ebee7..58905d96 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-rc7", + "version": "4.0.0", "private": true, "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-mock/package.json b/packages/wallet-plugin-mock/package.json index 41193cbb..c51e2ab2 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-rc7", + "version": "4.0.0", "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 823c051e..1a0dc2ff 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-rc7", + "version": "4.0.0", "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 1b6b9c25..b157b2de 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-rc7", + "version": "4.0.0", "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 295eb239..4cd9f258 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com/plugins/wallet-plugin-scatter", "license": "BSD-3-Clause", "engines": { diff --git a/packages/wallet-plugin-tacklebox/.editorconfig b/packages/wallet-plugin-tacklebox/.editorconfig new file mode 100644 index 00000000..779f99a1 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/packages/wallet-plugin-tacklebox/.gitignore b/packages/wallet-plugin-tacklebox/.gitignore new file mode 100644 index 00000000..75d3253c --- /dev/null +++ b/packages/wallet-plugin-tacklebox/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +lib/ +build/ + +# Yarn files +yarn-error.log diff --git a/packages/wallet-plugin-tacklebox/LICENSE b/packages/wallet-plugin-tacklebox/LICENSE new file mode 100644 index 00000000..1949a591 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2026 on-a-t-break & Greymass Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/wallet-plugin-tacklebox/Makefile b/packages/wallet-plugin-tacklebox/Makefile new file mode 100644 index 00000000..3543f49d --- /dev/null +++ b/packages/wallet-plugin-tacklebox/Makefile @@ -0,0 +1,3 @@ +MOCK_DIR := ./test/data + +include ../../common.mk diff --git a/packages/wallet-plugin-tacklebox/README.md b/packages/wallet-plugin-tacklebox/README.md new file mode 100644 index 00000000..a8e39826 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/README.md @@ -0,0 +1,115 @@ +TackleBox + +# @wharfkit/wallet-plugin-tacklebox + +A `WalletPlugin` for use with [`@wharfkit/session`](https://github.com/wharfkit/session) that lets +dapps login and sign transactions with [TackleBox](https://github.com/doyen-games/tacklebox), +the native wallet and block explorer for Antelope blockchains (EOS/Vaulta, WAX, Telos, and +friends). + +> TackleBox's dapp-link support is currently marked **experimental** in the wallet itself. The +> protocol is the anchor-link protocol (ESR identity requests, buoy callbacks, sealed push +> channels), so this plugin will keep working as the wallet feature matures. + +## How it works + +The flow mirrors Anchor's: requests open the wallet directly, with QR and copy-paste fallbacks. + +1. **Login** — the plugin fires a `tacklebox://request/…` deep link that opens TackleBox with + the ESR identity request (the wallet registers the `tacklebox:` scheme per user, and claims + `esr:` only when no other wallet owns it — so an Anchor install keeps its links). The prompt + keeps a **Launch TackleBox** link, a QR code and a **Copy login request** fallback + (paste target: **Contracts → ESR → CONNECT AS LOGIN**). TackleBox answers over a + [buoy](https://github.com/greymass/buoy) callback and announces its sealed push channel + (`link_ch` / `link_key` / `link_name`). +2. **Transact** — signing requests are sealed to the wallet's session key and pushed straight + into TackleBox over that channel; the wallet raises its own window for review. Every request + still passes TackleBox's whitelist guard, contract-hash verification and signing review + before a signature is produced and returned via callback. **Open TackleBox** and + **Sign manually instead** fallbacks cover a closed wallet or another device. + +The plugin is renderer-agnostic: it speaks only the abstract `UserInterface` contract +(prompts, translations, metadata), so it renders identically under +[`@wharfkit/web-renderer`](https://github.com/wharfkit/web-renderer), +[`@wharfkit/web-ui`](https://github.com/wharfkit/web-ui), or any custom session UI, and never +touches the host page's styling — TackleBox's presence in the modal comes through its logo and +prompt content, with theming left entirely to the integrating dapp. + +## Installation + +```bash +npm install @wharfkit/wallet-plugin-tacklebox +``` + +## Usage + +Include the plugin in the `walletPlugins` list when initializing the `SessionKit`: + +```ts +import {SessionKit} from '@wharfkit/session' +import {WebRenderer} from '@wharfkit/web-renderer' +import {WalletPluginTackleBox} from '@wharfkit/wallet-plugin-tacklebox' + +const sessionKit = new SessionKit({ + appName: 'myapp', + chains: [ + { + id: 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906', + url: 'https://eos.greymass.com', + }, + ], + ui: new WebRenderer(), + walletPlugins: [new WalletPluginTackleBox()], +}) + +const {session} = await sessionKit.login() +await session.transact({action}) +``` + +### Options + +```ts +new WalletPluginTackleBox({ + // Buoy callback-forwarder used for this dapp's callbacks. + // TackleBox's own listening channel is announced by the wallet at login. + buoyUrl: 'https://cb.anchor.link', + // WebSocket constructor override (defaults to isomorphic-ws). + buoyWs: WebSocket, + // Keep the tacklebox: deep link from firing automatically on login and + // manual signing; the prompt's launch link and fallbacks remain. + disableAutoLaunch: false, +}) +``` + +## Trying it live + +[`examples/dapp`](./examples/dapp) is a minimal browser dapp (login, self-transfer, restore, +logout) for exercising the plugin against a real TackleBox build: + +```bash +npm install && cd examples/dapp && npm install && npm run dev +``` + +## Developing + +This package is a member of the [wharfkit/js](https://github.com/wharfkit/js) monorepo. Install +the workspace once from the repository root with `bun install`, then work in this directory: + +```bash +make lib # bundle to lib/ (cjs, esm and types) +make test # run the mocha test suite +make check # lint +``` + +Tests replay recorded API responses from `test/data`; delete a file there to re-record it +against a live endpoint. + +## Credits + +Based on the [Wharfkit wallet plugin template](https://github.com/wharfkit/wallet-plugin-template) +and modeled on the [Anchor wallet plugin](https://github.com/wharfkit/wallet-plugin-anchor) by +[Greymass](https://greymass.com), whose anchor-link protocol TackleBox speaks. + +## License + +[BSD-3-Clause](./LICENSE) diff --git a/packages/wallet-plugin-tacklebox/assets/tacklebox.svg b/packages/wallet-plugin-tacklebox/assets/tacklebox.svg new file mode 100644 index 00000000..32e11662 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/assets/tacklebox.svg @@ -0,0 +1,15 @@ + + Tacklebox + An angular navy tacklebox with a teal lid, cyan circuit accents, twin latches, and a fishing hook emblem. + + + + + + + + + + + + diff --git a/packages/wallet-plugin-tacklebox/examples/dapp/README.md b/packages/wallet-plugin-tacklebox/examples/dapp/README.md new file mode 100644 index 00000000..32eac481 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/examples/dapp/README.md @@ -0,0 +1,24 @@ +# Example dapp + +A minimal browser dapp for exercising the plugin against a real TackleBox build: +login, a self-transfer, session restore and logout. + +```bash +# from the monorepo root: install and build the plugin first +bun install +make -C packages/wallet-plugin-tacklebox lib + +# then run the example +cd packages/wallet-plugin-tacklebox/examples/dapp +npm install +npm run dev +``` + +Open the printed URL, click **Login**, copy the request from the modal and paste it into +TackleBox under **Contracts → ESR → CONNECT AS LOGIN**. After that, **Transfer to self** +pushes a signing request straight into the wallet over the link channel — approve it in +TackleBox's review modal, or whitelist `eosio.token::transfer` for the account with a +pinned auto-sign rule to watch it complete with no wallet interaction at all. + +Jungle4 testnet is the first chain in the list — a throwaway jungle4 account is the +safest way to run the full flow. diff --git a/packages/wallet-plugin-tacklebox/examples/dapp/index.html b/packages/wallet-plugin-tacklebox/examples/dapp/index.html new file mode 100644 index 00000000..c97a7a12 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/examples/dapp/index.html @@ -0,0 +1,64 @@ + + + + + + + wallet-plugin-tacklebox example + + + +

wallet-plugin-tacklebox example

+

+ Login fires a tacklebox:// deep link that opens the wallet with the ESR + login request (QR and copy-paste into + Contracts → ESR → CONNECT AS LOGIN remain as fallbacks). Transfer + sends 0.0001 of the core token to yourself, pushed straight into the wallet over the + link channel — TackleBox raises its own window for review, and with a matching + pinned auto-sign whitelist rule it completes without touching the wallet. +

+
Not logged in.
+ + + +
--
+ + + diff --git a/packages/wallet-plugin-tacklebox/examples/dapp/package.json b/packages/wallet-plugin-tacklebox/examples/dapp/package.json new file mode 100644 index 00000000..b466b763 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/examples/dapp/package.json @@ -0,0 +1,19 @@ +{ + "name": "wallet-plugin-tacklebox-example", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@wharfkit/session": "4.0.0-rc7", + "@wharfkit/web-renderer": "4.0.0-rc7", + "@wharfkit/wallet-plugin-tacklebox": "file:../.." + }, + "devDependencies": { + "vite": "^5.4.0" + } +} diff --git a/packages/wallet-plugin-tacklebox/examples/dapp/public/tacklebox.svg b/packages/wallet-plugin-tacklebox/examples/dapp/public/tacklebox.svg new file mode 100644 index 00000000..32e11662 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/examples/dapp/public/tacklebox.svg @@ -0,0 +1,15 @@ + + Tacklebox + An angular navy tacklebox with a teal lid, cyan circuit accents, twin latches, and a fishing hook emblem. + + + + + + + + + + + + diff --git a/packages/wallet-plugin-tacklebox/examples/dapp/src/main.ts b/packages/wallet-plugin-tacklebox/examples/dapp/src/main.ts new file mode 100644 index 00000000..633eadf1 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/examples/dapp/src/main.ts @@ -0,0 +1,137 @@ +import {Asset, Session, SessionKit} from '@wharfkit/session' +import {WebRenderer} from '@wharfkit/web-renderer' +import {WalletPluginTackleBox} from '@wharfkit/wallet-plugin-tacklebox' + +// Core token per supported chain, for the self-transfer demo action. +const CORE_TOKEN: Record = { + // Jungle4 testnet - the recommended chain for a first run. + '73e4385a2708e6d7048834fbc1079f2fabb17b3c125b146af438971e90716c4d': { + contract: 'eosio.token', + symbol: '4,EOS', + }, + // EOS / Vaulta + aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906: { + contract: 'eosio.token', + symbol: '4,EOS', + }, + // WAX + '1064487b3cd1a897ce03ae5b6a865651747e2e152090f99c1d19d44e01aea5a4': { + contract: 'eosio.token', + symbol: '8,WAX', + }, + // Telos + '4667b205c6838ef70ff7988f6e8257e8be0e1284a2f59699054a018f743b1d11': { + contract: 'eosio.token', + symbol: '4,TLOS', + }, +} + +const sessionKit = new SessionKit({ + appName: 'tacklebox-example', + chains: [ + { + id: '73e4385a2708e6d7048834fbc1079f2fabb17b3c125b146af438971e90716c4d', + url: 'https://jungle4.greymass.com', + }, + { + id: 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906', + url: 'https://eos.greymass.com', + }, + { + id: '1064487b3cd1a897ce03ae5b6a865651747e2e152090f99c1d19d44e01aea5a4', + url: 'https://wax.greymass.com', + }, + { + id: '4667b205c6838ef70ff7988f6e8257e8be0e1284a2f59699054a018f743b1d11', + url: 'https://telos.greymass.com', + }, + ], + ui: new WebRenderer(), + walletPlugins: [new WalletPluginTackleBox()], +}) + +let session: Session | undefined + +const el = (id: string) => document.getElementById(id) as HTMLButtonElement +const output = document.getElementById('output') as HTMLPreElement +const status = document.getElementById('status') as HTMLDivElement + +function render() { + if (session) { + status.textContent = `Logged in as ${session.actor}@${session.permission} (chain ${String( + session.chain.id + ).slice(0, 8)}…)` + } else { + status.textContent = 'Not logged in.' + } + el('login').disabled = Boolean(session) + el('transfer').disabled = !session + el('logout').disabled = !session +} + +function show(value: unknown) { + output.textContent = + value instanceof Error ? `Error: ${value.message}` : JSON.stringify(value, null, 2) +} + +el('login').addEventListener('click', async () => { + try { + const result = await sessionKit.login() + session = result.session + show({connected: String(result.response.permissionLevel)}) + } catch (error) { + show(error) + } + render() +}) + +el('logout').addEventListener('click', async () => { + if (session) { + await sessionKit.logout(session) + session = undefined + show('Logged out.') + } + render() +}) + +el('transfer').addEventListener('click', async () => { + if (!session) return + const core = CORE_TOKEN[String(session.chain.id)] + if (!core) { + show(new Error('No core token configured for this chain')) + return + } + try { + const result = await session.transact({ + action: { + account: core.contract, + name: 'transfer', + authorization: [session.permissionLevel], + data: { + from: session.actor, + to: session.actor, + quantity: Asset.fromUnits(1, core.symbol), + memo: 'wallet-plugin-tacklebox example', + }, + }, + }) + show({ + transaction_id: result.resolved?.transaction.id + ? String(result.resolved.transaction.id) + : undefined, + signatures: result.signatures.map(String), + }) + } catch (error) { + show(error) + } +}) + +sessionKit.restore().then((restored) => { + if (restored) { + session = restored + show('Session restored.') + } + render() +}) + +render() diff --git a/packages/wallet-plugin-tacklebox/package.json b/packages/wallet-plugin-tacklebox/package.json new file mode 100644 index 00000000..2dcb8c0a --- /dev/null +++ b/packages/wallet-plugin-tacklebox/package.json @@ -0,0 +1,35 @@ +{ + "name": "@wharfkit/wallet-plugin-tacklebox", + "description": "A TackleBox plugin for use with @wharfkit/session.", + "version": "4.0.0", + "homepage": "https://wharfkit.com/plugins/wallet-plugin-tacklebox", + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.19.0" + }, + "main": "lib/wallet-plugin-tacklebox.js", + "module": "lib/wallet-plugin-tacklebox.m.js", + "types": "lib/wallet-plugin-tacklebox.d.ts", + "sideEffects": false, + "files": [ + "lib/*", + "src/*" + ], + "scripts": {}, + "dependencies": { + "@greymass/buoy": "catalog:", + "@wharfkit/protocol-esr": "workspace:*", + "@wharfkit/session": "workspace:*", + "isomorphic-ws": "catalog:" + }, + "devDependencies": { + "@wharfkit/antelope": "workspace:*", + "@wharfkit/mock-data": "workspace:*", + "pako": "catalog:" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/wharfkit/js.git", + "directory": "packages/wallet-plugin-tacklebox" + } +} diff --git a/packages/wallet-plugin-tacklebox/src/index.ts b/packages/wallet-plugin-tacklebox/src/index.ts new file mode 100644 index 00000000..b2b26284 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/src/index.ts @@ -0,0 +1,491 @@ +import { + AbstractWalletPlugin, + CallbackPayload, + Cancelable, + Checksum256, + LoginContext, + PermissionLevel, + PrivateKey, + PromptElement, + PromptResponse, + PublicKey, + ResolvedSigningRequest, + Serializer, + SigningRequest, + TransactContext, + WalletPluginConfig, + WalletPluginLoginResponse, + WalletPluginMetadata, + WalletPluginSignResponse, +} from '@wharfkit/session' +import { + createIdentityRequest, + extractSignaturesFromCallback, + isCallback, + isKnownMobile, + LinkInfo, + sealMessage, + setTransactionCallback, + verifyLoginCallbackResponse, + waitForCallback, +} from '@wharfkit/protocol-esr' +import {send} from '@greymass/buoy' +import WebSocket from 'isomorphic-ws' + +import defaultTranslations from './translations' +import {tackleboxLogo} from './logo' + +/** Options for the TackleBox wallet plugin. */ +export interface WalletPluginTackleBoxOptions { + /** + * Buoy callback-forwarder service used for this dapp's callbacks. + * + * TackleBox's own listening channel is announced by the wallet at login + * (`link_ch`) and does not depend on this value. + */ + buoyUrl?: string + /** WebSocket constructor override forwarded to buoy callback handling. */ + buoyWs?: WebSocket + /** + * Skip firing the `tacklebox:` deep link that opens the wallet app + * automatically on login and manual signing. The prompt always keeps the + * launch link, QR code and copy fallbacks. + */ + disableAutoLaunch?: boolean + /** @internal */ + send?: typeof send + /** @internal */ + waitForCallback?: typeof waitForCallback +} + +/** Default buoy service, shared with the rest of the anchor-link ecosystem. */ +export const DEFAULT_BUOY_URL = 'https://cb.anchor.link' + +/** + * A @wharfkit/session WalletPlugin for TackleBox, the native wallet and block + * explorer for Antelope blockchains (https://github.com/doyen-games/tacklebox). + * + * TackleBox speaks the anchor-link protocol. Logging in fires a `tacklebox:` + * deep link that opens the wallet with the ESR identity request (with QR code + * and copy-paste fallbacks in the prompt); TackleBox answers over a buoy + * callback and opens a sealed push channel. Transactions are then pushed + * straight into the wallet, which raises its own window for review - every + * request still passes TackleBox's whitelist guard and signing review. + * + * The plugin is renderer-agnostic: it speaks only the abstract UserInterface + * contract, so any session UI (web-renderer, web-ui, or custom) renders it. + */ +export class WalletPluginTackleBox extends AbstractWalletPlugin { + buoyUrl: string + buoyWs: WebSocket | undefined + autoLaunch: boolean + /** @internal */ + send: typeof send + /** @internal */ + waitForCallback: typeof waitForCallback + + /** + * The unique identifier for the wallet plugin. + */ + get id(): string { + return 'tacklebox' + } + + /** + * The translations for this plugin. + */ + translations = defaultTranslations + + constructor(options?: WalletPluginTackleBoxOptions) { + super() + this.buoyUrl = options?.buoyUrl || DEFAULT_BUOY_URL + this.buoyWs = options?.buoyWs || WebSocket + this.autoLaunch = !options?.disableAutoLaunch + this.send = options?.send || send + this.waitForCallback = options?.waitForCallback || waitForCallback + } + + /** + * The logic configuration for the wallet plugin. + */ + readonly config: WalletPluginConfig = { + // TackleBox resolves the chain from the request (or asks the user), + // so the UI never needs a chain selector. + requiresChainSelect: false, + // The user picks the account inside TackleBox. + requiresPermissionSelect: false, + } + + /** + * The metadata for the wallet plugin to be displayed in the user interface. + */ + readonly metadata: WalletPluginMetadata = WalletPluginMetadata.from({ + name: 'TackleBox', + description: 'A native wallet and block explorer for Antelope blockchains.', + logo: tackleboxLogo, + homepage: 'https://github.com/doyen-games/tacklebox', + download: 'https://github.com/doyen-games/tacklebox/releases', + }) + + login(context: LoginContext): Promise { + return this.handleLogin(context) + } + + private async handleLogin(context: LoginContext): Promise { + if (!context.ui) { + throw new Error('The TackleBox wallet plugin requires a UI to be present.') + } + + const t = context.ui.getTranslate(this.id) + + const {callback, request, sameDeviceRequest, requestKey, privateKey} = + await createIdentityRequest(context, this.buoyUrl) + + const encodedRequest = request.encode(true, true, 'esr:') + const launchUrl = tackleboxDeepLink(sameDeviceRequest) + + const prompt = (copied: boolean) => { + const elements: PromptElement[] = [ + { + type: 'link', + label: t('login.launch', {default: 'Launch TackleBox'}), + data: { + href: launchUrl, + label: t('login.launch', {default: 'Launch TackleBox'}), + variant: 'primary', + }, + }, + { + type: 'button', + label: t('login.copy', {default: 'Copy login request'}), + data: { + label: t('login.copy', {default: 'Copy login request'}), + onClick: () => { + copyToClipboard(encodedRequest).then( + (ok) => ok && !copied && prompt(true) + ) + }, + }, + }, + ] + + // The QR code only helps when this device is not the one + // running the wallet - hide it on known-mobile dapp devices. + if (!onMobileDevice()) { + elements.unshift({ + type: 'qr', + data: encodedRequest, + }) + } + + const promptPromise = context.ui!.prompt({ + title: t('login.title', {default: 'Connect with TackleBox'}), + body: copied + ? t('login.copied', { + default: + 'Request copied. In TackleBox, open Contracts → ESR → CONNECT AS LOGIN and paste it to continue.', + }) + : t('login.body', { + default: + 'TackleBox should open with this login request - approve the connection there. If nothing opens, launch it with the button, copy the request into TackleBox (Contracts → ESR → CONNECT AS LOGIN), or scan the QR code.', + }), + elements, + }) + // The modal closing is handled by the session kit; swallow the rejection. + promptPromise.catch(() => undefined) + } + + prompt(false) + + // Open the wallet directly with the request, anchor-style. + if (this.autoLaunch) { + openDeepLink(launchUrl) + } + + const callbackResponse: CallbackPayload = await this.waitForCallback( + callback, + this.buoyWs, + t + ) + + verifyLoginCallbackResponse(callbackResponse, context) + + if (!callbackResponse.cid || !callbackResponse.sa || !callbackResponse.sp) { + throw new Error( + t('error.invalid_response', { + default: + 'Invalid response from TackleBox, it must contain the cid, sa and sp fields.', + }) + ) + } + + // TackleBox always opens a push channel on login; store it so + // transact requests can be pushed straight into the wallet. + if (callbackResponse.link_ch && callbackResponse.link_key && callbackResponse.link_name) { + this.data.requestKey = requestKey + this.data.privateKey = privateKey + this.data.signerKey = PublicKey.from(callbackResponse.link_key) + this.data.channelUrl = callbackResponse.link_ch + this.data.channelName = callbackResponse.link_name + } + + const resolvedResponse = await ResolvedSigningRequest.fromPayload( + callbackResponse, + context.esrOptions + ) + + return { + chain: Checksum256.from(callbackResponse.cid), + permissionLevel: PermissionLevel.from({ + actor: callbackResponse.sa, + permission: callbackResponse.sp, + }), + identityProof: resolvedResponse.getIdentityProof(callbackResponse.sig), + } + } + + sign( + resolved: ResolvedSigningRequest, + context: TransactContext + ): Promise { + return this.handleSign(resolved, context) + } + + private async handleSign( + resolved: ResolvedSigningRequest, + context: TransactContext + ): Promise { + if (!context.ui) { + throw new Error('The TackleBox wallet plugin requires a UI to be present.') + } + + const t = context.ui.getTranslate(this.id) + + const expiration = resolved.transaction.expiration.toDate() + const expiresIn = Math.max(0, expiration.getTime() - Date.now()) + + // Re-create the request through the session so the wallet resolves it + // against fresh TAPOS values, and tag it with its expiration. + const modifiedRequest = await context.createRequest({transaction: resolved.transaction}) + modifiedRequest.setInfoKey('link', LinkInfo.from({expiration})) + + const callback = setTransactionCallback(modifiedRequest, this.buoyUrl) + const encodedRequest = modifiedRequest.encode(true, true, 'esr:') + const launchUrl = tackleboxDeepLink(modifiedRequest) + + // Track every prompt shown for this signature so the first one the + // user dismisses cancels the flow, whichever variant is on screen. + const prompts: Cancelable[] = [] + let onPromptSettled: (p: Cancelable) => void = () => undefined + const promptSettled = new Promise((resolve, reject) => { + onPromptSettled = (p) => { + prompts.push(p) + p.then(resolve, reject) + } + }) + + const countdown: PromptElement = { + type: 'countdown', + data: { + label: t('transact.await', {default: 'Waiting for TackleBox'}), + end: expiration.toISOString(), + }, + } + + const launchElement: PromptElement = { + type: 'link', + label: t('transact.launch', {default: 'Open TackleBox'}), + data: { + href: launchUrl, + label: t('transact.launch', {default: 'Open TackleBox'}), + }, + } + + const promptManual = (copied: boolean) => { + const elements: PromptElement[] = [ + launchElement, + { + type: 'button', + label: t('transact.copy', {default: 'Copy signing request'}), + data: { + label: t('transact.copy', {default: 'Copy signing request'}), + onClick: () => { + copyToClipboard(encodedRequest).then( + (ok) => ok && !copied && promptManual(true) + ) + }, + }, + }, + countdown, + ] + if (!onMobileDevice()) { + elements.unshift({ + type: 'qr', + data: encodedRequest, + }) + } + onPromptSettled( + context.ui!.prompt({ + title: t('transact.title_manual', {default: 'Sign with TackleBox'}), + body: copied + ? t('transact.copied', { + default: + 'Request copied. In TackleBox, open Contracts → ESR and paste it to continue.', + }) + : t('transact.body_manual', { + default: + 'TackleBox should open with this signing request. If nothing opens, launch it with the button, copy the request into TackleBox (Contracts → ESR), or scan the QR code.', + }), + elements, + }) + ) + } + + if (this.data.channelUrl) { + onPromptSettled( + context.ui.prompt({ + title: t('transact.title', {default: 'Sign with TackleBox'}), + body: t('transact.body', { + channelName: this.data.channelName, + default: `Review and approve this transaction in TackleBox ("${this.data.channelName}").`, + }), + elements: [ + countdown, + launchElement, + { + type: 'button', + label: t('transact.manual', {default: 'Sign manually instead'}), + data: { + label: t('transact.manual', { + default: 'Sign manually instead', + }), + onClick: () => promptManual(false), + }, + }, + ], + }) + ) + } else { + promptManual(false) + // No push channel: open the wallet directly with the request. + if (this.autoLaunch) { + openDeepLink(launchUrl) + } + } + + // Timeouts above 2^31-1ms fire immediately; clamp far-future expiries. + const timer = setTimeout( + () => { + prompts.forEach((p) => + p.cancel( + t('error.expired', {default: 'The request expired, please try again.'}) + ) + ) + }, + Math.min(expiresIn, 0x7fffffff) + ) + + const callbackPromise = this.waitForCallback(callback, this.buoyWs, t) + + if (this.data.channelUrl) { + // Seal the request to the wallet's session key and push it + // into TackleBox over its buoy channel; the wallet raises its + // own window when the request arrives. + const service = new URL(this.data.channelUrl).origin + const channel = new URL(this.data.channelUrl).pathname.substring(1) + const sealedMessage = await sealMessage( + encodedRequest, + PrivateKey.from(this.data.privateKey), + PublicKey.from(this.data.signerKey) + ) + this.send(Serializer.encode({object: sealedMessage}).array, {service, channel}) + } + + const callbackResponse = await Promise.race([callbackPromise, promptSettled]).finally( + () => { + clearTimeout(timer) + prompts.forEach((p) => p.cancel()) + } + ) + + if ( + isCallback(callbackResponse) && + extractSignaturesFromCallback(callbackResponse).length + ) { + const resolvedRequest = await ResolvedSigningRequest.fromPayload( + callbackResponse, + context.esrOptions + ) + return { + signatures: extractSignaturesFromCallback(callbackResponse), + resolved: resolvedRequest, + } + } + + throw new Error(t('error.not_completed', {default: 'The request was not completed.'})) + } +} + +/** + * The `tacklebox:` deep link that opens the wallet app with a signing request. + * + * TackleBox registers the `tacklebox:` scheme (and claims `esr:` only when no + * other wallet has it), so plugin-fired links use its own scheme - on machines + * where Anchor owns `esr:`, a plain esr link would open the wrong wallet. The + * payload is the request body without the scheme, which the wallet turns back + * into an `esr://` URI. + */ +export function tackleboxDeepLink(request: SigningRequest): string { + const encoded = request.encode(true, false, 'esr:') + return `tacklebox://request/${encoded.slice('esr:'.length)}` +} + +function openDeepLink(url: string) { + try { + if (typeof window !== 'undefined' && window.location) { + window.location.href = url + } + } catch { + // Leaving the prompt's launch link as the way in. + } +} + +/** + * Whether the dapp is running on a known mobile device (where a QR code is of + * no use). protocol-esr's isKnownMobile assumes a browser environment, so + * guard it for SSR and node. + */ +function onMobileDevice(): boolean { + return typeof navigator !== 'undefined' && isKnownMobile() +} + +/** + * Best-effort clipboard write; resolves true when the text was copied. + */ +export async function copyToClipboard(text: string): Promise { + try { + if (typeof navigator !== 'undefined' && navigator.clipboard) { + await navigator.clipboard.writeText(text) + return true + } + } catch { + // Permission denied or insecure context; try the legacy path below. + } + try { + if (typeof document !== 'undefined' && document.body) { + const element = document.createElement('textarea') + element.value = text + element.setAttribute('readonly', '') + element.style.position = 'absolute' + element.style.left = '-9999px' + document.body.appendChild(element) + element.select() + const copied = document.execCommand('copy') + document.body.removeChild(element) + return copied + } + } catch { + // Nothing else to fall back to. + } + return false +} diff --git a/packages/wallet-plugin-tacklebox/src/logo.ts b/packages/wallet-plugin-tacklebox/src/logo.ts new file mode 100644 index 00000000..b2fe1e6b --- /dev/null +++ b/packages/wallet-plugin-tacklebox/src/logo.ts @@ -0,0 +1,15 @@ +import {Logo} from '@wharfkit/session' + +/** + * The official TackleBox mark (assets/tacklebox.svg): an angular navy + * tacklebox with a teal lid, cyan circuit accents, twin latches and a fishing + * hook emblem. The navy fills carry it on light interfaces and the cyan + * light-lines carry it on dark ones, so both variants use the same art. + */ +const icon = + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJub25lIiByb2xlPSJpbWciIGFyaWEtbGFiZWxsZWRieT0idGl0bGUgZGVzYyI+CiAgPHRpdGxlIGlkPSJ0aXRsZSI+VGFja2xlYm94PC90aXRsZT4KICA8ZGVzYyBpZD0iZGVzYyI+QW4gYW5ndWxhciBuYXZ5IHRhY2tsZWJveCB3aXRoIGEgdGVhbCBsaWQsIGN5YW4gY2lyY3VpdCBhY2NlbnRzLCB0d2luIGxhdGNoZXMsIGFuZCBhIGZpc2hpbmcgaG9vayBlbWJsZW0uPC9kZXNjPgogIDxnIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogICAgPHBhdGggZmlsbD0iIzA3MTgyRSIgc3Ryb2tlPSIjMDcxODJFIiBzdHJva2Utd2lkdGg9IjEyIiBkPSJNMTAwIDczVjQzbDEwLTEwaDM2bDEwIDEwdjMwaDUybDIzIDI5djk0bC0xNyAxN0g0MmwtMTctMTd2LTk0bDIzLTI5eiIvPgogICAgPHBhdGggc3Ryb2tlPSIjNDJGNUYyIiBzdHJva2Utd2lkdGg9IjgiIGQ9Ik05OSA3MlY0NWwxMC0xMGgzOGwxMCAxMHYyNyIvPgogICAgPHBhdGggZmlsbD0iIzBCNjk3NyIgc3Ryb2tlPSIjNDJGNUYyIiBzdHJva2Utd2lkdGg9IjciIGQ9Ik00OCA3NWgxNjBsMjEgMjh2MjFIMjd2LTIxeiIvPgogICAgPHBhdGggZmlsbD0iIzA3MTgyRSIgc3Ryb2tlPSIjNDJGNUYyIiBzdHJva2Utd2lkdGg9IjciIGQ9Ik0yNyAxMjJoMjAydjczbC0xNyAxN0g0NGwtMTctMTd6Ii8+CiAgICA8cGF0aCBzdHJva2U9IiM0MkY1RjIiIHN0cm9rZS13aWR0aD0iNSIgZD0iTTQ4IDk5aDQ5bTYyIDBoNDkiLz4KICAgIDxwYXRoIGZpbGw9IiM0MkY1RjIiIHN0cm9rZT0iIzA3MTgyRSIgc3Ryb2tlLXdpZHRoPSI3IiBkPSJNNjIgMTEyaDIydjM0SDYyem0xMTAgMGgyMnYzNGgtMjJ6Ii8+CiAgICA8cGF0aCBzdHJva2U9IiMwQjY5NzciIHN0cm9rZS13aWR0aD0iNiIgZD0iTTQ2IDE1NnYyOGwxMCAxMGgzN203MCAwaDM3bDEwLTEwdi0yOCIvPgogIDwvZz4KICA8cGF0aCBzdHJva2U9IiM0MkY1RjIiIHN0cm9rZS13aWR0aD0iOCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNMTM5IDE1MXYyNGExNyAxNyAwIDAgMS0zNCAwdi02bDEwIDciLz4KICA8Y2lyY2xlIGN4PSIxMzkiIGN5PSIxNDUiIHI9IjciIGZpbGw9IiMwNzE4MkUiIHN0cm9rZT0iIzQyRjVGMiIgc3Ryb2tlLXdpZHRoPSI1Ii8+Cjwvc3ZnPgo=' + +export const tackleboxLogo = Logo.from({ + light: icon, + dark: icon, +}) diff --git a/packages/wallet-plugin-tacklebox/src/translations/en.json b/packages/wallet-plugin-tacklebox/src/translations/en.json new file mode 100644 index 00000000..1dd87c75 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/src/translations/en.json @@ -0,0 +1,26 @@ +{ + "login": { + "title": "Connect with TackleBox", + "body": "TackleBox should open with this login request - approve the connection there. If nothing opens, launch it with the button, copy the request into TackleBox (Contracts → ESR → CONNECT AS LOGIN), or scan the QR code.", + "copied": "Request copied. In TackleBox, open Contracts → ESR → CONNECT AS LOGIN and paste it to continue.", + "copy": "Copy login request", + "launch": "Launch TackleBox" + }, + "transact": { + "title": "Sign with TackleBox", + "title_manual": "Sign with TackleBox", + "body": "Review and approve this transaction in TackleBox (\"{{channelName}}\").", + "body_manual": "TackleBox should open with this signing request. If nothing opens, launch it with the button, copy the request into TackleBox (Contracts → ESR), or scan the QR code.", + "copied": "Request copied. In TackleBox, open Contracts → ESR and paste it to continue.", + "copy": "Copy signing request", + "manual": "Sign manually instead", + "launch": "Open TackleBox", + "await": "Waiting for TackleBox" + }, + "error": { + "expired": "The request expired, please try again.", + "invalid_response": "Invalid response from TackleBox, it must contain the cid, sa and sp fields.", + "not_completed": "The request was not completed.", + "cancelled": "The request was declined in TackleBox." + } +} diff --git a/packages/wallet-plugin-tacklebox/src/translations/index.ts b/packages/wallet-plugin-tacklebox/src/translations/index.ts new file mode 100644 index 00000000..54df1211 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/src/translations/index.ts @@ -0,0 +1,5 @@ +import en from './en.json' + +export default { + en, +} diff --git a/packages/wallet-plugin-tacklebox/test/data/a041de03f2a7ee6c133465c8c6b2b286704a5d8f.json b/packages/wallet-plugin-tacklebox/test/data/a041de03f2a7ee6c133465c8c6b2b286704a5d8f.json new file mode 100644 index 00000000..9ca01bae --- /dev/null +++ b/packages/wallet-plugin-tacklebox/test/data/a041de03f2a7ee6c133465c8c6b2b286704a5d8f.json @@ -0,0 +1,33 @@ +{ + "request": { + "path": "https://jungle4.greymass.com/v1/chain/get_info", + "params": { + "method": "GET", + "headers": {} + } + }, + "status": 200, + "json": { + "server_version": "13356212", + "chain_id": "73e4385a2708e6d7048834fbc1079f2fabb17b3c125b146af438971e90716c4d", + "head_block_num": 284535949, + "last_irreversible_block_num": 284535947, + "last_irreversible_block_id": "10f5ac8bd27f37626e13d55497f27512fee8b9477c1d7028bfb43478c060b8aa", + "head_block_id": "10f5ac8d9738a5d3fdffbff2c5d39aaf4627bb66afbc09391b38fef6f098155d", + "head_block_time": "2026-09-01T05:08:41.500", + "head_block_producer": "batinthedark", + "virtual_block_cpu_limit": 200000, + "virtual_block_net_limit": 1048576000, + "block_cpu_limit": 200000, + "block_net_limit": 1048576, + "server_version_string": "v1.1.5", + "fork_db_head_block_num": 284535949, + "fork_db_head_block_id": "10f5ac8d9738a5d3fdffbff2c5d39aaf4627bb66afbc09391b38fef6f098155d", + "server_full_version_string": "v1.1.5-13356212245054c35b1e1e7e20a9bd43ced48c4d", + "total_cpu_weight": "120573521296917", + "total_net_weight": "117540230729142", + "earliest_available_block_num": 284360852, + "last_irreversible_block_time": "2026-09-01T05:08:40.500" + }, + "text": "{\"server_version\":\"13356212\",\"chain_id\":\"73e4385a2708e6d7048834fbc1079f2fabb17b3c125b146af438971e90716c4d\",\"head_block_num\":284535949,\"last_irreversible_block_num\":284535947,\"last_irreversible_block_id\":\"10f5ac8bd27f37626e13d55497f27512fee8b9477c1d7028bfb43478c060b8aa\",\"head_block_id\":\"10f5ac8d9738a5d3fdffbff2c5d39aaf4627bb66afbc09391b38fef6f098155d\",\"head_block_time\":\"2026-09-01T05:08:41.500\",\"head_block_producer\":\"batinthedark\",\"virtual_block_cpu_limit\":200000,\"virtual_block_net_limit\":1048576000,\"block_cpu_limit\":200000,\"block_net_limit\":1048576,\"server_version_string\":\"v1.1.5\",\"fork_db_head_block_num\":284535949,\"fork_db_head_block_id\":\"10f5ac8d9738a5d3fdffbff2c5d39aaf4627bb66afbc09391b38fef6f098155d\",\"server_full_version_string\":\"v1.1.5-13356212245054c35b1e1e7e20a9bd43ced48c4d\",\"total_cpu_weight\":\"120573521296917\",\"total_net_weight\":\"117540230729142\",\"earliest_available_block_num\":284360852,\"last_irreversible_block_time\":\"2026-09-01T05:08:40.500\"}" +} \ No newline at end of file diff --git a/packages/wallet-plugin-tacklebox/test/data/ed314e8a151304a30043198b7b9e9df34a14aaeb.json b/packages/wallet-plugin-tacklebox/test/data/ed314e8a151304a30043198b7b9e9df34a14aaeb.json new file mode 100644 index 00000000..69cd7cd9 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/test/data/ed314e8a151304a30043198b7b9e9df34a14aaeb.json @@ -0,0 +1,18 @@ +{ + "request": { + "path": "https://jungle4.greymass.com/v1/chain/get_raw_abi", + "params": { + "method": "POST", + "body": "{\"account_name\":\"eosio.token\"}", + "headers": {} + } + }, + "status": 200, + "json": { + "account_name": "eosio.token", + "code_hash": "0a16e1dac533c4558698c8754f41219839ba2a2b75e517e65ea2537f76681f49", + "abi_hash": "4ddeabf8085f6a6045ec75a100d93b21f6979668d2f7ade18812a5698c6613ce", + "abi": "DmVvc2lvOjphYmkvMS4yAAoHYWNjb3VudAABB2JhbGFuY2UFYXNzZXQFY2xvc2UAAgVvd25lcgRuYW1lBnN5bWJvbAZzeW1ib2wGY3JlYXRlAAIGaXNzdWVyBG5hbWUObWF4aW11bV9zdXBwbHkFYXNzZXQOY3VycmVuY3lfc3RhdHMAAwZzdXBwbHkFYXNzZXQKbWF4X3N1cHBseQVhc3NldAZpc3N1ZXIEbmFtZQVpc3N1ZQADAnRvBG5hbWUIcXVhbnRpdHkFYXNzZXQEbWVtbwZzdHJpbmcKaXNzdWVmaXhlZAADAnRvBG5hbWUGc3VwcGx5BWFzc2V0BG1lbW8Gc3RyaW5nBG9wZW4AAwVvd25lcgRuYW1lBnN5bWJvbAZzeW1ib2wJcmFtX3BheWVyBG5hbWUGcmV0aXJlAAIIcXVhbnRpdHkFYXNzZXQEbWVtbwZzdHJpbmcMc2V0bWF4c3VwcGx5AAIGaXNzdWVyBG5hbWUObWF4aW11bV9zdXBwbHkFYXNzZXQIdHJhbnNmZXIABARmcm9tBG5hbWUCdG8EbmFtZQhxdWFudGl0eQVhc3NldARtZW1vBnN0cmluZwgAAAAAAIVpRAVjbG9zZfkDLS0tCnNwZWNfdmVyc2lvbjogIjAuMi4wIgp0aXRsZTogQ2xvc2UgVG9rZW4gQmFsYW5jZQpzdW1tYXJ5OiAnQ2xvc2Uge3tub3dyYXAgb3duZXJ9feKAmXMgemVybyBxdWFudGl0eSBiYWxhbmNlJwppY29uOiBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZW9zbmV0d29ya2ZvdW5kYXRpb24vZW9zLXN5c3RlbS1jb250cmFjdHMvbWFpbi9jb250cmFjdHMvaWNvbnMvdG9rZW4ucG5nIzIwN2ZmNjhiMDQwNmVhYTU2NjE4YjA4YmRhODFkNmEwOTU0NTQzZjM2YWRjMzI4YWIzMDY1ZjMxYTVjNWQ2NTQKLS0tCgp7e293bmVyfX0gYWdyZWVzIHRvIGNsb3NlIHRoZWlyIHplcm8gcXVhbnRpdHkgYmFsYW5jZSBmb3IgdGhlIHt7c3ltYm9sX3RvX3N5bWJvbF9jb2RlIHN5bWJvbH19IHRva2VuLgoKUkFNIHdpbGwgYmUgcmVmdW5kZWQgdG8gdGhlIFJBTSBwYXllciBvZiB0aGUge3tzeW1ib2xfdG9fc3ltYm9sX2NvZGUgc3ltYm9sfX0gdG9rZW4gYmFsYW5jZSBmb3Ige3tvd25lcn19LgAAAACobNRFBmNyZWF0ZZoFLS0tCnNwZWNfdmVyc2lvbjogIjAuMi4wIgp0aXRsZTogQ3JlYXRlIE5ldyBUb2tlbgpzdW1tYXJ5OiAnQ3JlYXRlIGEgbmV3IHRva2VuJwppY29uOiBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZW9zbmV0d29ya2ZvdW5kYXRpb24vZW9zLXN5c3RlbS1jb250cmFjdHMvbWFpbi9jb250cmFjdHMvaWNvbnMvdG9rZW4ucG5nIzIwN2ZmNjhiMDQwNmVhYTU2NjE4YjA4YmRhODFkNmEwOTU0NTQzZjM2YWRjMzI4YWIzMDY1ZjMxYTVjNWQ2NTQKLS0tCgp7eyRhY3Rpb24uYWNjb3VudH19IGFncmVlcyB0byBjcmVhdGUgYSBuZXcgdG9rZW4gd2l0aCBzeW1ib2wge3thc3NldF90b19zeW1ib2xfY29kZSBtYXhpbXVtX3N1cHBseX19IHRvIGJlIG1hbmFnZWQgYnkge3tpc3N1ZXJ9fS4KClRoaXMgYWN0aW9uIHdpbGwgbm90IHJlc3VsdCBhbnkgYW55IHRva2VucyBiZWluZyBpc3N1ZWQgaW50byBjaXJjdWxhdGlvbi4KCnt7aXNzdWVyfX0gd2lsbCBiZSBhbGxvd2VkIHRvIGlzc3VlIHRva2VucyBpbnRvIGNpcmN1bGF0aW9uLCB1cCB0byBhIG1heGltdW0gc3VwcGx5IG9mIHt7bWF4aW11bV9zdXBwbHl9fS4KClJBTSB3aWxsIGRlZHVjdGVkIGZyb20ge3skYWN0aW9uLmFjY291bnR9feKAmXMgcmVzb3VyY2VzIHRvIGNyZWF0ZSB0aGUgbmVjZXNzYXJ5IHJlY29yZHMuAAAAAAClMXYFaXNzdWXuBy0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IElzc3VlIFRva2VucyBpbnRvIENpcmN1bGF0aW9uCnN1bW1hcnk6ICdJc3N1ZSB7e25vd3JhcCBxdWFudGl0eX19IGludG8gY2lyY3VsYXRpb24gYW5kIHRyYW5zZmVyIGludG8ge3tub3dyYXAgdG99feKAmXMgYWNjb3VudCcKaWNvbjogaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2Vvc25ldHdvcmtmb3VuZGF0aW9uL2Vvcy1zeXN0ZW0tY29udHJhY3RzL21haW4vY29udHJhY3RzL2ljb25zL3Rva2VuLnBuZyMyMDdmZjY4YjA0MDZlYWE1NjYxOGIwOGJkYTgxZDZhMDk1NDU0M2YzNmFkYzMyOGFiMzA2NWYzMWE1YzVkNjU0Ci0tLQoKVGhlIHRva2VuIG1hbmFnZXIgYWdyZWVzIHRvIGlzc3VlIHt7cXVhbnRpdHl9fSBpbnRvIGNpcmN1bGF0aW9uLCBhbmQgdHJhbnNmZXIgaXQgaW50byB7e3RvfX3igJlzIGFjY291bnQuCgp7eyNpZiBtZW1vfX1UaGVyZSBpcyBhIG1lbW8gYXR0YWNoZWQgdG8gdGhlIHRyYW5zZmVyIHN0YXRpbmc6Cnt7bWVtb319Cnt7L2lmfX0KCklmIHt7dG99fSBkb2VzIG5vdCBoYXZlIGEgYmFsYW5jZSBmb3Ige3thc3NldF90b19zeW1ib2xfY29kZSBxdWFudGl0eX19LCBvciB0aGUgdG9rZW4gbWFuYWdlciBkb2VzIG5vdCBoYXZlIGEgYmFsYW5jZSBmb3Ige3thc3NldF90b19zeW1ib2xfY29kZSBxdWFudGl0eX19LCB0aGUgdG9rZW4gbWFuYWdlciB3aWxsIGJlIGRlc2lnbmF0ZWQgYXMgdGhlIFJBTSBwYXllciBvZiB0aGUge3thc3NldF90b19zeW1ib2xfY29kZSBxdWFudGl0eX19IHRva2VuIGJhbGFuY2UgZm9yIHt7dG99fS4gQXMgYSByZXN1bHQsIFJBTSB3aWxsIGJlIGRlZHVjdGVkIGZyb20gdGhlIHRva2VuIG1hbmFnZXLigJlzIHJlc291cmNlcyB0byBjcmVhdGUgdGhlIG5lY2Vzc2FyeSByZWNvcmRzLgoKVGhpcyBhY3Rpb24gZG9lcyBub3QgYWxsb3cgdGhlIHRvdGFsIHF1YW50aXR5IHRvIGV4Y2VlZCB0aGUgbWF4IGFsbG93ZWQgc3VwcGx5IG9mIHRoZSB0b2tlbi4AQFLdLaUxdgppc3N1ZWZpeGVkoQgtLS0Kc3BlY192ZXJzaW9uOiAiMC4yLjAiCnRpdGxlOiBJc3N1ZSBGaXhlZCBTdXBwbHkgb2YgVG9rZW5zIGludG8gQ2lyY3VsYXRpb24Kc3VtbWFyeTogJ0lzc3VlIHVwIHRvIHt7bm93cmFwIHN1cHBseX19IHN1cHBseSBpbnRvIGNpcmN1bGF0aW9uIGFuZCB0cmFuc2ZlciBpbnRvIHt7bm93cmFwIHRvfX3igJlzIGFjY291bnQnCmljb246IGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9lb3NuZXR3b3JrZm91bmRhdGlvbi9lb3Mtc3lzdGVtLWNvbnRyYWN0cy9tYWluL2NvbnRyYWN0cy9pY29ucy90b2tlbi5wbmcjMjA3ZmY2OGIwNDA2ZWFhNTY2MThiMDhiZGE4MWQ2YTA5NTQ1NDNmMzZhZGMzMjhhYjMwNjVmMzFhNWM1ZDY1NAotLS0KClRoZSB0b2tlbiBtYW5hZ2VyIGFncmVlcyB0byBpc3N1ZSB0b2tlbnMgdXAgdG8ge3tzdXBwbHl9fSBmaXhlZCBzdXBwbHkgaW50byBjaXJjdWxhdGlvbiwgYW5kIHRyYW5zZmVyIGl0IGludG8ge3t0b3194oCZcyBhY2NvdW50LgoKe3sjaWYgbWVtb319VGhlcmUgaXMgYSBtZW1vIGF0dGFjaGVkIHRvIHRoZSB0cmFuc2ZlciBzdGF0aW5nOgp7e21lbW99fQp7ey9pZn19CgpJZiB7e3RvfX0gZG9lcyBub3QgaGF2ZSBhIGJhbGFuY2UgZm9yIHt7YXNzZXRfdG9fc3ltYm9sX2NvZGUgcXVhbnRpdHl9fSwgb3IgdGhlIHRva2VuIG1hbmFnZXIgZG9lcyBub3QgaGF2ZSBhIGJhbGFuY2UgZm9yIHt7YXNzZXRfdG9fc3ltYm9sX2NvZGUgcXVhbnRpdHl9fSwgdGhlIHRva2VuIG1hbmFnZXIgd2lsbCBiZSBkZXNpZ25hdGVkIGFzIHRoZSBSQU0gcGF5ZXIgb2YgdGhlIHt7YXNzZXRfdG9fc3ltYm9sX2NvZGUgcXVhbnRpdHl9fSB0b2tlbiBiYWxhbmNlIGZvciB7e3RvfX0uIEFzIGEgcmVzdWx0LCBSQU0gd2lsbCBiZSBkZWR1Y3RlZCBmcm9tIHRoZSB0b2tlbiBtYW5hZ2Vy4oCZcyByZXNvdXJjZXMgdG8gY3JlYXRlIHRoZSBuZWNlc3NhcnkgcmVjb3Jkcy4KClRoaXMgYWN0aW9uIGRvZXMgbm90IGFsbG93IHRoZSB0b3RhbCBxdWFudGl0eSB0byBleGNlZWQgdGhlIG1heCBhbGxvd2VkIHN1cHBseSBvZiB0aGUgdG9rZW4uAAAAAAAwVaUEb3BlbsYFLS0tCnNwZWNfdmVyc2lvbjogIjAuMi4wIgp0aXRsZTogT3BlbiBUb2tlbiBCYWxhbmNlCnN1bW1hcnk6ICdPcGVuIGEgemVybyBxdWFudGl0eSBiYWxhbmNlIGZvciB7e25vd3JhcCBvd25lcn19JwppY29uOiBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZW9zbmV0d29ya2ZvdW5kYXRpb24vZW9zLXN5c3RlbS1jb250cmFjdHMvbWFpbi9jb250cmFjdHMvaWNvbnMvdG9rZW4ucG5nIzIwN2ZmNjhiMDQwNmVhYTU2NjE4YjA4YmRhODFkNmEwOTU0NTQzZjM2YWRjMzI4YWIzMDY1ZjMxYTVjNWQ2NTQKLS0tCgp7e3JhbV9wYXllcn19IGFncmVlcyB0byBlc3RhYmxpc2ggYSB6ZXJvIHF1YW50aXR5IGJhbGFuY2UgZm9yIHt7b3duZXJ9fSBmb3IgdGhlIHt7c3ltYm9sX3RvX3N5bWJvbF9jb2RlIHN5bWJvbH19IHRva2VuLgoKSWYge3tvd25lcn19IGRvZXMgbm90IGhhdmUgYSBiYWxhbmNlIGZvciB7e3N5bWJvbF90b19zeW1ib2xfY29kZSBzeW1ib2x9fSwge3tyYW1fcGF5ZXJ9fSB3aWxsIGJlIGRlc2lnbmF0ZWQgYXMgdGhlIFJBTSBwYXllciBvZiB0aGUge3tzeW1ib2xfdG9fc3ltYm9sX2NvZGUgc3ltYm9sfX0gdG9rZW4gYmFsYW5jZSBmb3Ige3tvd25lcn19LiBBcyBhIHJlc3VsdCwgUkFNIHdpbGwgYmUgZGVkdWN0ZWQgZnJvbSB7e3JhbV9wYXllcn194oCZcyByZXNvdXJjZXMgdG8gY3JlYXRlIHRoZSBuZWNlc3NhcnkgcmVjb3Jkcy4AAAAAqOuyugZyZXRpcmXcAy0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IFJlbW92ZSBUb2tlbnMgZnJvbSBDaXJjdWxhdGlvbgpzdW1tYXJ5OiAnUmVtb3ZlIHt7bm93cmFwIHF1YW50aXR5fX0gZnJvbSBjaXJjdWxhdGlvbicKaWNvbjogaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2Vvc25ldHdvcmtmb3VuZGF0aW9uL2Vvcy1zeXN0ZW0tY29udHJhY3RzL21haW4vY29udHJhY3RzL2ljb25zL3Rva2VuLnBuZyMyMDdmZjY4YjA0MDZlYWE1NjYxOGIwOGJkYTgxZDZhMDk1NDU0M2YzNmFkYzMyOGFiMzA2NWYzMWE1YzVkNjU0Ci0tLQoKVGhlIHRva2VuIG1hbmFnZXIgYWdyZWVzIHRvIHJlbW92ZSB7e3F1YW50aXR5fX0gZnJvbSBjaXJjdWxhdGlvbiwgdGFrZW4gZnJvbSB0aGVpciBvd24gYWNjb3VudC4KCnt7I2lmIG1lbW99fSBUaGVyZSBpcyBhIG1lbW8gYXR0YWNoZWQgdG8gdGhlIGFjdGlvbiBzdGF0aW5nOgp7e21lbW99fQp7ey9pZn194GOtGncjs8IMc2V0bWF4c3VwcGx5wAMtLS0Kc3BlY192ZXJzaW9uOiAiMC4yLjAiCnRpdGxlOiBTZXQgTWF4IFN1cHBseQpzdW1tYXJ5OiAnU2V0IG1heCBzdXBwbHkgZm9yIHRva2VuJwppY29uOiBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZW9zbmV0d29ya2ZvdW5kYXRpb24vZW9zLXN5c3RlbS1jb250cmFjdHMvbWFpbi9jb250cmFjdHMvaWNvbnMvdG9rZW4ucG5nIzIwN2ZmNjhiMDQwNmVhYTU2NjE4YjA4YmRhODFkNmEwOTU0NTQzZjM2YWRjMzI4YWIzMDY1ZjMxYTVjNWQ2NTQKLS0tCgp7e2lzc3Vlcn19IHdpbGwgYmUgYWxsb3dlZCB0byBpc3N1ZSB0b2tlbnMgaW50byBjaXJjdWxhdGlvbiwgdXAgdG8gYSBtYXhpbXVtIHN1cHBseSBvZiB7e21heGltdW1fc3VwcGx5fX0uCgpUaGlzIGFjdGlvbiB3aWxsIG5vdCByZXN1bHQgYW55IGFueSB0b2tlbnMgYmVpbmcgaXNzdWVkIGludG8gY2lyY3VsYXRpb24uAAAAVy08zc0IdHJhbnNmZXK2By0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IFRyYW5zZmVyIFRva2VucwpzdW1tYXJ5OiAnU2VuZCB7e25vd3JhcCBxdWFudGl0eX19IGZyb20ge3tub3dyYXAgZnJvbX19IHRvIHt7bm93cmFwIHRvfX0nCmljb246IGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9lb3NuZXR3b3JrZm91bmRhdGlvbi9lb3Mtc3lzdGVtLWNvbnRyYWN0cy9tYWluL2NvbnRyYWN0cy9pY29ucy90cmFuc2Zlci5wbmcjNWRmYWQwZGY3Mjc3MmVlMWNjYzE1NWU2NzBjMWQxMjRmNWM1MTIyZjFkNTAyNzU2NWRmMzhiNDE4MDQyZDFkZAotLS0KCnt7ZnJvbX19IGFncmVlcyB0byBzZW5kIHt7cXVhbnRpdHl9fSB0byB7e3RvfX0uCgp7eyNpZiBtZW1vfX1UaGVyZSBpcyBhIG1lbW8gYXR0YWNoZWQgdG8gdGhlIHRyYW5zZmVyIHN0YXRpbmc6Cnt7bWVtb319Cnt7L2lmfX0KCklmIHt7ZnJvbX19IGlzIG5vdCBhbHJlYWR5IHRoZSBSQU0gcGF5ZXIgb2YgdGhlaXIge3thc3NldF90b19zeW1ib2xfY29kZSBxdWFudGl0eX19IHRva2VuIGJhbGFuY2UsIHt7ZnJvbX19IHdpbGwgYmUgZGVzaWduYXRlZCBhcyBzdWNoLiBBcyBhIHJlc3VsdCwgUkFNIHdpbGwgYmUgZGVkdWN0ZWQgZnJvbSB7e2Zyb219feKAmXMgcmVzb3VyY2VzIHRvIHJlZnVuZCB0aGUgb3JpZ2luYWwgUkFNIHBheWVyLgoKSWYge3t0b319IGRvZXMgbm90IGhhdmUgYSBiYWxhbmNlIGZvciB7e2Fzc2V0X3RvX3N5bWJvbF9jb2RlIHF1YW50aXR5fX0sIHt7ZnJvbX19IHdpbGwgYmUgZGVzaWduYXRlZCBhcyB0aGUgUkFNIHBheWVyIG9mIHRoZSB7e2Fzc2V0X3RvX3N5bWJvbF9jb2RlIHF1YW50aXR5fX0gdG9rZW4gYmFsYW5jZSBmb3Ige3t0b319LiBBcyBhIHJlc3VsdCwgUkFNIHdpbGwgYmUgZGVkdWN0ZWQgZnJvbSB7e2Zyb219feKAmXMgcmVzb3VyY2VzIHRvIGNyZWF0ZSB0aGUgbmVjZXNzYXJ5IHJlY29yZHMuAgAAADhPTREyA2k2NAAAB2FjY291bnQAAAAAAJBNxgNpNjQAAA5jdXJyZW5jeV9zdGF0cwAAAAA=" + }, + "text": "{\"account_name\":\"eosio.token\",\"code_hash\":\"0a16e1dac533c4558698c8754f41219839ba2a2b75e517e65ea2537f76681f49\",\"abi_hash\":\"4ddeabf8085f6a6045ec75a100d93b21f6979668d2f7ade18812a5698c6613ce\",\"abi\":\"DmVvc2lvOjphYmkvMS4yAAoHYWNjb3VudAABB2JhbGFuY2UFYXNzZXQFY2xvc2UAAgVvd25lcgRuYW1lBnN5bWJvbAZzeW1ib2wGY3JlYXRlAAIGaXNzdWVyBG5hbWUObWF4aW11bV9zdXBwbHkFYXNzZXQOY3VycmVuY3lfc3RhdHMAAwZzdXBwbHkFYXNzZXQKbWF4X3N1cHBseQVhc3NldAZpc3N1ZXIEbmFtZQVpc3N1ZQADAnRvBG5hbWUIcXVhbnRpdHkFYXNzZXQEbWVtbwZzdHJpbmcKaXNzdWVmaXhlZAADAnRvBG5hbWUGc3VwcGx5BWFzc2V0BG1lbW8Gc3RyaW5nBG9wZW4AAwVvd25lcgRuYW1lBnN5bWJvbAZzeW1ib2wJcmFtX3BheWVyBG5hbWUGcmV0aXJlAAIIcXVhbnRpdHkFYXNzZXQEbWVtbwZzdHJpbmcMc2V0bWF4c3VwcGx5AAIGaXNzdWVyBG5hbWUObWF4aW11bV9zdXBwbHkFYXNzZXQIdHJhbnNmZXIABARmcm9tBG5hbWUCdG8EbmFtZQhxdWFudGl0eQVhc3NldARtZW1vBnN0cmluZwgAAAAAAIVpRAVjbG9zZfkDLS0tCnNwZWNfdmVyc2lvbjogIjAuMi4wIgp0aXRsZTogQ2xvc2UgVG9rZW4gQmFsYW5jZQpzdW1tYXJ5OiAnQ2xvc2Uge3tub3dyYXAgb3duZXJ9feKAmXMgemVybyBxdWFudGl0eSBiYWxhbmNlJwppY29uOiBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZW9zbmV0d29ya2ZvdW5kYXRpb24vZW9zLXN5c3RlbS1jb250cmFjdHMvbWFpbi9jb250cmFjdHMvaWNvbnMvdG9rZW4ucG5nIzIwN2ZmNjhiMDQwNmVhYTU2NjE4YjA4YmRhODFkNmEwOTU0NTQzZjM2YWRjMzI4YWIzMDY1ZjMxYTVjNWQ2NTQKLS0tCgp7e293bmVyfX0gYWdyZWVzIHRvIGNsb3NlIHRoZWlyIHplcm8gcXVhbnRpdHkgYmFsYW5jZSBmb3IgdGhlIHt7c3ltYm9sX3RvX3N5bWJvbF9jb2RlIHN5bWJvbH19IHRva2VuLgoKUkFNIHdpbGwgYmUgcmVmdW5kZWQgdG8gdGhlIFJBTSBwYXllciBvZiB0aGUge3tzeW1ib2xfdG9fc3ltYm9sX2NvZGUgc3ltYm9sfX0gdG9rZW4gYmFsYW5jZSBmb3Ige3tvd25lcn19LgAAAACobNRFBmNyZWF0ZZoFLS0tCnNwZWNfdmVyc2lvbjogIjAuMi4wIgp0aXRsZTogQ3JlYXRlIE5ldyBUb2tlbgpzdW1tYXJ5OiAnQ3JlYXRlIGEgbmV3IHRva2VuJwppY29uOiBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZW9zbmV0d29ya2ZvdW5kYXRpb24vZW9zLXN5c3RlbS1jb250cmFjdHMvbWFpbi9jb250cmFjdHMvaWNvbnMvdG9rZW4ucG5nIzIwN2ZmNjhiMDQwNmVhYTU2NjE4YjA4YmRhODFkNmEwOTU0NTQzZjM2YWRjMzI4YWIzMDY1ZjMxYTVjNWQ2NTQKLS0tCgp7eyRhY3Rpb24uYWNjb3VudH19IGFncmVlcyB0byBjcmVhdGUgYSBuZXcgdG9rZW4gd2l0aCBzeW1ib2wge3thc3NldF90b19zeW1ib2xfY29kZSBtYXhpbXVtX3N1cHBseX19IHRvIGJlIG1hbmFnZWQgYnkge3tpc3N1ZXJ9fS4KClRoaXMgYWN0aW9uIHdpbGwgbm90IHJlc3VsdCBhbnkgYW55IHRva2VucyBiZWluZyBpc3N1ZWQgaW50byBjaXJjdWxhdGlvbi4KCnt7aXNzdWVyfX0gd2lsbCBiZSBhbGxvd2VkIHRvIGlzc3VlIHRva2VucyBpbnRvIGNpcmN1bGF0aW9uLCB1cCB0byBhIG1heGltdW0gc3VwcGx5IG9mIHt7bWF4aW11bV9zdXBwbHl9fS4KClJBTSB3aWxsIGRlZHVjdGVkIGZyb20ge3skYWN0aW9uLmFjY291bnR9feKAmXMgcmVzb3VyY2VzIHRvIGNyZWF0ZSB0aGUgbmVjZXNzYXJ5IHJlY29yZHMuAAAAAAClMXYFaXNzdWXuBy0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IElzc3VlIFRva2VucyBpbnRvIENpcmN1bGF0aW9uCnN1bW1hcnk6ICdJc3N1ZSB7e25vd3JhcCBxdWFudGl0eX19IGludG8gY2lyY3VsYXRpb24gYW5kIHRyYW5zZmVyIGludG8ge3tub3dyYXAgdG99feKAmXMgYWNjb3VudCcKaWNvbjogaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2Vvc25ldHdvcmtmb3VuZGF0aW9uL2Vvcy1zeXN0ZW0tY29udHJhY3RzL21haW4vY29udHJhY3RzL2ljb25zL3Rva2VuLnBuZyMyMDdmZjY4YjA0MDZlYWE1NjYxOGIwOGJkYTgxZDZhMDk1NDU0M2YzNmFkYzMyOGFiMzA2NWYzMWE1YzVkNjU0Ci0tLQoKVGhlIHRva2VuIG1hbmFnZXIgYWdyZWVzIHRvIGlzc3VlIHt7cXVhbnRpdHl9fSBpbnRvIGNpcmN1bGF0aW9uLCBhbmQgdHJhbnNmZXIgaXQgaW50byB7e3RvfX3igJlzIGFjY291bnQuCgp7eyNpZiBtZW1vfX1UaGVyZSBpcyBhIG1lbW8gYXR0YWNoZWQgdG8gdGhlIHRyYW5zZmVyIHN0YXRpbmc6Cnt7bWVtb319Cnt7L2lmfX0KCklmIHt7dG99fSBkb2VzIG5vdCBoYXZlIGEgYmFsYW5jZSBmb3Ige3thc3NldF90b19zeW1ib2xfY29kZSBxdWFudGl0eX19LCBvciB0aGUgdG9rZW4gbWFuYWdlciBkb2VzIG5vdCBoYXZlIGEgYmFsYW5jZSBmb3Ige3thc3NldF90b19zeW1ib2xfY29kZSBxdWFudGl0eX19LCB0aGUgdG9rZW4gbWFuYWdlciB3aWxsIGJlIGRlc2lnbmF0ZWQgYXMgdGhlIFJBTSBwYXllciBvZiB0aGUge3thc3NldF90b19zeW1ib2xfY29kZSBxdWFudGl0eX19IHRva2VuIGJhbGFuY2UgZm9yIHt7dG99fS4gQXMgYSByZXN1bHQsIFJBTSB3aWxsIGJlIGRlZHVjdGVkIGZyb20gdGhlIHRva2VuIG1hbmFnZXLigJlzIHJlc291cmNlcyB0byBjcmVhdGUgdGhlIG5lY2Vzc2FyeSByZWNvcmRzLgoKVGhpcyBhY3Rpb24gZG9lcyBub3QgYWxsb3cgdGhlIHRvdGFsIHF1YW50aXR5IHRvIGV4Y2VlZCB0aGUgbWF4IGFsbG93ZWQgc3VwcGx5IG9mIHRoZSB0b2tlbi4AQFLdLaUxdgppc3N1ZWZpeGVkoQgtLS0Kc3BlY192ZXJzaW9uOiAiMC4yLjAiCnRpdGxlOiBJc3N1ZSBGaXhlZCBTdXBwbHkgb2YgVG9rZW5zIGludG8gQ2lyY3VsYXRpb24Kc3VtbWFyeTogJ0lzc3VlIHVwIHRvIHt7bm93cmFwIHN1cHBseX19IHN1cHBseSBpbnRvIGNpcmN1bGF0aW9uIGFuZCB0cmFuc2ZlciBpbnRvIHt7bm93cmFwIHRvfX3igJlzIGFjY291bnQnCmljb246IGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9lb3NuZXR3b3JrZm91bmRhdGlvbi9lb3Mtc3lzdGVtLWNvbnRyYWN0cy9tYWluL2NvbnRyYWN0cy9pY29ucy90b2tlbi5wbmcjMjA3ZmY2OGIwNDA2ZWFhNTY2MThiMDhiZGE4MWQ2YTA5NTQ1NDNmMzZhZGMzMjhhYjMwNjVmMzFhNWM1ZDY1NAotLS0KClRoZSB0b2tlbiBtYW5hZ2VyIGFncmVlcyB0byBpc3N1ZSB0b2tlbnMgdXAgdG8ge3tzdXBwbHl9fSBmaXhlZCBzdXBwbHkgaW50byBjaXJjdWxhdGlvbiwgYW5kIHRyYW5zZmVyIGl0IGludG8ge3t0b3194oCZcyBhY2NvdW50LgoKe3sjaWYgbWVtb319VGhlcmUgaXMgYSBtZW1vIGF0dGFjaGVkIHRvIHRoZSB0cmFuc2ZlciBzdGF0aW5nOgp7e21lbW99fQp7ey9pZn19CgpJZiB7e3RvfX0gZG9lcyBub3QgaGF2ZSBhIGJhbGFuY2UgZm9yIHt7YXNzZXRfdG9fc3ltYm9sX2NvZGUgcXVhbnRpdHl9fSwgb3IgdGhlIHRva2VuIG1hbmFnZXIgZG9lcyBub3QgaGF2ZSBhIGJhbGFuY2UgZm9yIHt7YXNzZXRfdG9fc3ltYm9sX2NvZGUgcXVhbnRpdHl9fSwgdGhlIHRva2VuIG1hbmFnZXIgd2lsbCBiZSBkZXNpZ25hdGVkIGFzIHRoZSBSQU0gcGF5ZXIgb2YgdGhlIHt7YXNzZXRfdG9fc3ltYm9sX2NvZGUgcXVhbnRpdHl9fSB0b2tlbiBiYWxhbmNlIGZvciB7e3RvfX0uIEFzIGEgcmVzdWx0LCBSQU0gd2lsbCBiZSBkZWR1Y3RlZCBmcm9tIHRoZSB0b2tlbiBtYW5hZ2Vy4oCZcyByZXNvdXJjZXMgdG8gY3JlYXRlIHRoZSBuZWNlc3NhcnkgcmVjb3Jkcy4KClRoaXMgYWN0aW9uIGRvZXMgbm90IGFsbG93IHRoZSB0b3RhbCBxdWFudGl0eSB0byBleGNlZWQgdGhlIG1heCBhbGxvd2VkIHN1cHBseSBvZiB0aGUgdG9rZW4uAAAAAAAwVaUEb3BlbsYFLS0tCnNwZWNfdmVyc2lvbjogIjAuMi4wIgp0aXRsZTogT3BlbiBUb2tlbiBCYWxhbmNlCnN1bW1hcnk6ICdPcGVuIGEgemVybyBxdWFudGl0eSBiYWxhbmNlIGZvciB7e25vd3JhcCBvd25lcn19JwppY29uOiBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZW9zbmV0d29ya2ZvdW5kYXRpb24vZW9zLXN5c3RlbS1jb250cmFjdHMvbWFpbi9jb250cmFjdHMvaWNvbnMvdG9rZW4ucG5nIzIwN2ZmNjhiMDQwNmVhYTU2NjE4YjA4YmRhODFkNmEwOTU0NTQzZjM2YWRjMzI4YWIzMDY1ZjMxYTVjNWQ2NTQKLS0tCgp7e3JhbV9wYXllcn19IGFncmVlcyB0byBlc3RhYmxpc2ggYSB6ZXJvIHF1YW50aXR5IGJhbGFuY2UgZm9yIHt7b3duZXJ9fSBmb3IgdGhlIHt7c3ltYm9sX3RvX3N5bWJvbF9jb2RlIHN5bWJvbH19IHRva2VuLgoKSWYge3tvd25lcn19IGRvZXMgbm90IGhhdmUgYSBiYWxhbmNlIGZvciB7e3N5bWJvbF90b19zeW1ib2xfY29kZSBzeW1ib2x9fSwge3tyYW1fcGF5ZXJ9fSB3aWxsIGJlIGRlc2lnbmF0ZWQgYXMgdGhlIFJBTSBwYXllciBvZiB0aGUge3tzeW1ib2xfdG9fc3ltYm9sX2NvZGUgc3ltYm9sfX0gdG9rZW4gYmFsYW5jZSBmb3Ige3tvd25lcn19LiBBcyBhIHJlc3VsdCwgUkFNIHdpbGwgYmUgZGVkdWN0ZWQgZnJvbSB7e3JhbV9wYXllcn194oCZcyByZXNvdXJjZXMgdG8gY3JlYXRlIHRoZSBuZWNlc3NhcnkgcmVjb3Jkcy4AAAAAqOuyugZyZXRpcmXcAy0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IFJlbW92ZSBUb2tlbnMgZnJvbSBDaXJjdWxhdGlvbgpzdW1tYXJ5OiAnUmVtb3ZlIHt7bm93cmFwIHF1YW50aXR5fX0gZnJvbSBjaXJjdWxhdGlvbicKaWNvbjogaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2Vvc25ldHdvcmtmb3VuZGF0aW9uL2Vvcy1zeXN0ZW0tY29udHJhY3RzL21haW4vY29udHJhY3RzL2ljb25zL3Rva2VuLnBuZyMyMDdmZjY4YjA0MDZlYWE1NjYxOGIwOGJkYTgxZDZhMDk1NDU0M2YzNmFkYzMyOGFiMzA2NWYzMWE1YzVkNjU0Ci0tLQoKVGhlIHRva2VuIG1hbmFnZXIgYWdyZWVzIHRvIHJlbW92ZSB7e3F1YW50aXR5fX0gZnJvbSBjaXJjdWxhdGlvbiwgdGFrZW4gZnJvbSB0aGVpciBvd24gYWNjb3VudC4KCnt7I2lmIG1lbW99fSBUaGVyZSBpcyBhIG1lbW8gYXR0YWNoZWQgdG8gdGhlIGFjdGlvbiBzdGF0aW5nOgp7e21lbW99fQp7ey9pZn194GOtGncjs8IMc2V0bWF4c3VwcGx5wAMtLS0Kc3BlY192ZXJzaW9uOiAiMC4yLjAiCnRpdGxlOiBTZXQgTWF4IFN1cHBseQpzdW1tYXJ5OiAnU2V0IG1heCBzdXBwbHkgZm9yIHRva2VuJwppY29uOiBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZW9zbmV0d29ya2ZvdW5kYXRpb24vZW9zLXN5c3RlbS1jb250cmFjdHMvbWFpbi9jb250cmFjdHMvaWNvbnMvdG9rZW4ucG5nIzIwN2ZmNjhiMDQwNmVhYTU2NjE4YjA4YmRhODFkNmEwOTU0NTQzZjM2YWRjMzI4YWIzMDY1ZjMxYTVjNWQ2NTQKLS0tCgp7e2lzc3Vlcn19IHdpbGwgYmUgYWxsb3dlZCB0byBpc3N1ZSB0b2tlbnMgaW50byBjaXJjdWxhdGlvbiwgdXAgdG8gYSBtYXhpbXVtIHN1cHBseSBvZiB7e21heGltdW1fc3VwcGx5fX0uCgpUaGlzIGFjdGlvbiB3aWxsIG5vdCByZXN1bHQgYW55IGFueSB0b2tlbnMgYmVpbmcgaXNzdWVkIGludG8gY2lyY3VsYXRpb24uAAAAVy08zc0IdHJhbnNmZXK2By0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IFRyYW5zZmVyIFRva2VucwpzdW1tYXJ5OiAnU2VuZCB7e25vd3JhcCBxdWFudGl0eX19IGZyb20ge3tub3dyYXAgZnJvbX19IHRvIHt7bm93cmFwIHRvfX0nCmljb246IGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9lb3NuZXR3b3JrZm91bmRhdGlvbi9lb3Mtc3lzdGVtLWNvbnRyYWN0cy9tYWluL2NvbnRyYWN0cy9pY29ucy90cmFuc2Zlci5wbmcjNWRmYWQwZGY3Mjc3MmVlMWNjYzE1NWU2NzBjMWQxMjRmNWM1MTIyZjFkNTAyNzU2NWRmMzhiNDE4MDQyZDFkZAotLS0KCnt7ZnJvbX19IGFncmVlcyB0byBzZW5kIHt7cXVhbnRpdHl9fSB0byB7e3RvfX0uCgp7eyNpZiBtZW1vfX1UaGVyZSBpcyBhIG1lbW8gYXR0YWNoZWQgdG8gdGhlIHRyYW5zZmVyIHN0YXRpbmc6Cnt7bWVtb319Cnt7L2lmfX0KCklmIHt7ZnJvbX19IGlzIG5vdCBhbHJlYWR5IHRoZSBSQU0gcGF5ZXIgb2YgdGhlaXIge3thc3NldF90b19zeW1ib2xfY29kZSBxdWFudGl0eX19IHRva2VuIGJhbGFuY2UsIHt7ZnJvbX19IHdpbGwgYmUgZGVzaWduYXRlZCBhcyBzdWNoLiBBcyBhIHJlc3VsdCwgUkFNIHdpbGwgYmUgZGVkdWN0ZWQgZnJvbSB7e2Zyb219feKAmXMgcmVzb3VyY2VzIHRvIHJlZnVuZCB0aGUgb3JpZ2luYWwgUkFNIHBheWVyLgoKSWYge3t0b319IGRvZXMgbm90IGhhdmUgYSBiYWxhbmNlIGZvciB7e2Fzc2V0X3RvX3N5bWJvbF9jb2RlIHF1YW50aXR5fX0sIHt7ZnJvbX19IHdpbGwgYmUgZGVzaWduYXRlZCBhcyB0aGUgUkFNIHBheWVyIG9mIHRoZSB7e2Fzc2V0X3RvX3N5bWJvbF9jb2RlIHF1YW50aXR5fX0gdG9rZW4gYmFsYW5jZSBmb3Ige3t0b319LiBBcyBhIHJlc3VsdCwgUkFNIHdpbGwgYmUgZGVkdWN0ZWQgZnJvbSB7e2Zyb219feKAmXMgcmVzb3VyY2VzIHRvIGNyZWF0ZSB0aGUgbmVjZXNzYXJ5IHJlY29yZHMuAgAAADhPTREyA2k2NAAAB2FjY291bnQAAAAAAJBNxgNpNjQAAA5jdXJyZW5jeV9zdGF0cwAAAAA=\"}" +} \ No newline at end of file diff --git a/packages/wallet-plugin-tacklebox/test/tests/common.ts b/packages/wallet-plugin-tacklebox/test/tests/common.ts new file mode 100644 index 00000000..c56bbc7e --- /dev/null +++ b/packages/wallet-plugin-tacklebox/test/tests/common.ts @@ -0,0 +1,76 @@ +import {assert} from 'chai' +import * as zlib from 'pako' +import {AbstractWalletPlugin, SigningRequest} from '@wharfkit/session' +import {mockChainId} from '@wharfkit/mock-data' + +import {copyToClipboard, DEFAULT_BUOY_URL, tackleboxDeepLink, WalletPluginTackleBox} from '$lib' + +suite('plugin surface', function () { + test('is a wallet plugin with the expected id', function () { + const plugin = new WalletPluginTackleBox() + assert.instanceOf(plugin, AbstractWalletPlugin as any) + assert.equal(plugin.id, 'tacklebox') + }) + + test('lets the wallet pick the chain and permission', function () { + const plugin = new WalletPluginTackleBox() + assert.isFalse(plugin.config.requiresChainSelect) + assert.isFalse(plugin.config.requiresPermissionSelect) + assert.isUndefined(plugin.config.supportedChains) + }) + + test('carries TackleBox metadata', function () { + const {metadata} = new WalletPluginTackleBox() + assert.equal(metadata.name, 'TackleBox') + assert.equal(metadata.homepage, 'https://github.com/doyen-games/tacklebox') + assert.equal(metadata.download, 'https://github.com/doyen-games/tacklebox/releases') + assert.exists(metadata.logo) + assert.isTrue(String(metadata.logo!.getVariant('light')).startsWith('data:image/svg+xml')) + assert.isTrue(String(metadata.logo!.getVariant('dark')).startsWith('data:image/svg+xml')) + }) + + test('defaults to the shared buoy service', function () { + const plugin = new WalletPluginTackleBox() + assert.equal(plugin.buoyUrl, DEFAULT_BUOY_URL) + assert.equal(DEFAULT_BUOY_URL, 'https://cb.anchor.link') + }) + + test('accepts a custom buoy service', function () { + const plugin = new WalletPluginTackleBox({buoyUrl: 'https://buoy.example.com'}) + assert.equal(plugin.buoyUrl, 'https://buoy.example.com') + }) + + test('ships english translations', function () { + const plugin = new WalletPluginTackleBox() + assert.exists(plugin.translations) + assert.equal(plugin.translations!.en.login.title, 'Connect with TackleBox') + assert.exists(plugin.translations!.en.error.cancelled) + }) + + test('clipboard helper degrades gracefully outside a browser', async function () { + assert.isFalse(await copyToClipboard('esr://test')) + }) + + test('deep links carry the request payload on the tacklebox scheme', function () { + const request = SigningRequest.identity( + { + callback: {url: 'https://cb.anchor.link/unittest', background: true}, + scope: 'unittest', + chainId: mockChainId, + }, + {zlib} + ) + const link = tackleboxDeepLink(request) + assert.isTrue(link.startsWith('tacklebox://request/')) + + const payload = link.slice('tacklebox://request/'.length) + const roundTrip = SigningRequest.from(`esr://${payload}`, {zlib}) + assert.isTrue(roundTrip.isIdentity()) + assert.equal(String(roundTrip.getIdentityScope()), 'unittest') + }) + + test('auto-launch can be disabled by option', function () { + assert.isTrue(new WalletPluginTackleBox().autoLaunch) + assert.isFalse(new WalletPluginTackleBox({disableAutoLaunch: true}).autoLaunch) + }) +}) diff --git a/packages/wallet-plugin-tacklebox/test/tests/login.ts b/packages/wallet-plugin-tacklebox/test/tests/login.ts new file mode 100644 index 00000000..9ba1d089 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/test/tests/login.ts @@ -0,0 +1,203 @@ +import {assert} from 'chai' +import sinon from 'sinon' +import * as zlib from 'pako' +import { + ChainDefinition, + LoginContext, + PermissionLevel, + PromptElement, + SigningRequest, +} from '@wharfkit/session' +import {mockChainDefinition, mockChainId, mockFetch, mockPermissionLevel} from '@wharfkit/mock-data' + +import {WalletPluginTackleBox} from '$lib' +import {makeMockUI} from '$test/utils/mock-ui' +import {makeLoginCallbackPayload, mockChannelUrl, mockPublicKey} from '$test/utils/mock-esr' + +const chain = ChainDefinition.from(mockChainDefinition) + +function makeLoginContext(ui: any): LoginContext { + return { + chain, + chains: [chain], + ui, + fetch: mockFetch, + hooks: {}, + appName: 'unittest', + permissionLevel: PermissionLevel.from(mockPermissionLevel), + walletPlugins: [], + arbitrary: {}, + uiRequirements: {}, + addHook: () => undefined, + getClient: () => undefined, + esrOptions: {zlib}, + } as unknown as LoginContext +} + +function stubBrowserWindow(): {location: {href: string}} { + const fakeWindow = {location: {href: 'http://localhost/unittest'}} + ;(global as any).window = fakeWindow + ;(global as any).navigator = {userAgent: 'mocha-unittest'} + return fakeWindow +} + +function unstubBrowserWindow() { + delete (global as any).window + delete (global as any).navigator +} + +/** + * The plugin takes its buoy callback wait as an internal seam, so a test + * substitutes the transport instead of stubbing a module export. + */ +function waitForCallbackStub(payload: any = makeLoginCallbackPayload()) { + return sinon.stub().resolves(payload) +} + +suite('login', function () { + teardown(function () { + unstubBrowserWindow() + sinon.restore() + }) + + test('completes a login answered over the buoy channel', async function () { + const plugin = new WalletPluginTackleBox({waitForCallback: waitForCallbackStub()}) + const ui = makeMockUI() + const response = await plugin.login(makeLoginContext(ui)) + + assert.equal(String(response.chain), mockChainId) + assert.equal(String(response.permissionLevel), mockPermissionLevel) + assert.exists(response.identityProof) + }) + + test('stores the wallet push channel for later signing', async function () { + const plugin = new WalletPluginTackleBox({waitForCallback: waitForCallbackStub()}) + await plugin.login(makeLoginContext(makeMockUI())) + + assert.equal(plugin.data.channelUrl, mockChannelUrl) + assert.equal(plugin.data.channelName, 'TackleBox') + assert.equal(String(plugin.data.signerKey), mockPublicKey) + assert.exists(plugin.data.privateKey) + assert.exists(plugin.data.requestKey) + }) + + test('session data survives a JSON round trip for restores', async function () { + const plugin = new WalletPluginTackleBox({waitForCallback: waitForCallbackStub()}) + await plugin.login(makeLoginContext(makeMockUI())) + + const restored = JSON.parse(JSON.stringify(plugin.data)) + assert.equal(restored.channelUrl, mockChannelUrl) + assert.equal(restored.channelName, 'TackleBox') + assert.equal(String(restored.signerKey), mockPublicKey) + assert.isString(restored.privateKey) + }) + + test('still logs in when the wallet announces no channel', async function () { + const plugin = new WalletPluginTackleBox({ + waitForCallback: waitForCallbackStub(makeLoginCallbackPayload({channel: false})), + }) + const response = await plugin.login(makeLoginContext(makeMockUI())) + + assert.equal(String(response.chain), mockChainId) + assert.isUndefined(plugin.data.channelUrl) + }) + + test('prompts with a QR code, a launch link and a copy fallback', async function () { + const plugin = new WalletPluginTackleBox({waitForCallback: waitForCallbackStub()}) + const ui = makeMockUI() + await plugin.login(makeLoginContext(ui)) + + const args = ui.prompts[0] + assert.exists(args) + assert.equal(args.title, 'Connect with TackleBox') + const elements = args.elements as PromptElement[] + assert.deepEqual( + elements.map((e) => e.type), + ['qr', 'link', 'button'] + ) + + // The launch link targets the wallet's own scheme. + const link: any = elements.find((e) => e.type === 'link') + assert.isTrue(String(link.data.href).startsWith('tacklebox://request/')) + + // The rendered request is a valid ESR identity request. + const qr: any = elements.find((e) => e.type === 'qr') + assert.isTrue(String(qr.data).startsWith('esr://')) + const request = SigningRequest.from(String(qr.data), {zlib}) + assert.isTrue(request.isIdentity()) + }) + + test('opens TackleBox directly when a window exists', async function () { + const fakeWindow = stubBrowserWindow() + + const plugin = new WalletPluginTackleBox({waitForCallback: waitForCallbackStub()}) + await plugin.login(makeLoginContext(makeMockUI())) + + assert.isTrue(fakeWindow.location.href.startsWith('tacklebox://request/')) + // The deep link payload decodes back into the identity request. + const payload = fakeWindow.location.href.slice('tacklebox://request/'.length) + const request = SigningRequest.from(`esr://${payload}`, {zlib}) + assert.isTrue(request.isIdentity()) + }) + + test('honors the disableAutoLaunch option', async function () { + const fakeWindow = stubBrowserWindow() + + const plugin = new WalletPluginTackleBox({ + disableAutoLaunch: true, + waitForCallback: waitForCallbackStub(), + }) + const ui = makeMockUI() + await plugin.login(makeLoginContext(ui)) + + assert.equal(fakeWindow.location.href, 'http://localhost/unittest') + // The launch link stays available in the prompt. + const elements = ui.prompts[0].elements as PromptElement[] + assert.isTrue(elements.some((e) => e.type === 'link')) + }) + + test('listens for the callback on the configured buoy service', async function () { + const waitStub = waitForCallbackStub() + + const plugin = new WalletPluginTackleBox({ + buoyUrl: 'https://buoy.example.com', + waitForCallback: waitStub, + }) + await plugin.login(makeLoginContext(makeMockUI())) + + const receiveOptions: any = waitStub.firstCall.args[0] + assert.equal(receiveOptions.service, 'https://buoy.example.com') + assert.isString(receiveOptions.channel) + }) + + test('rejects when the wallet declines the request', async function () { + // A decline reaches the plugin as a rejection out of waitForCallback, + // which is where protocol-esr turns an empty payload into this error. + const plugin = new WalletPluginTackleBox({ + waitForCallback: sinon + .stub() + .rejects(new Error('The request was cancelled from Anchor.')), + }) + let error: Error | undefined + try { + await plugin.login(makeLoginContext(makeMockUI())) + } catch (err) { + error = err as Error + } + assert.exists(error) + assert.match(String(error!.message), /cancelled|declined/i) + }) + + test('requires a UI', async function () { + const plugin = new WalletPluginTackleBox() + const context = {...makeLoginContext(makeMockUI()), ui: undefined} as any + let error: Error | undefined + try { + await plugin.login(context) + } catch (err) { + error = err as Error + } + assert.exists(error) + assert.match(String(error!.message), /requires a UI/) + }) +}) diff --git a/packages/wallet-plugin-tacklebox/test/tests/sign.ts b/packages/wallet-plugin-tacklebox/test/tests/sign.ts new file mode 100644 index 00000000..a0e13f7e --- /dev/null +++ b/packages/wallet-plugin-tacklebox/test/tests/sign.ts @@ -0,0 +1,224 @@ +import fs from 'fs' +import path from 'path' +import {assert} from 'chai' +import sinon from 'sinon' +import {PermissionLevel, SessionKit} from '@wharfkit/session' +import { + mockChainId, + mockPermissionLevel, + mockSessionKitArgs, + mockSessionKitOptions, + MockStorage, +} from '@wharfkit/mock-data' + +import {WalletPluginTackleBox} from '$lib' +import {makeMockUI, RecordingUserInterface} from '$test/utils/mock-ui' +import { + makeLoginCallbackPayload, + makeTransactCallbackPayload, + mockChannelId, +} from '$test/utils/mock-esr' + +const transferAction = { + authorization: [PermissionLevel.from(mockPermissionLevel)], + account: 'eosio.token', + name: 'transfer', + data: { + from: 'wharfkit1111', + to: 'wharfkittest', + quantity: '0.0001 EOS', + memo: 'wallet-plugin-tacklebox test', + }, +} + +/** The chain head time captured in the recorded get_info fixture. */ +function fixtureHeadTime(): number { + const dir = path.join(__dirname, '..', 'data') + for (const file of fs.readdirSync(dir)) { + const recorded = JSON.parse(fs.readFileSync(path.join(dir, file), 'utf8')) + const head = recorded.json && recorded.json.head_block_time + if (head) { + return new Date(head + 'Z').getTime() + } + } + throw new Error('No recorded get_info fixture found in test/data') +} + +function makeKit(plugin: WalletPluginTackleBox, ui: RecordingUserInterface) { + return new SessionKit( + { + ...mockSessionKitArgs, + ui, + walletPlugins: [plugin], + }, + { + ...mockSessionKitOptions, + storage: new MockStorage(), + } + ) +} + +suite('sign', function () { + this.timeout(120 * 1000) + this.slow(5 * 1000) + + let clock: sinon.SinonFakeTimers | undefined + + setup(function () { + // TAPOS in these tests comes from the recorded chain head; pin "now" + // right after it so request expirations stay in the future no matter + // when the suite actually runs. Timers stay real. + clock = sinon.useFakeTimers({now: fixtureHeadTime() + 1000, toFake: ['Date']}) + }) + + teardown(function () { + clock?.restore() + sinon.restore() + }) + + test('pushes the sealed request over the wallet channel and returns its signature', async function () { + const waitStub = sinon.stub() + const sendStub = sinon.stub() + waitStub.onFirstCall().resolves(makeLoginCallbackPayload()) + waitStub + .onSecondCall() + .callsFake(async () => await makeTransactCallbackPayload(transferAction)) + + const plugin = new WalletPluginTackleBox({waitForCallback: waitStub, send: sendStub}) + const ui = makeMockUI() + const kit = makeKit(plugin, ui) + + const {session} = await kit.login({ + chain: mockChainId, + permissionLevel: mockPermissionLevel, + }) + assert.equal(String(session.actor), 'wharfkit1111') + assert.equal(String(session.permission), 'test') + + const result = await session.transact({action: transferAction}, {broadcast: false}) + + assert.equal(result.signatures.length, 1) + assert.exists(result.resolved) + assert.equal(String(result.signer), mockPermissionLevel) + + // The request went out sealed, addressed to the wallet's channel. + assert.isTrue(sendStub.calledOnce) + const [message, destination] = sendStub.firstCall.args as any[] + assert.instanceOf(message, Uint8Array) + assert.isAbove(message.length, 0) + assert.equal(destination.service, 'https://cb.anchor.link') + assert.equal(destination.channel, mockChannelId) + }) + + test('shows the channel prompt with a countdown and a manual fallback', async function () { + const waitStub = sinon.stub() + const sendStub = sinon.stub() + waitStub.onFirstCall().resolves(makeLoginCallbackPayload()) + waitStub + .onSecondCall() + .callsFake(async () => await makeTransactCallbackPayload(transferAction)) + + const plugin = new WalletPluginTackleBox({waitForCallback: waitStub, send: sendStub}) + const ui = makeMockUI() + const kit = makeKit(plugin, ui) + const {session} = await kit.login({ + chain: mockChainId, + permissionLevel: mockPermissionLevel, + }) + await session.transact({action: transferAction}, {broadcast: false}) + + const prompt = ui.prompts[ui.prompts.length - 1] + assert.equal(prompt.title, 'Sign with TackleBox') + assert.match(String(prompt.body), /TackleBox/) + assert.deepEqual( + (prompt.elements as any[]).map((e) => e.type), + ['countdown', 'link', 'button'] + ) + const link: any = (prompt.elements as any[]).find((e) => e.type === 'link') + assert.isTrue(String(link.data.href).startsWith('tacklebox://request/')) + }) + + test('falls back to the paste flow when no channel was announced', async function () { + const waitStub = sinon.stub() + const sendStub = sinon.stub() + waitStub.onFirstCall().resolves(makeLoginCallbackPayload({channel: false})) + waitStub + .onSecondCall() + .callsFake(async () => await makeTransactCallbackPayload(transferAction)) + + const plugin = new WalletPluginTackleBox({waitForCallback: waitStub, send: sendStub}) + const ui = makeMockUI() + const kit = makeKit(plugin, ui) + const {session} = await kit.login({ + chain: mockChainId, + permissionLevel: mockPermissionLevel, + }) + const result = await session.transact({action: transferAction}, {broadcast: false}) + + assert.equal(result.signatures.length, 1) + assert.isTrue(sendStub.notCalled, 'nothing is pushed without a channel') + + const prompt = ui.prompts[ui.prompts.length - 1] + const types = (prompt.elements as any[]).map((e) => e.type) + assert.deepEqual(types, ['qr', 'link', 'button', 'countdown']) + const qr: any = (prompt.elements as any[]).find((e) => e.type === 'qr') + assert.isTrue(String(qr.data).startsWith('esr://')) + }) + + test('opens TackleBox directly for channel-less signing when a window exists', async function () { + const waitStub = sinon.stub() + const sendStub = sinon.stub() + waitStub.onFirstCall().resolves(makeLoginCallbackPayload({channel: false})) + waitStub + .onSecondCall() + .callsFake(async () => await makeTransactCallbackPayload(transferAction)) + + const fakeWindow = {location: {href: 'http://localhost/unittest'}} + ;(global as any).window = fakeWindow + ;(global as any).navigator = {userAgent: 'mocha-unittest'} + try { + const plugin = new WalletPluginTackleBox({waitForCallback: waitStub, send: sendStub}) + const kit = makeKit(plugin, makeMockUI()) + const {session} = await kit.login({ + chain: mockChainId, + permissionLevel: mockPermissionLevel, + }) + fakeWindow.location.href = 'http://localhost/after-login' + + await session.transact({action: transferAction}, {broadcast: false}) + assert.isTrue(fakeWindow.location.href.startsWith('tacklebox://request/')) + } finally { + delete (global as any).window + delete (global as any).navigator + } + }) + + test('does not navigate when signing over the wallet channel', async function () { + const waitStub = sinon.stub() + const sendStub = sinon.stub() + waitStub.onFirstCall().resolves(makeLoginCallbackPayload()) + waitStub + .onSecondCall() + .callsFake(async () => await makeTransactCallbackPayload(transferAction)) + + const fakeWindow = {location: {href: 'http://localhost/unittest'}} + ;(global as any).window = fakeWindow + ;(global as any).navigator = {userAgent: 'mocha-unittest'} + try { + const plugin = new WalletPluginTackleBox({waitForCallback: waitStub, send: sendStub}) + const kit = makeKit(plugin, makeMockUI()) + const {session} = await kit.login({ + chain: mockChainId, + permissionLevel: mockPermissionLevel, + }) + fakeWindow.location.href = 'http://localhost/after-login' + + await session.transact({action: transferAction}, {broadcast: false}) + // The wallet raises itself on the channel push; the page stays put. + assert.equal(fakeWindow.location.href, 'http://localhost/after-login') + } finally { + delete (global as any).window + delete (global as any).navigator + } + }) +}) diff --git a/packages/wallet-plugin-tacklebox/test/tsconfig.json b/packages/wallet-plugin-tacklebox/test/tsconfig.json new file mode 100644 index 00000000..5e3252bd --- /dev/null +++ b/packages/wallet-plugin-tacklebox/test/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": ["../tsconfig.json", "../../../tsconfig.test.json"], + "compilerOptions": { + "baseUrl": "..", + "paths": { + "$lib": ["src"], + "$lib/*": ["src/*"], + "$test": ["test"], + "$test/*": ["test/*"] + } + }, + "include": ["*.ts", "**/*.ts", "../src/**/*.ts"] +} diff --git a/packages/wallet-plugin-tacklebox/test/utils/mock-esr.ts b/packages/wallet-plugin-tacklebox/test/utils/mock-esr.ts new file mode 100644 index 00000000..1e62ea74 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/test/utils/mock-esr.ts @@ -0,0 +1,88 @@ +import * as zlib from 'pako' +import {APIClient, FetchProvider} from '@wharfkit/antelope' +import { + ABICache, + CallbackPayload, + PermissionLevel, + PrivateKey, + SigningRequest, +} from '@wharfkit/session' +import { + mockChainId, + mockFetch, + mockPermissionLevel, + mockPrivateKey, + mockUrl, +} from '@wharfkit/mock-data' + +export const mockPublicKey = String(PrivateKey.from(mockPrivateKey).toPublic()) +export const mockChannelId = '00000000-0000-0000-0000-00000000cafe' +export const mockChannelUrl = `https://cb.anchor.link/${mockChannelId}` + +const client = new APIClient({provider: new FetchProvider(mockUrl, {fetch: mockFetch})}) +const abiCache = new ABICache(client) + +const mockTapos = { + expiration: '2035-01-01T00:00:00', + ref_block_num: 0, + ref_block_prefix: 0, +} + +function signAndBuildPayload(resolved): CallbackPayload { + const signature = PrivateKey.from(mockPrivateKey).signDigest( + resolved.transaction.signingDigest(mockChainId) + ) + const callback = resolved.getCallback([signature]) + if (!callback) { + throw new Error('mock request has no callback') + } + return {...callback.payload} +} + +/** + * Build the callback payload TackleBox POSTs after the user accepts a login + * request: the signed identity plus the link session announcement. + */ +export function makeLoginCallbackPayload(options: {channel?: boolean} = {}): CallbackPayload { + const request = SigningRequest.identity( + { + callback: {url: 'https://cb.anchor.link/login-channel', background: true}, + scope: 'unittest', + chainId: mockChainId, + }, + {zlib} + ) + const resolved = request.resolve( + new Map(), + PermissionLevel.from(mockPermissionLevel), + mockTapos + ) + const payload = signAndBuildPayload(resolved) + if (options.channel !== false) { + payload.link_ch = mockChannelUrl + payload.link_key = mockPublicKey + payload.link_name = 'TackleBox' + } + return payload +} + +/** + * Build the callback payload TackleBox POSTs after signing a pushed + * transaction request. + */ +export async function makeTransactCallbackPayload(action): Promise { + const request = await SigningRequest.create( + { + action, + chainId: mockChainId, + broadcast: false, + callback: {url: 'https://cb.anchor.link/transact-channel', background: true}, + }, + {zlib, abiProvider: abiCache} + ) + const abis = await request.fetchAbis(abiCache) + const resolved = request.resolve(abis, PermissionLevel.from(mockPermissionLevel), mockTapos) + const payload = signAndBuildPayload(resolved) + payload.link_name = 'TackleBox' + return payload +} diff --git a/packages/wallet-plugin-tacklebox/test/utils/mock-ui.ts b/packages/wallet-plugin-tacklebox/test/utils/mock-ui.ts new file mode 100644 index 00000000..0b9e6adb --- /dev/null +++ b/packages/wallet-plugin-tacklebox/test/utils/mock-ui.ts @@ -0,0 +1,88 @@ +import { + Cancelable, + PromptArgs, + PromptElement, + PromptResponse, + UserInterface, +} from '@wharfkit/session' + +export interface RecordingUserInterface extends UserInterface { + prompts: PromptArgs[] + statuses: string[] + lastPrompt(): PromptArgs | undefined + clickButton(index: number): void + close(reason?: string): void +} + +/** A UserInterface that records every prompt and lets tests drive buttons. */ +export function makeMockUI(): RecordingUserInterface { + const prompts: PromptArgs[] = [] + const statuses: string[] = [] + let rejectCurrent: ((reason: Error) => void) | undefined + + const ui: any = { + prompts, + statuses, + lastPrompt() { + return prompts[prompts.length - 1] + }, + clickButton(index: number) { + const args = ui.lastPrompt() + if (!args) { + throw new Error('No prompt has been shown') + } + const buttons = (args.elements as PromptElement[]).filter((e) => e.type === 'button') + const button: any = buttons[index] + if (!button) { + throw new Error(`No button at index ${index} (found ${buttons.length})`) + } + button.data.onClick() + }, + close(reason = 'Modal closed') { + if (rejectCurrent) { + rejectCurrent(new Error(reason)) + rejectCurrent = undefined + } + }, + status(message: string) { + statuses.push(message) + return Promise.resolve() + }, + prompt(args: PromptArgs): Cancelable { + prompts.push(args) + const promise = new Promise((resolve, reject) => { + rejectCurrent = reject + }) + // Swallow the default unhandled rejection; tests assert on the caller's handling. + promise.catch(() => undefined) + return Object.assign(promise, { + cancel: (reason?: string) => { + ui.close(reason) + return promise as Cancelable + }, + }) as Cancelable + }, + getTranslate() { + return (key: string, options?: Record) => + (options && options.default) || key + }, + translate(key: string) { + return key + }, + addTranslations() { + // Translations are not rendered by the mock. + }, + onLogin: () => Promise.resolve(), + onLoginComplete: () => Promise.resolve(), + onTransact: () => Promise.resolve(), + onTransactComplete: () => Promise.resolve(), + onSign: () => Promise.resolve(), + onSignComplete: () => Promise.resolve(), + onBroadcast: () => Promise.resolve(), + onBroadcastComplete: () => Promise.resolve(), + onAccountCreate: () => Promise.resolve(), + onAccountCreateComplete: () => Promise.resolve(), + onError: () => Promise.resolve(), + } + return ui as RecordingUserInterface +} diff --git a/packages/wallet-plugin-tacklebox/tsconfig.json b/packages/wallet-plugin-tacklebox/tsconfig.json new file mode 100644 index 00000000..e4f39799 --- /dev/null +++ b/packages/wallet-plugin-tacklebox/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "resolveJsonModule": true + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/wallet-plugin-tokenpocket/package.json b/packages/wallet-plugin-tokenpocket/package.json index 0b6ca150..f3523d06 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-rc7", + "version": "4.0.0", "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 6793393f..7c2b98a5 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-rc7", + "version": "4.0.0", "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 8a373c52..f283f401 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-rc7", + "version": "4.0.0", "description": "", "license": "BSD-3-Clause", "engines": { diff --git a/packages/web-ui/package.json b/packages/web-ui/package.json index 598ebce6..3d5f2f92 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-rc7", + "version": "4.0.0", "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 9f4228fd..38d29700 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-rc7", + "version": "4.0.0", "homepage": "https://wharfkit.com", "license": "BSD-3-Clause", "engines": { diff --git a/rolldown.config.mjs b/rolldown.config.mjs index 56308949..3e3fb874 100644 --- a/rolldown.config.mjs +++ b/rolldown.config.mjs @@ -39,6 +39,7 @@ const MEMBERS = { }, }, 'wallet-plugin-anchor': {stripInternal: true, replaceVersion: true}, + 'wallet-plugin-tacklebox': {stripInternal: true}, 'wallet-plugin-scatter': {browser: true, bundleDeps: true, dir: true}, 'wallet-plugin-tokenpocket': {browser: true, bundleDeps: true, dir: true}, webauthn: { diff --git a/scripts/release.ts b/scripts/release.ts index a2beb564..384e7c54 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -1,7 +1,7 @@ import {execFileSync} from 'node:child_process' import {existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync} from 'node:fs' import {tmpdir} from 'node:os' -import {join} from 'node:path' +import {basename, join} from 'node:path' import semver from 'semver' import {checkDependencyLicenses, checkMemberLicenses} from './check-licenses.ts' @@ -194,6 +194,62 @@ function distTag(version: string): string { return semver.prerelease(version) ? 'next' : 'latest' } +// files outside packages/ whose changes alter every published tarball +const SHARED_BUILD_FILES = [ + 'bun.lock', + 'common.mk', + 'rolldown.base.mjs', + 'rolldown.config.mjs', + 'scripts/release.ts', + 'tsconfig.base.json', +] + +// manifest fields whose changes alter nothing a consumer installs +const METADATA_KEYS = ['author', 'bugs', 'description', 'homepage', 'keywords', 'repository'] + +function manifestChangeIsMetadata(base: string, file: string): boolean { + const before = trySh('git', ['show', `${base}:${file}`]) + if (before === null) return false + const a = JSON.parse(before) + const b = JSON.parse(readFileSync(join(ROOT, file), 'utf8')) + for (const key of METADATA_KEYS) { + delete a[key] + delete b[key] + } + return JSON.stringify(a) === JSON.stringify(b) +} + +function promotionBody(list: Member[], base: string): string { + const files = sh('git', ['diff', '--name-only', `${base}..HEAD`]) + .split('\n') + .filter(Boolean) + const names = new Set(publishable(list).map((m) => basename(m.dir))) + const changed = new Set() + const metadataOnly = new Set() + for (const file of files) { + const match = file.match(/^packages\/([^/]+)\/(.+)$/) + if (!match || !names.has(match[1])) continue + const metadata = + match[2] === 'README.md' || + (match[2] === 'package.json' && manifestChangeIsMetadata(base, file)) + if (metadata) metadataOnly.add(match[1]) + else changed.add(match[1]) + } + for (const name of changed) metadataOnly.delete(name) + const lines: string[] = [] + if (changed.size) { + const others = metadataOnly.size ? `, and ${metadataOnly.size} others by metadata only` : '' + lines.push(`Packages changed: ${[...changed].sort().join(', ')}${others}.`) + } else if (metadataOnly.size) { + lines.push(`Packages changed by metadata only: ${[...metadataOnly].sort().join(', ')}.`) + } else { + lines.push('No package changed.') + } + const shared = SHARED_BUILD_FILES.filter((f) => files.includes(f)) + if (shared.length) lines.push(`Shared build changes: ${shared.join(', ')}.`) + return lines.join('\n') +} + function resolveTarget(current: string, arg: string): string { if (semver.valid(arg)) return arg if (arg === 'prerelease') { @@ -299,6 +355,9 @@ function bump(arg: string, dryRun: boolean) { verify({install: true}) + sh('git', ['fetch', 'origin', 'master']) + const body = promotionBody(list, 'origin/master') + log(`bumping to ${target}`) writeVersion(join(ROOT, 'package.json'), target) for (const member of list) writeVersion(member.manifestPath, target) @@ -308,6 +367,7 @@ function bump(arg: string, dryRun: boolean) { if (dryRun) { sh('git', ['checkout', '--', '.']) + log(`promotion PR body would read:\n${body}`) log(`dry run for ${target} complete; changes reverted`) return } @@ -315,7 +375,7 @@ function bump(arg: string, dryRun: boolean) { sh('git', ['add', '-A']) sh('git', ['commit', '-m', `Version ${target}`]) sh('git', ['push', 'origin', 'dev']) - sh('gh', [ + const url = sh('gh', [ 'pr', 'create', '--base', @@ -325,9 +385,9 @@ function bump(arg: string, dryRun: boolean) { '--title', `Version ${target}`, '--body', - `Lockstep release ${target}. Promotes dev to master; publishes on merge via release.yml.`, + body, ]) - log(`promotion PR opened for ${target}`) + log(`promotion PR opened for ${target}: ${url}`) } function publish(force: boolean) { @@ -393,7 +453,14 @@ function publish(force: boolean) { '--title', `Version ${version}`, ] - if (semver.prerelease(version)) releaseArgs.push('--prerelease') + if (semver.prerelease(version)) { + releaseArgs.push('--prerelease') + } else { + releaseArgs.push( + '--notes', + `Release notes: https://wharfkit.com/docs/releases/${version.replace(/\./g, '-')}` + ) + } if (trySh('gh', ['release', 'view', tag]) !== null) { log(`GitHub release for ${tag} already exists`) } else {