From 80d97fa4b3d2c3fd5fb710c2bdaae10ab521f129 Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Wed, 26 Apr 2023 21:47:40 +0700 Subject: [PATCH 01/56] fix(aepp): call `onDetected` always with `newWallet` --- src/aepp-wallet-communication/wallet-detector.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/aepp-wallet-communication/wallet-detector.ts b/src/aepp-wallet-communication/wallet-detector.ts index 732c061aaf..eb557afbbc 100644 --- a/src/aepp-wallet-communication/wallet-detector.ts +++ b/src/aepp-wallet-communication/wallet-detector.ts @@ -22,7 +22,7 @@ interface Wallets { [key: string]: Wallet } */ export default ( connection: BrowserConnection, - onDetected: ({ wallets, newWallet }: { wallets: Wallets; newWallet?: Wallet }) => void, + onDetected: ({ wallets, newWallet }: { wallets: Wallets; newWallet: Wallet }) => void, ): () => void => { if (window == null) throw new UnsupportedPlatformError('Window object not found, you can run wallet detector only in browser'); const wallets: Wallets = {}; @@ -49,7 +49,6 @@ export default ( wallets[wallet.info.id] = wallet; onDetected({ wallets, newWallet: wallet }); }, () => {}); - if (Object.keys(wallets).length > 0) onDetected({ wallets }); return () => connection.disconnect(); }; From f7f3dc2c7c9485fa739303005d61ddce4d416053 Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Thu, 27 Apr 2023 14:10:05 +0700 Subject: [PATCH 02/56] refactor: enable strictPropertyInitialization --- src/account/Base.ts | 2 +- src/aepp-wallet-communication/schema.ts | 2 +- src/channel/Base.ts | 8 ++++---- src/contract/compiler/Cli.ts | 2 +- tsconfig.json | 1 + 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/account/Base.ts b/src/account/Base.ts index 47f8dd48f1..05d8b41cd9 100644 --- a/src/account/Base.ts +++ b/src/account/Base.ts @@ -63,5 +63,5 @@ export default abstract class AccountBase { /** * Account address */ - readonly address: Encoded.AccountAddress; + readonly address!: Encoded.AccountAddress; } diff --git a/src/aepp-wallet-communication/schema.ts b/src/aepp-wallet-communication/schema.ts index 6279565873..ec875b9c31 100644 --- a/src/aepp-wallet-communication/schema.ts +++ b/src/aepp-wallet-communication/schema.ts @@ -64,7 +64,7 @@ const rpcErrors: Array<(new (data?: any) => RpcError) & { code: number }> = []; export abstract class RpcError extends BaseError { static code: number; - code: number; + code!: number; data?: any; diff --git a/src/channel/Base.ts b/src/channel/Base.ts index 38e236be06..93fb1d7b47 100644 --- a/src/channel/Base.ts +++ b/src/channel/Base.ts @@ -55,7 +55,7 @@ function snakeToPascalObjKeys(obj: object): Type { export default class Channel { _eventEmitter = new EventEmitter(); - _pingTimeoutId: NodeJS.Timeout; + _pingTimeoutId!: NodeJS.Timeout; _nextRpcMessageId = 0; @@ -73,13 +73,13 @@ export default class Channel { _status: ChannelStatus = 'disconnected'; - _fsm: ChannelFsm; + _fsm!: ChannelFsm; - _websocket: W3CWebSocket; + _websocket!: W3CWebSocket; _state: Encoded.Transaction | '' = ''; - _options: ChannelOptions; + _options!: ChannelOptions; _channelId?: Encoded.Channel; diff --git a/src/contract/compiler/Cli.ts b/src/contract/compiler/Cli.ts index acad9f97e4..ce3fc2a595 100644 --- a/src/contract/compiler/Cli.ts +++ b/src/contract/compiler/Cli.ts @@ -22,7 +22,7 @@ const getPackagePath = (): string => { export default class CompilerCli extends CompilerBase { #path: string; - #ensureCompatibleVersion: Promise; + #ensureCompatibleVersion = Promise.resolve(); constructor( compilerPath = resolve(getPackagePath(), './bin/aesophia_cli'), diff --git a/tsconfig.json b/tsconfig.json index ef3cbf319c..0938343a8b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,7 @@ "preserveConstEnums": true, "declaration": true, "downlevelIteration": true, + "strictPropertyInitialization": true, "strictNullChecks": true, "allowSyntheticDefaultImports": true, "typeRoots": [ From 03bc24cbf394e2816bb685e686caed70031c076d Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Thu, 27 Apr 2023 15:21:42 +0700 Subject: [PATCH 03/56] refactor: enable useUnknownInCatchVariables --- src/AeSdkWallet.ts | 2 ++ src/chain.ts | 3 ++- src/channel/internal.ts | 2 ++ src/contract/compiler/Cli.ts | 2 ++ src/tx/validator.ts | 5 +++-- src/utils/autorest.ts | 15 +++++---------- src/utils/errors.ts | 4 ++-- src/utils/other.ts | 5 +++++ tsconfig.json | 1 + 9 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/AeSdkWallet.ts b/src/AeSdkWallet.ts index 71f9c24812..75ea3f4c16 100644 --- a/src/AeSdkWallet.ts +++ b/src/AeSdkWallet.ts @@ -20,6 +20,7 @@ import { } from './aepp-wallet-communication/rpc/types'; import { Encoded } from './utils/encoder'; import jsonBig from './utils/json-big'; +import { ensureError } from './utils/other'; type RpcClientWallet = RpcClient; @@ -259,6 +260,7 @@ export default class AeSdkWallet extends AeSdk { } catch (error) { const validation = await verifyTransaction(tx, this.api); if (validation.length > 0) throw new RpcInvalidTransactionError(validation); + ensureError(error); throw new RpcBroadcastError(error.message); } }, diff --git a/src/chain.ts b/src/chain.ts index 17eb194b9f..b526e66050 100644 --- a/src/chain.ts +++ b/src/chain.ts @@ -1,6 +1,6 @@ import { AE_AMOUNT_FORMATS, formatAmount } from './utils/amount-formatter'; import verifyTransaction, { ValidatorResult } from './tx/validator'; -import { isAccountNotFoundError, pause } from './utils/other'; +import { ensureError, isAccountNotFoundError, pause } from './utils/other'; import { isNameValid, produceNameId } from './tx/builder/helpers'; import { DRY_RUN_ACCOUNT } from './tx/builder/schema'; import { AensName } from './tx/builder/constants'; @@ -208,6 +208,7 @@ export async function sendTransaction( } return { hash: txHash, rawTx: tx }; } catch (error) { + ensureError(error); throw Object.assign(error, { rawTx: tx, verifyTx: async () => verifyTransaction(tx, onNode), diff --git a/src/channel/internal.ts b/src/channel/internal.ts index fd2ef38aae..4f95947b4d 100644 --- a/src/channel/internal.ts +++ b/src/channel/internal.ts @@ -15,6 +15,7 @@ import { } from '../utils/errors'; import { encodeContractAddress } from '../utils/crypto'; import { buildTx } from '../tx/builder'; +import { ensureError } from '../utils/other'; export interface ChannelEvents { statusChanged: (status: ChannelStatus) => void; @@ -240,6 +241,7 @@ async function dequeueMessage(channel: Channel): Promise { try { await handleMessage(channel, message); } catch (error) { + ensureError(error); emit(channel, 'error', new ChannelIncomingMessageError(error, message)); } } diff --git a/src/contract/compiler/Cli.ts b/src/contract/compiler/Cli.ts index ce3fc2a595..ccfea2bfc7 100644 --- a/src/contract/compiler/Cli.ts +++ b/src/contract/compiler/Cli.ts @@ -7,6 +7,7 @@ import CompilerBase, { Aci } from './Base'; import { Encoded } from '../../utils/encoder'; import { CompilerError, InternalError, UnsupportedVersionError } from '../../utils/errors'; import semverSatisfies from '../../utils/semver-satisfies'; +import { ensureError } from '../../utils/other'; const getPackagePath = (): string => { const path = dirname(fileURLToPath(import.meta.url)); @@ -81,6 +82,7 @@ export default class CompilerCli extends CompilerBase { aci: aci as Aci, }; } catch (error) { + ensureError(error); throw new CompilerError(error.message); } } diff --git a/src/tx/validator.ts b/src/tx/validator.ts index 9a50bc1b62..87057152b3 100644 --- a/src/tx/validator.ts +++ b/src/tx/validator.ts @@ -1,3 +1,4 @@ +import { RestError } from '@azure/core-rest-pipeline'; import { hash, verify } from '../utils/crypto'; import { TxUnpacked } from './builder/schema.generated'; import { CtVersion, ProtocolToVmAbi } from './builder/field-types/ct-version'; @@ -183,9 +184,9 @@ validators.push( checkedKeys: ['contractId'], }]; } catch (error) { - if (error.response?.parsedBody?.reason == null) throw error; + if (!(error instanceof RestError) || error.response?.bodyAsText == null) throw error; return [{ - message: error.response.parsedBody.reason, + message: JSON.parse(error.response.bodyAsText).reason, // TODO: use parsedBody instead key: 'ContractNotFound', checkedKeys: ['contractId'], }]; diff --git a/src/utils/autorest.ts b/src/utils/autorest.ts index cbe3426808..2d45829029 100644 --- a/src/utils/autorest.ts +++ b/src/utils/autorest.ts @@ -2,7 +2,7 @@ import { RestError, PipelineResponse, PipelinePolicy } from '@azure/core-rest-pi import { AdditionalPolicyConfig } from '@azure/core-client'; import { pause } from './other'; import semverSatisfies from './semver-satisfies'; -import { UnexpectedTsError, UnsupportedVersionError } from './errors'; +import { UnsupportedVersionError } from './errors'; export const genRequestQueuesPolicy = (): AdditionalPolicyConfig => { const requestQueues = new Map>(); @@ -126,22 +126,17 @@ export const genRetryOnFailurePolicy = ( const intervalSum = intervals.reduce((a, b) => a + b); const intervalsInMs = intervals.map((el) => (el / intervalSum) * retryOverallDelay); - let error: Error | undefined; + let error = new RestError('Not expected to be thrown'); for (let attempt = 0; attempt <= retryCount; attempt += 1) { - if (error != null) { - if ( - !(error instanceof RestError) - || statusesToNotRetry.includes(error.response?.status ?? 0) - ) throw error; - await pause(intervalsInMs[attempt - 1]); - } + if (attempt !== 0) await pause(intervalsInMs[attempt - 1]); try { return await next(request); } catch (e) { + if (!(e instanceof RestError)) throw e; + if (statusesToNotRetry.includes(e.response?.status ?? 0)) throw e; error = e; } } - if (error == null) throw new UnexpectedTsError(); throw error; }, }, diff --git a/src/utils/errors.ts b/src/utils/errors.ts index a475ca0acd..febf54db62 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -401,11 +401,11 @@ export class UnexpectedChannelMessageError extends ChannelError { * @category exception */ export class ChannelIncomingMessageError extends ChannelError { - handlerError: BaseError; + handlerError: Error; incomingMessage: { [key: string]: any }; - constructor(handlerError: BaseError, incomingMessage: { [key: string]: any }) { + constructor(handlerError: Error, incomingMessage: { [key: string]: any }) { super(handlerError.message); this.handlerError = handlerError; this.incomingMessage = incomingMessage; diff --git a/src/utils/other.ts b/src/utils/other.ts index c49900fd36..2e137e0249 100644 --- a/src/utils/other.ts +++ b/src/utils/other.ts @@ -57,3 +57,8 @@ export function isAccountNotFoundError(error: Error): boolean { export type UnionToIntersection = (Union extends any ? (k: Union) => void : never) extends ((k: infer Intersection) => void) ? Intersection : never; + +export function ensureError(error: unknown): asserts error is Error { + if (error instanceof Error) return; + throw error; +} diff --git a/tsconfig.json b/tsconfig.json index 0938343a8b..2eb1609621 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,7 @@ "downlevelIteration": true, "strictPropertyInitialization": true, "strictNullChecks": true, + "useUnknownInCatchVariables": true, "allowSyntheticDefaultImports": true, "typeRoots": [ "./node_modules/@types", From d93e163082cc82cbb3af424a04a8c08e349c96e0 Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Thu, 27 Apr 2023 15:46:59 +0700 Subject: [PATCH 04/56] refactor: enable noImplicitThis --- src/AeSdkMethods.ts | 2 +- src/tx/builder/field-types/coin-amount.ts | 3 +- test/integration/rpc.ts | 39 ++++++++++++----------- tsconfig.json | 1 + 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/AeSdkMethods.ts b/src/AeSdkMethods.ts index 5cfd72f0b7..4f065b4745 100644 --- a/src/AeSdkMethods.ts +++ b/src/AeSdkMethods.ts @@ -147,7 +147,7 @@ Object.assign(AeSdkMethods.prototype, mapObject( methods, ([name, handler]) => [ name, - function methodWrapper(...args: any[]) { + function methodWrapper(this: AeSdkMethods, ...args: any[]) { args.length = handler.length; const options = args[args.length - 1]; args[args.length - 1] = { diff --git a/src/tx/builder/field-types/coin-amount.ts b/src/tx/builder/field-types/coin-amount.ts index 902b7504e9..0f53bea7a4 100644 --- a/src/tx/builder/field-types/coin-amount.ts +++ b/src/tx/builder/field-types/coin-amount.ts @@ -5,7 +5,8 @@ import { AE_AMOUNT_FORMATS, formatAmount } from '../../../utils/amount-formatter export default { ...uInt, - serializeAettos(value: string | undefined): string { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + serializeAettos(value: string | undefined, params: {}): string { return value ?? '0'; }, diff --git a/test/integration/rpc.ts b/test/integration/rpc.ts index 0f2a483266..393a1a8bbe 100644 --- a/test/integration/rpc.ts +++ b/test/integration/rpc.ts @@ -41,24 +41,27 @@ import { Accounts, Network } from '../../src/aepp-wallet-communication/rpc/types const WindowPostMessageFake = ( name: string, -): ImplPostMessage & { name: string; messages: any[] } => ({ - name, - messages: [], - addEventListener(onEvent: string, listener: any) { - this.listener = listener; - }, - removeEventListener() { - return () => null; - }, - postMessage(source: any, msg: any) { - this.messages.push(msg); - setTimeout(() => { - if (typeof this.listener === 'function') { - this.listener({ data: msg, origin: 'http://origin.test', source }); - } - }); - }, -}); +): ImplPostMessage & { name: string; messages: any[] } => { + let listener: (event: any) => void; + return { + name, + messages: [], + addEventListener(onEvent: string, _listener: typeof listener) { + listener = _listener; + }, + removeEventListener() { + return () => null; + }, + postMessage(source: any, msg: any) { + this.messages.push(msg); + setTimeout(() => { + if (typeof listener === 'function') { + listener({ data: msg, origin: 'http://origin.test', source }); + } + }); + }, + }; +}; const getConnections = (): { walletWindow: ImplPostMessage; aeppWindow: ImplPostMessage } => { // @ts-expect-error workaround for tests diff --git a/tsconfig.json b/tsconfig.json index 2eb1609621..d645703d9f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,7 @@ "preserveConstEnums": true, "declaration": true, "downlevelIteration": true, + "noImplicitThis": true, "strictPropertyInitialization": true, "strictNullChecks": true, "useUnknownInCatchVariables": true, From 6cbaaf5a857ca79d43f4fba61aab1c769e9b4c30 Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Thu, 27 Apr 2023 15:54:31 +0700 Subject: [PATCH 05/56] chore: use `strict` instead of separate options --- tsconfig.json | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index d645703d9f..6a863e97e0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,6 @@ "emitDeclarationOnly": true, "isolatedModules": true, "outDir": "./es", - "noImplicitAny": true, "noImplicitOverride": true, "module": "es2020", "target": "es2020", @@ -18,15 +17,13 @@ "preserveConstEnums": true, "declaration": true, "downlevelIteration": true, - "noImplicitThis": true, - "strictPropertyInitialization": true, - "strictNullChecks": true, - "useUnknownInCatchVariables": true, "allowSyntheticDefaultImports": true, "typeRoots": [ "./node_modules/@types", "./src/typings" - ] + ], + "strict": true, + "strictFunctionTypes": false, // see https://github.com/aeternity/aepp-sdk-js/issues/1793 }, "include": [ "src/**/*" From 5b1e4c779f518865f66e73260d5be974ea610540 Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Tue, 2 May 2023 12:21:09 +0700 Subject: [PATCH 06/56] ci: update GH actions and conda.yml --- .github/workflows/docs-develop.yml | 15 +++++++-------- .github/workflows/docs-release.yml | 14 ++++++-------- .github/workflows/main.yml | 4 ++-- docs/conda.yml | 4 ++-- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/.github/workflows/docs-develop.yml b/.github/workflows/docs-develop.yml index a166be4af6..6ac3ef456a 100644 --- a/.github/workflows/docs-develop.yml +++ b/.github/workflows/docs-develop.yml @@ -1,23 +1,22 @@ +# TODO: combine with docs-release using if name: Publish develop docs on: push: branches: ['develop'] - + jobs: main: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: - python-version: 3.8 - - uses: actions/cache@v2 + python-version: 3.11 + - uses: actions/cache@v3 with: path: ~/.cache/pip3 key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements.txt') }} - run: pip3 install -r docs/requirements.txt - - run: git config --global user.email "github-action@users.noreply.github.com" - - run: git config --global user.name "GitHub Action" - - run: mike deploy --push develop \ No newline at end of file + - run: mike deploy --push develop diff --git a/.github/workflows/docs-release.yml b/.github/workflows/docs-release.yml index 2ebf1fb129..5b32e320ba 100644 --- a/.github/workflows/docs-release.yml +++ b/.github/workflows/docs-release.yml @@ -2,23 +2,21 @@ name: Publish release docs on: release: types: [released] - + jobs: main: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: - python-version: 3.8 - - uses: actions/cache@v2 + python-version: 3.11 + - uses: actions/cache@v3 with: path: ~/.cache/pip3 key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements.txt') }} - run: pip3 install -r docs/requirements.txt - - run: git config --global user.email "github-action@users.noreply.github.com" - - run: git config --global user.name "GitHub Action" - run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV - - run: mike deploy --push --update-aliases $RELEASE_VERSION latest \ No newline at end of file + - run: mike deploy --push --update-aliases $RELEASE_VERSION latest diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7e7101ecff..7cd2ea9842 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,10 +7,10 @@ jobs: - run: | wget -q https://packages.erlang-solutions.com/erlang/debian/pool/esl-erlang_25.0.4-1~ubuntu~jammy_amd64.deb sudo apt install --allow-downgrades ./esl-erlang_25.0.4-1~ubuntu~jammy_amd64.deb - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 100 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 with: node-version: 18.x - uses: actions/cache@v3 diff --git a/docs/conda.yml b/docs/conda.yml index e1322646d8..fac1f4392a 100644 --- a/docs/conda.yml +++ b/docs/conda.yml @@ -1,6 +1,6 @@ dependencies: - - python=3.8.* - - nodejs=10.* + - python=3.11.* + - nodejs=18.* - pip - pip: - -r file:requirements.txt From e2e286d2efcf470fcbd1919e455aae08c000ec71 Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Fri, 5 May 2023 16:23:22 +0600 Subject: [PATCH 07/56] docs: links to the latest version of sdk --- docs/guides/paying-for-tx.md | 4 ++-- examples/node/transfer-ae.mjs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guides/paying-for-tx.md b/docs/guides/paying-for-tx.md index 6fd321794f..f2e9a5b2f8 100644 --- a/docs/guides/paying-for-tx.md +++ b/docs/guides/paying-for-tx.md @@ -13,8 +13,8 @@ You can then collect the signed inner transaction, wrap it into a `PayingForTx` ## Usage examples We provided following two NodeJS examples which you can take a look at: -- [InnerTx: ContractCallTx](https://docs.aeternity.com/aepp-sdk-js/v12.1.3/examples/node/paying-for-tx-contract-call-tx/) -- [InnerTx: SpendTx](https://docs.aeternity.com/aepp-sdk-js/v12.1.3/examples/node/paying-for-tx-spend-tx/) +- [InnerTx: ContractCallTx](https://docs.aeternity.com/aepp-sdk-js/v13.0.1/examples/node/paying-for-contract-call-tx/) +- [InnerTx: SpendTx](https://docs.aeternity.com/aepp-sdk-js/v13.0.1/examples/node/paying-for-spend-tx/) Note: diff --git a/examples/node/transfer-ae.mjs b/examples/node/transfer-ae.mjs index 59f097b450..317299f7de 100755 --- a/examples/node/transfer-ae.mjs +++ b/examples/node/transfer-ae.mjs @@ -53,7 +53,7 @@ console.log(`Balance of ${recipient} (before): ${balanceBefore} aettos`); // Calling the `spend` function will create, sign and broadcast a `SpendTx` to the network. const tx = await aeSdk.spend(amount, recipient); console.log('Transaction mined', tx); -// Alternatively, you can use [transferFunds](https://docs.aeternity.com/aepp-sdk-js/latest/api/functions/transferFunds.html) +// Alternatively, you can use [transferFunds](https://docs.aeternity.com/aepp-sdk-js/v13.0.1/api/functions/transferFunds.html) // method to transfer a fraction of your AE to another account. // ## 6. Get AE balance of recipient (after transfer) From 1f8c70a63823098343141865ff806191d8023325 Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Fri, 5 May 2023 16:25:35 +0600 Subject: [PATCH 08/56] docs(chain): document possible address lengths --- docs/guides/address-length.md | 85 +++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 86 insertions(+) create mode 100644 docs/guides/address-length.md diff --git a/docs/guides/address-length.md b/docs/guides/address-length.md new file mode 100644 index 0000000000..7b016205d8 --- /dev/null +++ b/docs/guides/address-length.md @@ -0,0 +1,85 @@ +# The range of possible address length + +While base64-encoded strings have a constant length depending on the length of data to encode. In +base58 it depends on the exact data to encode, e.g. it is shorter if encoded data have more +leading zeroes. + +Building an aepp you may need to know the range of possible address lengths to validate an +user-provided addresses (though better to use [isAddressValid]) or for designs of address-related +components. Doing manual tests you may conclude that account address length is between 52 and 53 +chars, but it is not correct. + +```js +import { MemoryAccount } from '@aeternity/aepp-sdk'; + +const result = new Array(10000).fill() + .map(() => MemoryAccount.generate().address.length) + .reduce((p, n) => ({ ...p, [n]: (p[n] ?? 0) + 1 }), {}); + +console.log(result); +``` + +Running the above code you would get something like `{ '51': 55, '52': 5021, '53': 4924 }` +depending on generated accounts. So, while the most of addresses have length between 52 and 53 +chars, there is a ~0.55% chance to get an address of 51 chars. + +Theoretically there can be even shorter addresses if they lucky to be prefixed with a long +sequence of `0`. + +```js +import { + MemoryAccount, Encoding, encode, decode, +} from '@aeternity/aepp-sdk'; + +const publicKey = decode(MemoryAccount.generate().address); + +for (let i = -1; i < publicKey.length; i += 1) { + if (i >= 0) publicKey[i] = 0; + const address = encode(publicKey, Encoding.AccountAddress); + console.log(address.length, address); +} +``` + +Running the above code you would get output like + +``` +52 ak_XsSLpN161dHo77k82CZHDnUCDpVG1JSujZjbGYhNKTgMy5exZ +52 ak_13P6GKgb4VcxJHrb5Vnhb66RNBGgdnFLVJS8RaLcrAeseZmuc +52 ak_115z6Ns8nevqahWHQfYU3QNJbK7PsX2rWxoPQRcpvWzB4U77s +51 ak_111dqrd5iRqVHQe2T2JdZe79bqNBVCkWPqSc1JAXMW6F2vvT +50 ak_1111PM8Acd6qZCjioCqPt6PcTuWVxxS22gt2ytCdH82FY4C +51 ak_111113q5w8zgNNjAgLbxrMmA8qBNCv8aVHBM7eLm7JbfcgVe +50 ak_111111UGVF8HYKFC7hLzwffE8JR5vQKQ9z4BiYJYCVcEGna +49 ak_1111111wnTfJ9TWagQmzk42ADoUqVg1VvNMG7t8Fo8SQGf +50 ak_11111111389cwfh57Mw1d4uYXps2orpWxc9Zoov7PdW6G7S +49 ak_111111111G6iURaQ8ycLUNERoocnfJPQ8J7bopyndikHFM +49 ak_11111111116b9WMyCB85kS4YkDfP9D3LhqUu5eG7AEfrLg +48 ak_11111111111urvTGsQm76A4yTbApC8DJ1rLggAFKXiZXp +48 ak_1111111111119eSHeN26TTHqgKpcEVTmbCHEbQgAHJkjd +48 ak_11111111111115QmcWAusFcLUq2MfYLXdxnFEhPkRrQu5 +47 ak_11111111111111c7Y1hucPEYuPH6ZY5sFdKo6dhb86gk +47 ak_1111111111111116qaT3Ac44qzDFevr9Czj1S9p9Y46r +47 ak_11111111111111114KqRXnNJJwVDnphudyDt4XDmjLn7 +46 ak_11111111111111111XGs41KwLTa74AMCwx3gYYAHv81 +46 ak_1111111111111111114sffTSqJvTbsAjsQykvVsDcV8 +45 ak_1111111111111111111wVeBwZE5g63oPLdnrQC7TuP +45 ak_11111111111111111111Y4FSQ8pV3J8f96h3SS68Df +45 ak_1111111111111111111114ZmkEyJVpPoE6bFrxSt3n +44 ak_1111111111111111111111hEHXkLzkWadzEyFn6pw +44 ak_11111111111111111111111A6y5vKi2SUtEiqyw1H +44 ak_1111111111111111111111112PszJecmUrBA1wHbx +43 ak_1111111111111111111111111yqJmD1ujq7rtGX6 +43 ak_111111111111111111111111113hoVWLS2aptDtG +42 ak_111111111111111111111111111PhgEh1GA292t +42 ak_1111111111111111111111111111Z6ci7mUpU7i +42 ak_111111111111111111111111111119gETXQQAsT +42 ak_11111111111111111111111111111136u6WXDf6 +41 ak_1111111111111111111111111111111PqPZYur +41 ak_11111111111111111111111111111111273Yts +``` + +Therefore the minimum address length is 41 chars. All these addresses valid, for example +`ak_11111111111111111111111111111111273Yts` [used] to collect AENS name fees. + +[isAddressValid]: https://docs.aeternity.com/aepp-sdk-js/v13.0.1/api/functions/isAddressValid.html +[used]: https://mainnet.aeternity.io/v3/accounts/ak_11111111111111111111111111111111273Yts diff --git a/mkdocs.yml b/mkdocs.yml index dc0efd8dcc..d3c650bb04 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -49,6 +49,7 @@ nav: - index.md - quick-start.md - Usage Guides: + - guides/address-length.md - guides/aens.md - guides/contracts.md - guides/contract-events.md From 9462adde8fab9848fca0a6d9a382dbd34f5e2b8f Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Fri, 5 May 2023 16:31:35 +0600 Subject: [PATCH 09/56] fix: reject prefixes other then provided in isAddressValid --- src/utils/crypto.ts | 5 +++++ test/unit/crypto.ts | 1 + 2 files changed, 6 insertions(+) diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts index 7813956ff1..680dc1c213 100644 --- a/src/utils/crypto.ts +++ b/src/utils/crypto.ts @@ -8,6 +8,7 @@ import { concatBuffers } from './other'; import { decode, encode, Encoded, Encoding, } from './encoder'; +import { ArgumentError } from './errors'; /** * Generate address from secret key @@ -32,6 +33,10 @@ export function isAddressValid( ): boolean { try { decode(address as Encoded.Generic); + const actualPrefix = address.split('_')[0]; + if (actualPrefix !== prefix) { + throw new ArgumentError('Encoded string type', prefix, actualPrefix); + } return true; } catch (e) { return false; diff --git a/test/unit/crypto.ts b/test/unit/crypto.ts index 7eb9d30d8f..6690ed5a74 100644 --- a/test/unit/crypto.ts +++ b/test/unit/crypto.ts @@ -56,6 +56,7 @@ describe('crypto', () => { it('isAddressValid', () => { expect(isAddressValid('test')).to.be.equal(false); + expect(isAddressValid('th_11111111111111111111111111111111273Yts')).to.be.equal(false); expect(isAddressValid('ak_11111111111111111111111111111111273Yts')).to.be.equal(true); }); From f4a0ebc4a902f48dddb0842bed949ef6e2a31336 Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Sat, 29 Apr 2023 19:23:51 +0700 Subject: [PATCH 10/56] test: use ensureEqual instead of throwing exception --- test/integration/account-generalized.ts | 2 +- test/unit/tx.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/account-generalized.ts b/test/integration/account-generalized.ts index b3d0948df6..c8ea8935d1 100644 --- a/test/integration/account-generalized.ts +++ b/test/integration/account-generalized.ts @@ -86,7 +86,7 @@ describe('Generalized Account', () => { .eql((await authContract.getTxHash()).decodedResult); const gaMetaTxParams = unpackTx(rawTx, Tag.SignedTx).encodedTx; - if (gaMetaTxParams.tag !== Tag.GaMetaTx) throw new Error('Unexpected nested transaction'); + ensureEqual(gaMetaTxParams.tag, Tag.GaMetaTx); const spendTx = buildTx(gaMetaTxParams.tx.encodedTx); const { fee, gasPrice } = gaMetaTxParams; expect(new Uint8Array(await aeSdk.buildAuthTxHash(spendTx, { fee, gasPrice }))).to.be diff --git a/test/unit/tx.ts b/test/unit/tx.ts index 0ba9b343a0..a669bb71a9 100644 --- a/test/unit/tx.ts +++ b/test/unit/tx.ts @@ -3,7 +3,7 @@ import { describe, it } from 'mocha'; import { expect } from 'chai'; import { encode as rlpEncode } from 'rlp'; import BigNumber from 'bignumber.js'; -import { checkOnlyTypes, randomName } from '../utils'; +import { checkOnlyTypes, ensureEqual, randomName } from '../utils'; import { genSalt, decode, encode, Encoding, Encoded, @@ -215,7 +215,7 @@ describe('Tx', () => { Tag.SignedTx, ); expect(signatures).to.have.lengthOf(2); - if (encodedTx.tag !== Tag.ChannelOffChainTx) throw new Error('Unexpected nested transaction'); + ensureEqual(encodedTx.tag, Tag.ChannelOffChainTx); expect(encodedTx.channelId).to.be.satisfy((s: string) => s.startsWith('ch_')); expect(encodedTx.round).to.be.equal(3); expect(encodedTx.stateHash).to.be.satisfy((s: string) => s.startsWith('st_')); From 75af4454eca746afc4924863b35aa34308eea88b Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Fri, 5 May 2023 19:53:27 +0600 Subject: [PATCH 11/56] chore: update node to 6.8.1 --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index a310146a26..dfbfa815dd 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -NODE_TAG=v6.8.0 +NODE_TAG=v6.8.1 COMPILER_TAG=v7.2.0 From 3d8b336e67d0f8e53846accf0e2cc9edf7a861a4 Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Sat, 6 May 2023 17:09:28 +0600 Subject: [PATCH 12/56] docs(aepp): extract SpendCoins component --- examples/browser/aepp/src/Basic.vue | 48 ++-------------- examples/browser/aepp/src/Contracts.vue | 2 +- .../aepp/src/components/SpendCoins.vue | 56 +++++++++++++++++++ .../aepp/src/{ => components}/Value.vue | 0 4 files changed, 61 insertions(+), 45 deletions(-) create mode 100644 examples/browser/aepp/src/components/SpendCoins.vue rename examples/browser/aepp/src/{ => components}/Value.vue (100%) diff --git a/examples/browser/aepp/src/Basic.vue b/examples/browser/aepp/src/Basic.vue index 50ab44889b..bfbc4bf24b 100644 --- a/examples/browser/aepp/src/Basic.vue +++ b/examples/browser/aepp/src/Basic.vue @@ -23,54 +23,21 @@ -

Spend coins

-
-
-
Recipient address
-
- -
-
-
-
Coins amount
-
-
-
-
Payload
-
-
- -
-
Spend result
- -
-
+ diff --git a/examples/browser/aepp/src/Contracts.vue b/examples/browser/aepp/src/Contracts.vue index 892619bb17..c722ee164b 100644 --- a/examples/browser/aepp/src/Contracts.vue +++ b/examples/browser/aepp/src/Contracts.vue @@ -80,7 +80,7 @@ diff --git a/examples/browser/aepp/src/Value.vue b/examples/browser/aepp/src/components/Value.vue similarity index 100% rename from examples/browser/aepp/src/Value.vue rename to examples/browser/aepp/src/components/Value.vue From 359f6c669f0094877ac4b70915728f8ebde3c019 Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Sat, 6 May 2023 17:13:11 +0600 Subject: [PATCH 13/56] docs(aepp): assume sdk is always provided --- examples/browser/aepp/src/App.vue | 7 ++----- examples/browser/aepp/src/Basic.vue | 4 ++-- examples/browser/aepp/src/Connect.vue | 2 -- examples/browser/aepp/src/components/SpendCoins.vue | 5 +---- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/examples/browser/aepp/src/App.vue b/examples/browser/aepp/src/App.vue index c194db2774..e47c10e676 100644 --- a/examples/browser/aepp/src/App.vue +++ b/examples/browser/aepp/src/App.vue @@ -3,10 +3,7 @@ -