Removes every workaround the symlink approach needed, and reverts tsconfig to
upstream's exactly.
The three forked packages — @polkadot/{networks,util-crypto,keyring} — are now
pinned to Gitea tarball URLs in resolutions. @polkadot/util is not, because this
fork does not modify it; it comes from npm like the rest of the scope.
Tarball URLs rather than routing the @polkadot scope to Gitea, because the scope
is mostly *not* forked: routing it would send yarn looking for dozens of packages
that are not there. @quantus is routed as a whole scope in .yarnrc.yml, which is
safe because we own every name in it.
Four things go away, all of which existed for one reason — node resolves a portal
or link symlink to its realpath, after which @polkadot/util-crypto resolved inside
*common's* tree, where it is a workspace source directory with no exports map:
- resolutions pointing four packages at ../common/packages/*/build
- tsconfig.base.json `*.d.ts` path mappings, needed because NodeNext will not
infer an extension for a bare path and looked for a `types` directory
- tsconfig.json overriding those with a runtime `.js` view, because
@polkadot/dev-ts reads that file and tried to import a declaration file as a
module
- --preserve-symlinks in the test scripts
Also drops fflate and @quantus/crypto from ui-keyring's devDependencies; they were
there only because `link:` does not install its target's dependencies.
`yarn lint` now reports 0 errors, down from 25 on pristine upstream. Those were
no-unsafe-assignment and no-unsafe-argument on values that were `any` purely
because the types did not resolve.
Refs quantus/extension#2, quantus/ui#1
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
@polkadot/ui-keyring
A wrapper extending the base @polkadot/keyring interface for usage in the browser: Key management of user accounts including generation and retrieval of keyring pairs from a variety of input combinations.
Usage Examples
All module methods are exposed through a single default export.
Regular
import keyring from @polkadot/ui-keyring
render () {
// encode publicKey to ss58 address
const address = keyring.encodeAddress(publicKey);
// get keyring pair from ss58 address
const pair = keyring.getPair(address);
// ask questions about that particular keyring pair
const isLocked = pair.isLocked;
const meta = pair.meta;
// save account from pair
keyring.saveAccount(pair, password);
// save address without unlocking it
keyring.saveAddress(address, { ...meta });
}
Observables
Option 1: Declarative subscribe/unsubscribe w/ react-with-observable (recommended 'React' way)
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { SingleAddress, SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import React from 'react';
import { Subscribe } from 'react-with-observable';
import { map } from 'rxjs';
class MyReactComponent extends React.PureComponent {
render () {
<Subscribe>
{accountObservable.subject.pipe(
map((allAccounts: SubjectInfo) =>
!allAccounts
? this.renderEmpty()
: Object.values(allAccounts).map((account: SingleAddress) =>
// Your component goes here
console.log(account.json.address)
)
))}
</Subscribe>
}
renderEmpty () {
return (
<div> no accounts to display ... </div>
);
}
}
Option 2: Imperative subscribe/unsubscribe
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { SingleAddress, SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import React from 'react';
import { Subscription } from 'rxjs';
type State = {
allAccounts?: SubjectInfo,
subscriptions?: [Subscription]
}
class MyReactComponent extends React.PureComponent<State> {
componentDidMount () {
const accountSubscription = accountObservable.subject.subscribe((observedAccounts) => {
this.setState({
accounts: observedAccounts
});
})
this.setState({
subscriptions: [accountSubscription]
});
}
componentWillUnmount () {
const { subscriptions } = this.state;
for (s in subscriptions) {
s.subject.unsubscribe();
}
}
render () {
const { accounts } = this.state;
return (
<h1>All Accounts</h1>
{
Object.keys(accounts).map((address: SingleAddress) => {
return <p> {address} </p>;
})
}
)
}
}
FAQ
- Difference between Keyring Accounts and Addresses?
- From the perspective of the keyring, it saves a particular user's unlocked identities as an account, a la keyring.saveAccount(pair, password). So with these accounts you are able to send and sign transactions.
- To save addresses without unlocking them (i.e. because a user might want to have easy access to addresses they frequently transact with), use keyring.saveAddress(address, meta)
- What are 'external' accounts, i.e. when to set the
isExternalmeta key to true?- An external account is one where the keys are not managed by keyring, e.g. in Parity Signer or Ledger Nano.
- SS58 Encode / Decode?
- SS58 is a simple address format designed for Substrate based chains. You can read about its specification in more detail in the Substrate Documentation.
If you have any unanswered/undocumented questions, please raise an issue here.
Users
Keyring is core to many polkadot/substrate apps.