FRE-600: Fix code review blockers

- Consolidated duplicate UndoManagers to single instance
- Fixed connection promise to only resolve on 'connected' status
- Fixed WebSocketProvider import (WebsocketProvider)
- Added proper doc.destroy() cleanup
- Renamed isPresenceInitialized property to avoid conflict

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-25 00:08:01 -04:00
parent 65b552bb08
commit 7c684a42cc
48450 changed files with 5679671 additions and 383 deletions

View File

@@ -0,0 +1,88 @@
import { decodeEncodedContext, encodeContextObject } from '../context.js';
async function getTestContext() {
return {
a: BigInt(1),
b: '"bar"',
c: "'baz'",
d: '!=$&ymbo;s\\',
e: [1, ["'2a'", '"2b"', '2c'], 3],
f: Symbol('hi'),
g: { foo: 'bar' },
h: new URL('http://anza.xyz'),
i: ((await crypto.subtle.generateKey('Ed25519', false /* extractable */, ['sign', 'verify'])) as CryptoKeyPair)
.privateKey,
j: Object.create(null),
k: null,
l: undefined,
m: "'",
n: "\\'",
o: "\\\\'",
p: '🚀',
q: 'حب',
r: 'प्यार',
s: '爱',
} as const;
}
const EXPECTED_URL_ENCODED_CONTEXT =
'a=1n&' +
'b=%22bar%22&' +
"c='baz'&" +
'd=!%3D%24%26ymbo%3Bs%5C&' +
"e=%5B1%2C%20%5B'2a'%2C%20%222b%22%2C%202c%5D%2C%203%5D&" +
'f=Symbol(hi)&' +
'g=%5Bobject%20Object%5D&' +
'h=http%3A%2F%2Fanza.xyz%2F&' +
'i=%5Bobject%20CryptoKey%5D&' +
'j=%5Bobject%20Object%5D&' +
'k=null&' +
'l=undefined&' +
"m='&" +
"n=%5C'&" +
"o=%5C%5C'&" +
'p=%F0%9F%9A%80&' +
'q=%D8%AD%D8%A8&' +
'r=%E0%A4%AA%E0%A5%8D%E0%A4%AF%E0%A4%BE%E0%A4%B0&' +
's=%E7%88%B1';
describe('decodeEncodedContext', () => {
it('produces the expected context object from a URL-encoded search string', () => {
const encodedContext = btoa(EXPECTED_URL_ENCODED_CONTEXT);
expect(decodeEncodedContext(encodedContext)).toStrictEqual({
a: '1n',
b: '"bar"',
c: "'baz'",
d: '!=$&ymbo;s\\',
e: '[1, [\'2a\', "2b", 2c], 3]',
f: 'Symbol(hi)',
g: '[object Object]',
h: 'http://anza.xyz/',
i: '[object CryptoKey]',
j: '[object Object]',
k: 'null',
l: 'undefined',
m: "'",
n: "\\'",
o: "\\\\'",
p: '🚀',
q: 'حب',
r: 'प्यार',
s: '爱',
});
});
});
describe('encodeContextObject', () => {
let context: object;
beforeEach(async () => {
context = await getTestContext();
});
it('produces a string with no single quotes in it', () => {
const encodedContext = encodeContextObject(context);
expect(encodedContext).not.toContain("'");
});
it('produces encoded context that base64 decodes to the expected URL-encoded search string', () => {
const encodedContext = encodeContextObject(context);
expect(atob(encodedContext)).toBe(EXPECTED_URL_ENCODED_CONTEXT);
});
});

View File

@@ -0,0 +1,122 @@
import { isWalletStandardError, WalletStandardError } from '../error.js';
import { getErrorMessage } from '../message-formatter.js';
jest.mock('../message-formatter');
describe('WalletStandardError', () => {
describe('given an error with context', () => {
let errorWithContext: WalletStandardError;
beforeEach(() => {
errorWithContext = new WalletStandardError(
// @ts-expect-error Mock error codes don't conform to `WalletStandardErrorCode`
123,
{ foo: 'bar' }
);
});
it('exposes its error code', () => {
expect(errorWithContext.context).toHaveProperty('__code', 123);
});
it('exposes its context', () => {
expect(errorWithContext.context).toHaveProperty('foo', 'bar');
});
it('exposes no cause', () => {
expect(errorWithContext.cause).toBeUndefined();
});
it('calls the message formatter with the code and context', () => {
expect(getErrorMessage).toHaveBeenCalledWith(123, { foo: 'bar' });
});
});
describe('given an error with no context', () => {
beforeEach(() => {
new WalletStandardError(
// @ts-expect-error Mock error codes don't conform to `WalletStandardErrorCode`
123,
undefined
);
});
it('calls the message formatter with undefined context', () => {
expect(getErrorMessage).toHaveBeenCalledWith(123, undefined);
});
});
describe('given an error with a cause', () => {
let errorWithCause: WalletStandardError;
let cause: unknown;
beforeEach(() => {
cause = {};
errorWithCause = new WalletStandardError(
// @ts-expect-error Mock error codes don't conform to `WalletStandardErrorCode`
123,
{ cause }
);
});
it('exposes its cause', () => {
expect(errorWithCause.cause).toBe(cause);
});
});
describe.each(['cause'])('given an error with only the `%s` property from `ErrorOptions` present', (propName) => {
let errorOptionValue: unknown;
let errorWithOption: WalletStandardError;
beforeEach(() => {
errorOptionValue = Symbol();
errorWithOption = new WalletStandardError(
// @ts-expect-error Mock error codes don't conform to `WalletStandardErrorCode`
123,
{ [propName]: errorOptionValue }
);
});
it('omits the error option from its context', () => {
expect(errorWithOption.context).not.toHaveProperty(propName);
});
it('calls the message formatter with the error option omitted', () => {
expect(getErrorMessage).toHaveBeenCalledWith(
123,
expect.not.objectContaining({ [propName]: errorOptionValue })
);
});
});
it('sets its message to the output of the message formatter', async () => {
expect.assertions(1);
jest.mocked(getErrorMessage).mockReturnValue('o no');
await jest.isolateModulesAsync(async () => {
const WalletStandardErrorModule =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
await import('../error');
// @ts-expect-error Mock error codes don't conform to `WalletStandardErrorCode`
const error456 = new WalletStandardErrorModule.WalletStandardError(456);
expect(error456).toHaveProperty('message', 'o no');
});
});
});
describe('isWalletStandardError()', () => {
let error123: WalletStandardError;
beforeEach(() => {
// @ts-expect-error Mock error codes don't conform to `WalletStandardErrorCode`
error123 = new WalletStandardError(123);
});
it('returns `true` for an instance of `WalletStandardError`', () => {
expect(isWalletStandardError(error123)).toBe(true);
});
it('returns `false` for an instance of `Error`', () => {
expect(isWalletStandardError(new Error('bad thing'))).toBe(false);
});
it('returns `true` when the error code matches', () => {
expect(
isWalletStandardError(
error123,
// @ts-expect-error Mock error codes don't conform to `WalletStandardErrorCode`
123
)
).toBe(true);
});
it('returns `false` when the error code does not match', () => {
expect(
isWalletStandardError(
error123,
// @ts-expect-error Mock error codes don't conform to `WalletStandardErrorCode`
456
)
).toBe(false);
});
});

View File

@@ -0,0 +1,188 @@
import type { WalletStandardErrorCode } from '../codes.js';
import { encodeContextObject } from '../context.js';
import { getErrorMessage } from '../message-formatter.js';
import * as MessagesModule from '../messages.js';
jest.mock('../context');
jest.mock('../messages', () => ({
get WalletStandardErrorMessages() {
return {};
},
__esModule: true,
}));
describe('getErrorMessage', () => {
describe('in production mode', () => {
let originalEnv = process.env;
beforeEach(() => {
originalEnv = process.env;
process.env = { ...originalEnv, NODE_ENV: 'production' };
});
afterEach(() => {
process.env = originalEnv; // Reset to the original env
});
it('renders advice on where to decode a context-less error', () => {
const message = getErrorMessage(
// @ts-expect-error Mock error codes don't conform to `WalletStandardErrorCode`
123
);
expect(message).toBe(
'Wallet Standard error #123; Decode this error by running `npx @wallet-standard/errors decode -- 123`'
);
});
it('does not call the context encoder when the error has no context', () => {
getErrorMessage(
// @ts-expect-error Mock error codes don't conform to `WalletStandardErrorCode`
123
);
expect(encodeContextObject).not.toHaveBeenCalled();
});
it('does not call the context encoder when the error context has no keys', () => {
const context = {};
getErrorMessage(
// @ts-expect-error Mock error codes don't conform to `WalletStandardErrorCode`
123,
context
);
expect(encodeContextObject).not.toHaveBeenCalled();
});
it('calls the context encoder with the context', () => {
const context = { foo: 'bar' };
getErrorMessage(
// @ts-expect-error Mock error codes don't conform to `WalletStandardErrorCode`
123,
context
);
expect(encodeContextObject).toHaveBeenCalledWith(context);
});
it('renders advice on where to decode an error with encoded context', () => {
jest.mocked(encodeContextObject).mockReturnValue('ENCODED_CONTEXT');
const context = { foo: 'bar' };
const message = getErrorMessage(123 as WalletStandardErrorCode, context);
expect(message).toBe(
"Wallet Standard error #123; Decode this error by running `npx @wallet-standard/errors decode -- 123 'ENCODED_CONTEXT'`"
);
});
it('renders no encoded context in the decoding advice when the context has no keys', () => {
jest.mocked(encodeContextObject).mockReturnValue('ENCODED_CONTEXT');
const context = {};
const message = getErrorMessage(123 as WalletStandardErrorCode, context);
expect(message).toBe(
'Wallet Standard error #123; Decode this error by running `npx @wallet-standard/errors decode -- 123`'
);
});
});
describe('in dev mode', () => {
let originalEnv = process.env;
beforeEach(() => {
originalEnv = process.env;
process.env = { ...originalEnv, NODE_ENV: 'development' };
});
afterEach(() => {
process.env = originalEnv; // Reset to the original env
});
it('renders static error messages', () => {
const messagesSpy = jest.spyOn(MessagesModule, 'WalletStandardErrorMessages', 'get');
messagesSpy.mockReturnValue({
// @ts-expect-error Mock error config doesn't conform to exported config.
123: 'static error message',
});
const message = getErrorMessage(
// @ts-expect-error Mock error config doesn't conform to exported config.
123
);
expect(message).toBe('static error message');
});
it.each([
{
expected: "Something awful happened: 'bar'. How awful!",
input: "Something $severity happened: '$foo'. How $severity!",
},
// Literal backslashes, escaped dollar signs
{
expected: 'How \\awful\\ is the $severity?',
input: 'How \\\\$severity\\\\ is the \\$severity?',
},
// Variable at beginning of sequence
{ expected: 'awful times!', input: '$severity times!' },
// Variable at end of sequence
{ expected: "Isn't it awful?", input: "Isn't it $severity?" },
// Variable in middle of text sequence
{ expected: '~awful~', input: '~$severity~' },
// Variable interpolation with no value in the lookup
{ expected: 'Is $thing a sandwich?', input: 'Is $thing a sandwich?' },
// Variable that has, as a substring, some other value in the lookup
{ expected: '$fool', input: '$fool' },
// Trick for butting a variable up against regular text
{ expected: 'barl', input: '$foo\\l' },
// Escaped variable marker
{ expected: "It's the $severity, ya hear?", input: "It's the \\$severity, ya hear?" },
// Single dollar sign
{ expected: ' $ ', input: ' $ ' },
// Single dollar sign at start
{ expected: '$ ', input: '$ ' },
// Single dollar sign at end
{ expected: ' $', input: ' $' },
// Double dollar sign with legitimate variable name
{ expected: ' $bar ', input: ' $$foo ' },
// Double dollar sign with legitimate variable name at start
{ expected: '$bar ', input: '$$foo ' },
// Double dollar sign with legitimate variable name at end
{ expected: ' $bar', input: ' $$foo' },
// Single escape sequence
{ expected: ' ', input: ' \\ ' },
// Single escape sequence at start
{ expected: ' ', input: '\\ ' },
// Single escape sequence at end
{ expected: ' ', input: ' \\' },
// Double escape sequence
{ expected: ' \\ ', input: ' \\\\ ' },
// Double escape sequence at start
{ expected: '\\ ', input: '\\\\ ' },
// Double escape sequence at end
{ expected: ' \\', input: ' \\\\' },
// Just text
{ expected: 'Some unencumbered text.', input: 'Some unencumbered text.' },
// Empty string
{ expected: '', input: '' },
])('interpolates variables into the error message format string `"$input"`', ({ input, expected }) => {
const messagesSpy = jest.spyOn(MessagesModule, 'WalletStandardErrorMessages', 'get');
messagesSpy.mockReturnValue({
// @ts-expect-error Mock error config doesn't conform to exported config.
123: input,
});
const message = getErrorMessage(
// @ts-expect-error Mock error context doesn't conform to exported context.
123,
{ foo: 'bar', severity: 'awful' }
);
expect(message).toBe(expected);
});
it('interpolates a Uint8Array variable into a error message format string', () => {
const messagesSpy = jest.spyOn(MessagesModule, 'WalletStandardErrorMessages', 'get');
messagesSpy.mockReturnValue({
// @ts-expect-error Mock error config doesn't conform to exported config.
123: 'Here is some data: $data',
});
const message = getErrorMessage(
// @ts-expect-error Mock error context doesn't conform to exported context.
123,
{ data: new Uint8Array([1, 2, 3, 4]) }
);
expect(message).toBe('Here is some data: 1,2,3,4');
});
it('interpolates an undefined variable into a error message format string', () => {
const messagesSpy = jest.spyOn(MessagesModule, 'WalletStandardErrorMessages', 'get');
messagesSpy.mockReturnValue({
// @ts-expect-error Mock error config doesn't conform to exported config.
123: 'Here is a variable: $variable',
});
const message = getErrorMessage(
// @ts-expect-error Mock error context doesn't conform to exported context.
123,
{ variable: undefined }
);
expect(message).toBe('Here is a variable: undefined');
});
});
});

View File

@@ -0,0 +1,59 @@
import * as WalletStandardErrorCodeModule from '../codes.js';
import type { WalletStandardErrorCode } from '../codes.js';
import type { WalletStandardErrorContext } from '../context.js';
import { isWalletStandardError, WalletStandardError } from '../error.js';
const { WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND, WALLET_STANDARD_ERROR__REGISTRY__WALLET_NOT_FOUND } =
WalletStandardErrorCodeModule;
// If this line raises a type error, you might have forgotten to add a new error to the
// `WalletStandardErrorCode` union in `src/codes.ts`.
Object.values(WalletStandardErrorCodeModule) satisfies WalletStandardErrorCode[];
const walletAccountNotFoundError = new WalletStandardError(WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND, {
address: 'abc',
walletName: 'Mock Wallet',
});
{
const code = walletAccountNotFoundError.context.__code;
code satisfies typeof WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND;
// @ts-expect-error Wrong error code.
code satisfies typeof WALLET_STANDARD_ERROR__REGISTRY__WALLET_NOT_FOUND;
}
walletAccountNotFoundError.context satisfies WalletStandardErrorContext[typeof WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND];
// @ts-expect-error Non existent context property.
walletAccountNotFoundError.context.chains;
new WalletStandardError(WALLET_STANDARD_ERROR__REGISTRY__WALLET_NOT_FOUND);
// @ts-expect-error Missing context property (`address` and `walletName`)
new WalletStandardError(WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND);
const unknownError = null as unknown as WalletStandardError;
if (unknownError.context.__code === WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND) {
unknownError.context satisfies WalletStandardErrorContext[typeof WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND];
// @ts-expect-error Context belongs to another error code
unknownError.context satisfies WalletStandardErrorContext[typeof WALLET_STANDARD_ERROR__REGISTRY__WALLET_NOT_FOUND];
}
const e = null as unknown;
if (isWalletStandardError(e)) {
e.context satisfies Readonly<{ __code: WalletStandardErrorCode }>;
}
if (isWalletStandardError(e, WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND)) {
e.context satisfies WalletStandardErrorContext[typeof WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND];
// @ts-expect-error Context belongs to another error code
e.context satisfies WalletStandardErrorContext[typeof WALLET_STANDARD_ERROR__REGISTRY__WALLET_NOT_FOUND];
}
// `WalletStandardErrorContext` must not contain any keys reserved by `ErrorOptions` (eg. `cause`)
null as unknown as WalletStandardErrorContext satisfies {
[Code in keyof WalletStandardErrorContext]: WalletStandardErrorContext[Code] extends undefined
? undefined
: {
[PP in keyof WalletStandardErrorContext[Code]]: PP extends keyof ErrorOptions
? never
: WalletStandardErrorContext[Code][PP];
};
};

63
node_modules/@wallet-standard/errors/src/cli.ts generated vendored Normal file
View File

@@ -0,0 +1,63 @@
import chalk from 'chalk';
import { Command, InvalidArgumentError } from 'commander';
import { version } from '../package.json';
import type { WalletStandardErrorCode } from './codes.js';
import { decodeEncodedContext } from './context.js';
import { getHumanReadableErrorMessage } from './message-formatter.js';
import { WalletStandardErrorMessages } from './messages.js';
const program = new Command();
program
.name('@wallet-standard/errors')
.description('Decode Wallet Standard JavaScript errors thrown in production')
.version(version);
program
.command('decode')
.description('Decode a `WalletStandardErrorCode` to a human-readable message')
.argument('<code>', 'numeric error code to decode', (rawCode) => {
const code = parseInt(rawCode, 10);
if (isNaN(code) || `${code}` !== rawCode) {
throw new InvalidArgumentError('It must be an integer');
}
if (!(code in WalletStandardErrorMessages)) {
throw new InvalidArgumentError('There exists no error with that code');
}
return code;
})
.argument('[encodedContext]', 'encoded context to interpolate into the error message', (encodedContext) => {
try {
return decodeEncodedContext(encodedContext);
} catch (e) {
throw new InvalidArgumentError('Encoded context malformed');
}
})
.action((code: number, context) => {
const message = getHumanReadableErrorMessage(code as WalletStandardErrorCode, context);
console.log(`
${
chalk.bold(
chalk.rgb(154, 71, 255)('[') +
chalk.rgb(144, 108, 244)('D') +
chalk.rgb(134, 135, 233)('e') +
chalk.rgb(122, 158, 221)('c') +
chalk.rgb(110, 178, 209)('o') +
chalk.rgb(95, 195, 196)('d') +
chalk.rgb(79, 212, 181)('e') +
chalk.rgb(57, 227, 166)('d') +
chalk.rgb(19, 241, 149)(']')
) + chalk.rgb(19, 241, 149)(' Wallet Standard error code #' + code)
}
- ${message}`);
if (context) {
console.log(`
${chalk.yellowBright(chalk.bold('[Context]'))}
${JSON.stringify(context, null, 4).split('\n').join('\n ')}`);
}
});
export function run(argv: readonly string[]) {
program.parse(argv);
}

64
node_modules/@wallet-standard/errors/src/codes.ts generated vendored Normal file
View File

@@ -0,0 +1,64 @@
/**
* To add a new error, follow the instructions at
* https://github.com/wallet-standard/wallet-standard/tree/master/packages/core/errors/#adding-a-new-error
*
* WARNING:
* - Don't remove error codes
* - Don't change or reorder error codes.
*
* Good naming conventions:
* - Prefixing common errors — e.g. under the same package — can be a good way to namespace them. E.g. All codec-related errors start with `WALLET_STANDARD_ERROR__ACCOUNT__`.
* - Use consistent names — e.g. choose `PDA` or `PROGRAM_DERIVED_ADDRESS` and stick with it. Ensure your names are consistent with existing error codes. The decision might have been made for you.
* - Recommended prefixes and suffixes:
* - `MALFORMED_`: Some input was not constructed properly. E.g. `MALFORMED_BASE58_ENCODED_ADDRESS`.
* - `INVALID_`: Some input is invalid (other than because it was MALFORMED). E.g. `INVALID_NUMBER_OF_BYTES`.
* - `EXPECTED_`: Some input was different than expected, no need to specify the "GOT" part unless necessary. E.g. `EXPECTED_DECODED_ACCOUNT`.
* - `_CANNOT_`: Some operation cannot be performed or some input cannot be used due to some condition. E.g. `CANNOT_DECODE_EMPTY_BYTE_ARRAY` or `PDA_CANNOT_END_WITH_PDA_MARKER`.
* - `_MUST_BE_`: Some condition must be true. E.g. `NONCE_TRANSACTION_FIRST_INSTRUCTION_MUST_BE_ADVANCE_NONCE`.
* - `_FAILED_TO_`: Tried to perform some operation and failed. E.g. `FAILED_TO_DECODE_ACCOUNT`.
* - `_NOT_FOUND`: Some operation lead to not finding something. E.g. `ACCOUNT_NOT_FOUND`.
* - `_OUT_OF_RANGE`: Some value is out of range. E.g. `ENUM_DISCRIMINATOR_OUT_OF_RANGE`.
* - `_EXCEEDED`: Some limit was exceeded. E.g. `PDA_MAX_SEED_LENGTH_EXCEEDED`.
* - `_MISMATCH`: Some elements do not match. E.g. `ENCODER_DECODER_FIXED_SIZE_MISMATCH`.
* - `_MISSING`: Some required input is missing. E.g. `TRANSACTION_FEE_PAYER_MISSING`.
* - `_UNIMPLEMENTED`: Some required component is not available in the environment. E.g. `SUBTLE_CRYPTO_VERIFY_FUNCTION_UNIMPLEMENTED`.
*/
// Registry-related errors.
// Reserve error codes in the range [3834000-3834999].
export const WALLET_STANDARD_ERROR__REGISTRY__WALLET_NOT_FOUND = 3834000 as const;
export const WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND = 3834001 as const;
// User-related errors.
// Reserve error codes in the range [4001000-4001999].
export const WALLET_STANDARD_ERROR__USER__REQUEST_REJECTED = 4001000 as const;
// Feature-related errors.
// Reserve error codes in the range [6160000-6160999].
export const WALLET_STANDARD_ERROR__FEATURES__WALLET_ACCOUNT_CHAIN_UNSUPPORTED = 6160000 as const;
export const WALLET_STANDARD_ERROR__FEATURES__WALLET_ACCOUNT_FEATURE_UNIMPLEMENTED = 6160001 as const;
export const WALLET_STANDARD_ERROR__FEATURES__WALLET_FEATURE_UNIMPLEMENTED = 6160002 as const;
/**
* A union of every Wallet Standard error code
*
* You might be wondering why this is not a TypeScript enum or const enum.
*
* One of the goals of this library is to enable people to use some or none of it without having to
* bundle all of it.
*
* If we made the set of error codes an enum then anyone who imported it (even if to only use a
* single error code) would be forced to bundle every code and its label.
*
* Const enums appear to solve this problem by letting the compiler inline only the codes that are
* actually used. Unfortunately exporting ambient (const) enums from a library like
* `@wallet-standard/errors` is not safe, for a variety of reasons covered here:
* https://stackoverflow.com/a/28818850
*/
export type WalletStandardErrorCode =
| typeof WALLET_STANDARD_ERROR__FEATURES__WALLET_ACCOUNT_CHAIN_UNSUPPORTED
| typeof WALLET_STANDARD_ERROR__FEATURES__WALLET_ACCOUNT_FEATURE_UNIMPLEMENTED
| typeof WALLET_STANDARD_ERROR__FEATURES__WALLET_FEATURE_UNIMPLEMENTED
| typeof WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND
| typeof WALLET_STANDARD_ERROR__REGISTRY__WALLET_NOT_FOUND
| typeof WALLET_STANDARD_ERROR__USER__REQUEST_REJECTED;

77
node_modules/@wallet-standard/errors/src/context.ts generated vendored Normal file
View File

@@ -0,0 +1,77 @@
import type {
WALLET_STANDARD_ERROR__FEATURES__WALLET_ACCOUNT_CHAIN_UNSUPPORTED,
WALLET_STANDARD_ERROR__FEATURES__WALLET_ACCOUNT_FEATURE_UNIMPLEMENTED,
WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND,
WALLET_STANDARD_ERROR__FEATURES__WALLET_FEATURE_UNIMPLEMENTED,
WalletStandardErrorCode,
} from './codes.js';
type DefaultUnspecifiedErrorContextToUndefined<T> = {
[P in WalletStandardErrorCode]: P extends keyof T ? T[P] : undefined;
};
/**
* To add a new error, follow the instructions at
* https://github.com/wallet-standard/wallet-standard/tree/master/packages/core/errors/#adding-a-new-error
*
* WARNING:
* - Don't change or remove members of an error's context.
*/
export type WalletStandardErrorContext = DefaultUnspecifiedErrorContextToUndefined<{
[WALLET_STANDARD_ERROR__FEATURES__WALLET_ACCOUNT_CHAIN_UNSUPPORTED]: {
address: string;
chain: string;
featureName: string;
supportedChains: string[];
supportedFeatures: string[];
};
[WALLET_STANDARD_ERROR__FEATURES__WALLET_ACCOUNT_FEATURE_UNIMPLEMENTED]: {
address: string;
featureName: string;
supportedChains: string[];
supportedFeatures: string[];
};
[WALLET_STANDARD_ERROR__FEATURES__WALLET_FEATURE_UNIMPLEMENTED]: {
featureName: string;
supportedFeatures: string[];
supportedChains: string[];
walletName: string;
};
[WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND]: {
address: string;
walletName: string;
};
}>;
export function decodeEncodedContext(encodedContext: string): object {
const decodedUrlString = atob(encodedContext);
return Object.fromEntries(new URLSearchParams(decodedUrlString).entries());
}
function encodeValue(value: unknown): string {
if (Array.isArray(value)) {
const commaSeparatedValues = value.map(encodeValue).join('%2C%20' /* ", " */);
return '%5B' /* "[" */ + commaSeparatedValues + /* "]" */ '%5D';
} else if (typeof value === 'bigint') {
return `${value}n`;
} else {
return encodeURIComponent(
String(
value != null && Object.getPrototypeOf(value) === null
? // Plain objects with no prototype don't have a `toString` method.
// Convert them before stringifying them.
{ ...(value as object) }
: value
)
);
}
}
function encodeObjectContextEntry([key, value]: [string, unknown]): `${typeof key}=${string}` {
return `${key}=${encodeValue(value)}`;
}
export function encodeContextObject(context: object): string {
const searchParamsString = Object.entries(context).map(encodeObjectContextEntry).join('&');
return btoa(searchParamsString);
}

59
node_modules/@wallet-standard/errors/src/error.ts generated vendored Normal file
View File

@@ -0,0 +1,59 @@
import type { WalletStandardErrorCode } from './codes.js';
import type { WalletStandardErrorContext } from './context.js';
import { getErrorMessage } from './message-formatter.js';
export function isWalletStandardError<TErrorCode extends WalletStandardErrorCode>(
e: unknown,
code?: TErrorCode
): e is WalletStandardError<TErrorCode> {
const isWalletStandardError = e instanceof Error && e.name === 'WalletStandardError';
if (isWalletStandardError) {
if (code !== undefined) {
return (e as WalletStandardError<TErrorCode>).context.__code === code;
}
return true;
}
return false;
}
type WalletStandardErrorCodedContext = Readonly<{
[P in WalletStandardErrorCode]: (WalletStandardErrorContext[P] extends undefined
? object
: WalletStandardErrorContext[P]) & {
__code: P;
};
}>;
export class WalletStandardError<TErrorCode extends WalletStandardErrorCode = WalletStandardErrorCode> extends Error {
readonly context: WalletStandardErrorCodedContext[TErrorCode];
constructor(
...[code, contextAndErrorOptions]: WalletStandardErrorContext[TErrorCode] extends undefined
? [code: TErrorCode, errorOptions?: ErrorOptions | undefined]
: [
code: TErrorCode,
contextAndErrorOptions: WalletStandardErrorContext[TErrorCode] & (ErrorOptions | undefined),
]
) {
let context: WalletStandardErrorContext[TErrorCode] | undefined;
let errorOptions: ErrorOptions | undefined;
if (contextAndErrorOptions) {
// If the `ErrorOptions` type ever changes, update this code.
const { cause, ...contextRest } = contextAndErrorOptions;
if (cause) {
errorOptions = { cause };
}
if (Object.keys(contextRest).length > 0) {
context = contextRest as WalletStandardErrorContext[TErrorCode];
}
}
const message = getErrorMessage(code, context);
super(message, errorOptions);
this.context = {
__code: code,
...context,
} as WalletStandardErrorCodedContext[TErrorCode];
// This is necessary so that `isWalletStandardError()` can identify a `WalletStandardError`
// without having to import the class for use in an `instanceof` check.
this.name = 'WalletStandardError';
}
}

3
node_modules/@wallet-standard/errors/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,3 @@
export * from './codes.js';
export * from './error.js';
export * from './stack-trace.js';

View File

@@ -0,0 +1,101 @@
import type { WalletStandardErrorCode } from './codes.js';
import { encodeContextObject } from './context.js';
import { WalletStandardErrorMessages } from './messages.js';
const enum StateType {
EscapeSequence,
Text,
Variable,
}
type State = Readonly<{
[START_INDEX]: number;
[TYPE]: StateType;
}>;
const START_INDEX = 'i';
const TYPE = 't';
export function getHumanReadableErrorMessage<TErrorCode extends WalletStandardErrorCode>(
code: TErrorCode,
context: object = {}
): string {
const messageFormatString = WalletStandardErrorMessages[code];
if (messageFormatString.length === 0) {
return '';
}
let state: State;
function commitStateUpTo(endIndex?: number) {
if (state[TYPE] === StateType.Variable) {
const variableName = messageFormatString.slice(state[START_INDEX] + 1, endIndex);
fragments.push(
variableName in context ? `${context[variableName as keyof typeof context]}` : `$${variableName}`
);
} else if (state[TYPE] === StateType.Text) {
fragments.push(messageFormatString.slice(state[START_INDEX], endIndex));
}
}
const fragments: string[] = [];
messageFormatString.split('').forEach((char, ii) => {
if (ii === 0) {
state = {
[START_INDEX]: 0,
[TYPE]:
messageFormatString[0] === '\\'
? StateType.EscapeSequence
: messageFormatString[0] === '$'
? StateType.Variable
: StateType.Text,
};
return;
}
let nextState;
switch (state[TYPE]) {
case StateType.EscapeSequence:
nextState = { [START_INDEX]: ii, [TYPE]: StateType.Text };
break;
case StateType.Text:
if (char === '\\') {
nextState = { [START_INDEX]: ii, [TYPE]: StateType.EscapeSequence };
} else if (char === '$') {
nextState = { [START_INDEX]: ii, [TYPE]: StateType.Variable };
}
break;
case StateType.Variable:
if (char === '\\') {
nextState = { [START_INDEX]: ii, [TYPE]: StateType.EscapeSequence };
} else if (char === '$') {
nextState = { [START_INDEX]: ii, [TYPE]: StateType.Variable };
} else if (!char.match(/\w/)) {
nextState = { [START_INDEX]: ii, [TYPE]: StateType.Text };
}
break;
}
if (nextState) {
if (state !== nextState) {
commitStateUpTo(ii);
}
state = nextState;
}
});
commitStateUpTo();
return fragments.join('');
}
export function getErrorMessage<TErrorCode extends WalletStandardErrorCode>(
code: TErrorCode,
context: object = {}
): string {
if (process.env.NODE_ENV !== 'production') {
return getHumanReadableErrorMessage(code, context);
} else {
let decodingAdviceMessage = `Wallet Standard error #${code}; Decode this error by running \`npx @wallet-standard/errors decode -- ${code}`;
if (Object.keys(context).length) {
/**
* DANGER: Be sure that the shell command is escaped in such a way that makes it
* impossible for someone to craft malicious context values that would result in
* an exploit against anyone who bindly copy/pastes it into their terminal.
*/
decodingAdviceMessage += ` '${encodeContextObject(context)}'`;
}
return `${decodingAdviceMessage}\``;
}
}

35
node_modules/@wallet-standard/errors/src/messages.ts generated vendored Normal file
View File

@@ -0,0 +1,35 @@
import type { WalletStandardErrorCode } from './codes.js';
import {
WALLET_STANDARD_ERROR__FEATURES__WALLET_ACCOUNT_CHAIN_UNSUPPORTED,
WALLET_STANDARD_ERROR__FEATURES__WALLET_ACCOUNT_FEATURE_UNIMPLEMENTED,
WALLET_STANDARD_ERROR__FEATURES__WALLET_FEATURE_UNIMPLEMENTED,
WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND,
WALLET_STANDARD_ERROR__REGISTRY__WALLET_NOT_FOUND,
WALLET_STANDARD_ERROR__USER__REQUEST_REJECTED,
} from './codes.js';
/**
* To add a new error, follow the instructions at
* https://github.com/wallet-standard/wallet-standard/tree/master/packages/core/errors#adding-a-new-error
*
* WARNING:
* - Don't change the meaning of an error message.
*/
export const WalletStandardErrorMessages: Readonly<{
// This type makes this data structure exhaustive with respect to `WalletStandardErrorCode`.
// TypeScript will fail to build this project if add an error code without a message.
[P in WalletStandardErrorCode]: string;
}> = {
[WALLET_STANDARD_ERROR__FEATURES__WALLET_ACCOUNT_CHAIN_UNSUPPORTED]:
'The wallet account $address does not support the chain `$chain`',
[WALLET_STANDARD_ERROR__FEATURES__WALLET_ACCOUNT_FEATURE_UNIMPLEMENTED]:
'The wallet account $address does not support the `$featureName` feature',
[WALLET_STANDARD_ERROR__FEATURES__WALLET_FEATURE_UNIMPLEMENTED]:
"The wallet '$walletName' does not support the `$featureName` feature",
[WALLET_STANDARD_ERROR__REGISTRY__WALLET_ACCOUNT_NOT_FOUND]:
"No account with address $address could be found in the '$walletName' wallet",
[WALLET_STANDARD_ERROR__REGISTRY__WALLET_NOT_FOUND]:
'No underlying Wallet Standard wallet could be found for this handle. This can happen if ' +
'the wallet associated with the handle has been unregistered.',
[WALLET_STANDARD_ERROR__USER__REQUEST_REJECTED]: 'The user rejected the request',
};

View File

@@ -0,0 +1,5 @@
export function safeCaptureStackTrace(...args: Parameters<typeof Error.captureStackTrace>): void {
if ('captureStackTrace' in Error && typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(...args);
}
}