├── docs
├── .gitignore
├── images
│ └── favicon.png
├── requirements.txt
├── hooks.py
├── build-assets.sh
├── tutorials
│ └── vuejs
│ │ └── helloworld-blockheight.md
├── guides
│ ├── paying-for-tx.md
│ ├── build-wallet.md
│ ├── migration
│ │ ├── 7.md
│ │ └── 9.md
│ ├── connect-aepp-to-wallet.md
│ ├── typed-data.md
│ ├── low-vs-high-usage.md
│ └── jwt.md
├── quick-start.md
└── compatibility.md
├── .prettierrc
├── examples
├── browser
│ ├── tools
│ │ ├── src
│ │ │ ├── vite-env.d.ts
│ │ │ ├── main.tsx
│ │ │ ├── app.tsx
│ │ │ ├── index.css
│ │ │ └── components
│ │ │ │ ├── DataEncoder.tsx
│ │ │ │ ├── ConvertSk.tsx
│ │ │ │ └── AccountsByMnemonic.tsx
│ │ ├── tsconfig.node.json
│ │ ├── vite.config.ts
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ └── public
│ │ │ └── preact.svg
│ ├── aepp
│ │ ├── babel.config.js
│ │ ├── src
│ │ │ ├── main.js
│ │ │ ├── Connect.vue
│ │ │ ├── components
│ │ │ │ ├── MessageSign.vue
│ │ │ │ ├── Value.vue
│ │ │ │ ├── FieldAction.vue
│ │ │ │ ├── SpendCoins.vue
│ │ │ │ ├── SelectNetwork.vue
│ │ │ │ ├── GenerateSpendTx.vue
│ │ │ │ └── DataSign.vue
│ │ │ ├── store.js
│ │ │ ├── PayForTx.vue
│ │ │ ├── styles.scss
│ │ │ ├── App.vue
│ │ │ ├── Basic.vue
│ │ │ └── Jwt.vue
│ │ ├── vue.config.js
│ │ ├── public
│ │ │ ├── webmanifest.json
│ │ │ ├── index.html
│ │ │ └── icon.svg
│ │ ├── README.md
│ │ └── package.json
│ ├── wallet-iframe
│ │ ├── babel.config.js
│ │ ├── src
│ │ │ ├── main.js
│ │ │ ├── Value.vue
│ │ │ └── styles.scss
│ │ ├── vue.config.js
│ │ ├── README.md
│ │ ├── public
│ │ │ └── index.html
│ │ └── package.json
│ ├── wallet-web-extension
│ │ ├── babel.config.js
│ │ ├── public
│ │ │ ├── icons
│ │ │ │ ├── 128.png
│ │ │ │ └── 34.png
│ │ │ └── browser-extension.html
│ │ ├── src
│ │ │ ├── popup.js
│ │ │ ├── manifest.json
│ │ │ ├── styles.scss
│ │ │ ├── content-script.js
│ │ │ └── Popup.vue
│ │ ├── README.md
│ │ ├── package.json
│ │ └── vue.config.js
│ └── README.md
├── node
│ ├── _api-high-level.js
│ ├── _api-low-level.js
│ └── oracle.js
└── README.md
├── test
├── environment
│ ├── typescript
│ │ ├── main.ts
│ │ ├── tsconfig.json
│ │ ├── package.json
│ │ └── run.sh
│ ├── vue-cli-4-autorest
│ │ ├── babel.config.js
│ │ ├── vue.config.js
│ │ ├── src
│ │ │ └── main.js
│ │ └── package.json
│ ├── ledger
│ │ ├── node.js
│ │ ├── browser.js
│ │ ├── webpack.config.js
│ │ └── main.js
│ ├── node-unhandled-exception.js
│ ├── node.js
│ ├── node.ts
│ ├── node.cjs
│ ├── browser.html
│ └── name-claim-queue.js
├── integration
│ ├── contracts
│ │ ├── lib
│ │ │ ├── DecrementInterface.aes
│ │ │ ├── Sublibrary.aes
│ │ │ └── Library.aes
│ │ ├── Interface.aes
│ │ ├── Includes.aes
│ │ └── Includes.json
│ ├── AeSdk.ts
│ └── channel-utils.ts
├── index.ts
├── emitter
│ ├── Dockerfile
│ └── main.js
├── unit
│ ├── ae-sdk.ts
│ ├── string.ts
│ ├── semver-satisfies.ts
│ ├── bytes.ts
│ ├── entry-packing.ts
│ ├── utils.ts
│ └── pretty-numbers.ts
└── examples.sh
├── tsconfig.eslint.json
├── src
├── typings
│ ├── sha-js
│ │ └── index.d.ts
│ ├── blakejs
│ │ └── index.d.ts
│ └── tweetnacl-auth
│ │ └── index.d.ts
├── tx
│ ├── builder
│ │ ├── field-types
│ │ │ ├── raw.ts
│ │ │ ├── field.ts
│ │ │ ├── boolean.ts
│ │ │ ├── short-u-int.ts
│ │ │ ├── string.ts
│ │ │ ├── interface.ts
│ │ │ ├── name.ts
│ │ │ ├── u-int.ts
│ │ │ ├── name-id.ts
│ │ │ ├── with-default.ts
│ │ │ ├── with-formatting.ts
│ │ │ ├── array.ts
│ │ │ ├── coin-amount.ts
│ │ │ ├── encoded.ts
│ │ │ ├── query-fee.ts
│ │ │ ├── short-u-int-const.ts
│ │ │ ├── name-fee.ts
│ │ │ ├── ttl.ts
│ │ │ ├── enumeration.ts
│ │ │ ├── transaction.ts
│ │ │ ├── entry.ts
│ │ │ ├── wrapped.ts
│ │ │ ├── abi-version.ts
│ │ │ ├── gas-limit.ts
│ │ │ ├── map.ts
│ │ │ ├── nonce.ts
│ │ │ ├── address.ts
│ │ │ └── ct-version.ts
│ │ ├── entry
│ │ │ ├── constants.ts
│ │ │ └── index.ts
│ │ └── delegation
│ │ │ └── index.ts
│ └── transaction-signer.ts
├── index.ts
├── utils
│ ├── string.ts
│ ├── bignumber.ts
│ ├── semver-satisfies.ts
│ ├── json-big.ts
│ ├── bytes.ts
│ ├── wrap-proxy.ts
│ ├── MiddlewarePage.ts
│ └── other.ts
├── aepp-wallet-communication
│ ├── connection-proxy.ts
│ ├── WalletConnectorFrame.ts
│ ├── connection
│ │ ├── Browser.ts
│ │ └── BrowserRuntime.ts
│ ├── WalletConnectorFrameWithNode.ts
│ └── wallet-detector.ts
├── account
│ └── BaseFactory.ts
├── contract
│ └── compiler
│ │ ├── HttpNode.ts
│ │ └── getFileSystem.ts
└── oracle
│ └── OracleBase.ts
├── .editorconfig
├── tooling
├── docs
│ ├── overrides
│ │ └── main.html
│ └── examples-to-md.js
├── fetch-aesophia-cli.js
├── autorest
│ ├── compiler-prepare.js
│ ├── middleware-prepare.js
│ └── compiler.yaml
├── restore-file.js
├── eslint-rules
│ └── tsdoc-syntax.cjs
└── fetch-metamask.js
├── .mocharc.cjs
├── tsconfig.json
├── babel.esm.config.js
├── .gitignore
├── tsdoc.json
├── commitlint.config.js
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── workflows
│ ├── codeql.yml
│ ├── docs.yml
│ └── main.yml
├── docker
├── aeternity.yaml
└── middleware.yaml
├── babel.config.js
├── LICENSE
├── tsconfig.src.json
├── docker-compose.yml
└── webpack.config.js
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "singleQuote": true
4 | }
5 |
--------------------------------------------------------------------------------
/examples/browser/tools/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/docs/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aeternity/aepp-sdk-js/HEAD/docs/images/favicon.png
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | mkdocs==1.6.1
2 | mkdocs-simple-hooks==0.1.5
3 | mkdocs-material==9.6.12
4 | mike==2.1.3
5 |
--------------------------------------------------------------------------------
/examples/browser/aepp/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/cli-plugin-babel/preset'],
3 | };
4 |
--------------------------------------------------------------------------------
/test/environment/typescript/main.ts:
--------------------------------------------------------------------------------
1 | import * as Sdk from '@aeternity/aepp-sdk';
2 |
3 | console.log('Sdk', Sdk);
4 |
--------------------------------------------------------------------------------
/test/integration/contracts/lib/DecrementInterface.aes:
--------------------------------------------------------------------------------
1 | contract Decrement =
2 | entrypoint decrement: (int) => int
3 |
--------------------------------------------------------------------------------
/test/integration/contracts/lib/Sublibrary.aes:
--------------------------------------------------------------------------------
1 | namespace Sublibrary =
2 | function sum(x: int, y: int): int = x + y
3 |
--------------------------------------------------------------------------------
/examples/browser/wallet-iframe/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/cli-plugin-babel/preset'],
3 | };
4 |
--------------------------------------------------------------------------------
/test/index.ts:
--------------------------------------------------------------------------------
1 | import { use } from 'chai';
2 | import chaiAsPromised from 'chai-as-promised';
3 |
4 | use(chaiAsPromised);
5 |
--------------------------------------------------------------------------------
/examples/browser/wallet-web-extension/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/cli-plugin-babel/preset'],
3 | };
4 |
--------------------------------------------------------------------------------
/test/emitter/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20-alpine
2 | WORKDIR /app
3 |
4 | COPY ./main.js main.mjs
5 |
6 | CMD [ "node", "main.mjs" ]
7 |
--------------------------------------------------------------------------------
/test/environment/vue-cli-4-autorest/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/cli-plugin-babel/preset'],
3 | };
4 |
--------------------------------------------------------------------------------
/tsconfig.eslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.src.json",
3 | "include": ["src/**/*", "test/**/*", "tooling/**/*"]
4 | }
5 |
--------------------------------------------------------------------------------
/src/typings/sha-js/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'sha.js/sha256.js' {
2 | import { sha256 } from 'sha.js';
3 |
4 | export default sha256;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/browser/wallet-iframe/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import App from './App.vue';
3 |
4 | createApp(App).mount('#app');
5 |
--------------------------------------------------------------------------------
/test/integration/contracts/lib/Library.aes:
--------------------------------------------------------------------------------
1 | include"lib/Sublibrary.aes"
2 |
3 | namespace Library =
4 | function sum(x: int, y: int): int = Sublibrary.sum(x, y)
5 |
--------------------------------------------------------------------------------
/examples/browser/wallet-web-extension/public/icons/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aeternity/aepp-sdk-js/HEAD/examples/browser/wallet-web-extension/public/icons/128.png
--------------------------------------------------------------------------------
/examples/browser/wallet-web-extension/public/icons/34.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aeternity/aepp-sdk-js/HEAD/examples/browser/wallet-web-extension/public/icons/34.png
--------------------------------------------------------------------------------
/examples/browser/wallet-web-extension/src/popup.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './Popup.vue';
3 |
4 | new Vue({
5 | render: (h) => h(App),
6 | }).$mount('#app');
7 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import App from './App.vue';
3 | import store from './store';
4 |
5 | createApp(App).use(store).mount('#app');
6 |
--------------------------------------------------------------------------------
/test/integration/contracts/Interface.aes:
--------------------------------------------------------------------------------
1 | include "String.aes"
2 | include "./lib/DecrementInterface.aes"
3 |
4 | main contract Increment =
5 | entrypoint increment: (int) => int
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
--------------------------------------------------------------------------------
/examples/browser/tools/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { render } from 'preact';
2 | import { App } from './app.tsx';
3 | import './index.css';
4 |
5 | render( , document.getElementById('app')!);
6 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/raw.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | serialize(buffer: Uint8Array): Buffer {
3 | return Buffer.from(buffer);
4 | },
5 |
6 | deserialize(buffer: Buffer): Buffer {
7 | return buffer;
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/tooling/docs/overrides/main.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %} {% block outdated %} You're not viewing the latest version.
2 |
3 | Click here to go to latest.
4 |
5 | {% endblock %}
6 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/field.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | serialize(value: string): Buffer {
3 | return Buffer.from(value);
4 | },
5 |
6 | deserialize(value: Buffer): string {
7 | return value.toString();
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/.mocharc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'node-option': ['import=tsx'],
3 | recursive: true,
4 | extension: '.ts',
5 | timeout: process.env.NETWORK ? '30s' : '6s',
6 | ignore: ['test/charts/**', 'test/emitter/**', 'test/environment/**'],
7 | };
8 |
--------------------------------------------------------------------------------
/examples/browser/wallet-iframe/vue.config.js:
--------------------------------------------------------------------------------
1 | const { defineConfig } = require('@vue/cli-service');
2 |
3 | module.exports = defineConfig({
4 | publicPath: process.env.PUBLIC_PATH ?? '/',
5 | devServer: {
6 | port: 9000,
7 | },
8 | });
9 |
--------------------------------------------------------------------------------
/src/typings/blakejs/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'blakejs/blake2b.js' {
2 | export {
3 | Blake2bCTX,
4 | blake2bInit,
5 | blake2bUpdate,
6 | blake2bFinal,
7 | blake2b,
8 | blake2bHex,
9 | } from 'blakejs';
10 | }
11 |
--------------------------------------------------------------------------------
/test/integration/contracts/Includes.aes:
--------------------------------------------------------------------------------
1 | include "String.aes"
2 | include "./lib/Library.aes"
3 |
4 | contract Includes =
5 | entrypoint test(x: int): int = Library.sum(x, 4)
6 |
7 | entrypoint getLength(x: string): int = String.length(x)
8 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/boolean.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | serialize(value: boolean): Buffer {
3 | return Buffer.from([value ? 1 : 0]);
4 | },
5 |
6 | deserialize(buffer: Buffer): boolean {
7 | return buffer[0] === 1;
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/test/environment/typescript/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "target": "es2016",
5 | "moduleResolution": "node",
6 | "noEmit": true,
7 | "typeRoots": ["./node_modules/@types", "../../../src/typings"]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './index-browser.js';
2 |
3 | export { default as CompilerCli } from './contract/compiler/Cli.js';
4 | export { default as getFileSystem } from './contract/compiler/getFileSystem.js';
5 | export { default as CompilerHttpNode } from './contract/compiler/HttpNode.js';
6 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/short-u-int.ts:
--------------------------------------------------------------------------------
1 | import uInt from './u-int.js';
2 |
3 | export default {
4 | serialize(value: number): Buffer {
5 | return uInt.serialize(value);
6 | },
7 |
8 | deserialize(value: Buffer): number {
9 | return +uInt.deserialize(value);
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.src.json",
3 | "compilerOptions": {
4 | "emitDeclarationOnly": false,
5 | "resolveJsonModule": true,
6 | "noEmit": true
7 | },
8 | "include": ["src/**/*", "test/**/*"],
9 | "exclude": ["test/environment/typescript/**/*"]
10 | }
11 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/string.ts:
--------------------------------------------------------------------------------
1 | import { toBytes } from '../../../utils/bytes.js';
2 |
3 | export default {
4 | serialize(string: string): Buffer {
5 | return toBytes(string);
6 | },
7 |
8 | deserialize(buffer: Buffer): string {
9 | return buffer.toString();
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/test/environment/ledger/node.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import TransportNodeHid from '@ledgerhq/hw-transport-node-hid-singleton';
3 | // eslint-disable-next-line import/extensions
4 | import run from './main.js';
5 |
6 | const transport = await TransportNodeHid.default.create();
7 | await run(transport);
8 |
--------------------------------------------------------------------------------
/examples/browser/tools/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true,
8 | "strict": true
9 | },
10 | "include": ["vite.config.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/examples/browser/aepp/vue.config.js:
--------------------------------------------------------------------------------
1 | const { defineConfig } = require('@vue/cli-service');
2 | const webpack = require('webpack');
3 |
4 | module.exports = defineConfig({
5 | publicPath: process.env.PUBLIC_PATH ?? '/',
6 | devServer: {
7 | port: 9001,
8 | },
9 | configureWebpack: {
10 | plugins: [new webpack.ProvidePlugin({ Buffer: ['buffer', 'Buffer'] })],
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/examples/browser/tools/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import preact from '@preact/preset-vite';
3 | import { visualizer } from 'rollup-plugin-visualizer';
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [
8 | preact(),
9 | visualizer({
10 | filename: 'dist/stats.html',
11 | }),
12 | ],
13 | base: './',
14 | });
15 |
--------------------------------------------------------------------------------
/test/unit/ae-sdk.ts:
--------------------------------------------------------------------------------
1 | import { describe, it } from 'mocha';
2 | import { expect } from 'chai';
3 | import '../index';
4 | import { AeSdk } from '../../src';
5 |
6 | describe('AeSdk', () => {
7 | it('executes methods without node, compiler, accounts', async () => {
8 | const aeSdk = new AeSdk({ _expectedMineRate: 1000 });
9 | expect(await aeSdk._getPollInterval('key-block')).to.equal(333);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/test/environment/vue-cli-4-autorest/vue.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | // this workaround is only needed when sdk is not in the node_modules folder
5 | chainWebpack: (config) => {
6 | const sdkPath = path.join(__dirname, '..', '..', '..', 'es');
7 | config.module.rule('mjs').include.add(sdkPath);
8 | },
9 | transpileDependencies: ['@aeternity/aepp-calldata'],
10 | };
11 |
--------------------------------------------------------------------------------
/examples/browser/wallet-web-extension/public/browser-extension.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= htmlWebpackPlugin.options.title %>
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/test/environment/typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "typescript-test",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "main.ts",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "@aeternity/aepp-sdk": "file:../../.."
13 | },
14 | "devDependencies": {
15 | "typescript": "4.1"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/interface.ts:
--------------------------------------------------------------------------------
1 | export type BinaryData =
2 | | Buffer
3 | | Buffer[]
4 | | Buffer[][]
5 | | Array<[Buffer, Array<[Buffer, Buffer[]]>]>;
6 |
7 | export interface Field {
8 | serialize: (value: any, options: any, parameters: any) => BinaryData;
9 | prepare?: (value: any, options: any, parameters: any) => Promise;
10 | deserialize: (value: BinaryData, options: any) => any;
11 | recursiveType?: boolean;
12 | }
13 |
--------------------------------------------------------------------------------
/test/environment/ledger/browser.js:
--------------------------------------------------------------------------------
1 | import TransportWebUSB from '@ledgerhq/hw-transport-webusb';
2 | // eslint-disable-next-line import/extensions
3 | import run from './main.js';
4 |
5 | document.body.innerHTML = `
6 | Open developer console
7 | Run
8 | `;
9 | document.getElementById('run').addEventListener('click', async () => {
10 | const transport = await TransportWebUSB.create();
11 | await run(transport);
12 | });
13 |
--------------------------------------------------------------------------------
/examples/browser/tools/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | SDK tools
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/environment/typescript/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # TODO: revisit --ignore-scripts after solving https://github.com/npm/cli/issues/4202
5 | perl -i -pe 's/"prepare"/"rem-prepare"/g' ../../../package.json
6 |
7 | for i in 4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8
8 | do
9 | echo "Try typescript@$i"
10 | npm i "typescript@$i" -D --no-audit
11 | npx tsc
12 | done
13 |
14 | perl -i -pe 's/"rem-prepare"/"prepare"/g' ../../../package.json
15 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/name.ts:
--------------------------------------------------------------------------------
1 | import field from './field.js';
2 | import { AensName } from '../constants.js';
3 |
4 | export default {
5 | /**
6 | * @param value - AENS name
7 | */
8 | serialize(value: AensName): Buffer {
9 | return field.serialize(value);
10 | },
11 |
12 | /**
13 | * @param value - AENS name
14 | */
15 | deserialize(value: Buffer): AensName {
16 | return field.deserialize(value) as AensName;
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/babel.esm.config.js:
--------------------------------------------------------------------------------
1 | import config from './babel.config.js';
2 |
3 | config.presets
4 | .filter((plugin) => Array.isArray(plugin))
5 | .find(([name]) => name === '@babel/preset-env')[1].modules = false;
6 |
7 | config.plugins.push([
8 | 'import-globals',
9 | {
10 | Buffer: { moduleName: 'buffer', exportName: 'Buffer' },
11 | },
12 | ]);
13 | config.plugins = config.plugins.filter((p) => p !== 'babel-plugin-transform-import-meta');
14 |
15 | export default config;
16 |
--------------------------------------------------------------------------------
/examples/browser/tools/src/app.tsx:
--------------------------------------------------------------------------------
1 | import { ConvertSk } from './components/ConvertSk';
2 | import { AccountsByMnemonic } from './components/AccountsByMnemonic';
3 | import { TransactionPacker } from './components/TransactionPacker';
4 | import { DataEncoder } from './components/DataEncoder';
5 |
6 | export function App() {
7 | return (
8 | <>
9 |
10 |
11 |
12 |
13 | >
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/u-int.ts:
--------------------------------------------------------------------------------
1 | import { readInt } from '../helpers.js';
2 | import { Int } from '../constants.js';
3 | import { ArgumentError } from '../../../utils/errors.js';
4 | import { toBytes } from '../../../utils/bytes.js';
5 |
6 | export default {
7 | serialize(value: Int): Buffer {
8 | if (Number(value) < 0) throw new ArgumentError('value', 'greater or equal to 0', value);
9 | return toBytes(value, true);
10 | },
11 |
12 | deserialize(value: Buffer): string {
13 | return readInt(value);
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /dist
3 | /es
4 | /docs/api
5 | /docs/examples
6 | /docs/public
7 | __pycache__
8 | .idea
9 | .vscode
10 | /coverage
11 | /**/package-lock.json
12 | /**/dist
13 | /examples/**/artifacts
14 | .history
15 | .site
16 | site
17 | /src/apis/
18 | /src/tx/builder/schema.generated.ts
19 | /src/tx/builder/delegation/schema.generated.ts
20 | /src/tx/builder/entry/schema.generated.ts
21 | /tooling/autorest/compiler-swagger.yaml
22 | /tooling/autorest/middleware-openapi.yaml
23 | /test/environment/ledger/browser
24 | /test/assets
25 | /bin
26 |
--------------------------------------------------------------------------------
/examples/browser/aepp/public/webmanifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Simple æpp",
3 | "short_name": "Simple æpp",
4 | "icons": [
5 | {
6 | "src": "./icon.svg",
7 | "type": "image/svg"
8 | }
9 | ],
10 | "aeternity_network_ids": ["ae_mainnet", "ae_uat"],
11 | "description": "Simple æpp description. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
12 | "categories": ["finance"],
13 | "author": "aeternity developers",
14 | "author_url": "https://github.com/aeternity/aepp-sdk-js"
15 | }
16 |
--------------------------------------------------------------------------------
/src/utils/string.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Convert string from snake_case to PascalCase
3 | * @param s - String to convert
4 | * @returns Converted string
5 | */
6 | export function snakeToPascal(s: string): string {
7 | return s.replace(/_./g, (match) => match[1].toUpperCase());
8 | }
9 |
10 | /**
11 | * Convert string from PascalCase to snake_case
12 | * @param s - String to convert
13 | * @returns Converted string
14 | */
15 | export function pascalToSnake(s: string): string {
16 | return s.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`);
17 | }
18 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/name-id.ts:
--------------------------------------------------------------------------------
1 | import { AensName } from '../constants.js';
2 | import { produceNameId, isName } from '../helpers.js';
3 | import address from './address.js';
4 | import { Encoded, Encoding } from '../../../utils/encoder.js';
5 |
6 | const addressName = address(Encoding.Name);
7 |
8 | export default {
9 | ...addressName,
10 |
11 | /**
12 | * @param value - AENS name ID
13 | */
14 | serialize(value: AensName | Encoded.Name): Buffer {
15 | return addressName.serialize(isName(value) ? produceNameId(value) : value);
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/tsdoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
3 | "tagDefinitions": [
4 | {
5 | "tagName": "@category",
6 | "syntaxKind": "block"
7 | },
8 | {
9 | "tagName": "@hidden",
10 | "syntaxKind": "block"
11 | }
12 | ],
13 | "supportForTags": {
14 | "@param": true,
15 | "@returns": true,
16 | "@throws": true,
17 | "@link": true,
18 | "@see": true,
19 | "@example": true,
20 | "@category": true,
21 | "@deprecated": true,
22 | "@hidden": true
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/browser/tools/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tools",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@aeternity/aepp-sdk": "file:../../..",
13 | "@scure/bip39": "^1.3.0",
14 | "preact": "^10.19.6"
15 | },
16 | "devDependencies": {
17 | "@preact/preset-vite": "^2.8.2",
18 | "rollup-plugin-visualizer": "^5.12.0",
19 | "typescript": "^5.2.2",
20 | "vite": "^5.2.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/test/environment/ledger/webpack.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import webpack from 'webpack';
3 | import HtmlWebpackPlugin from 'html-webpack-plugin';
4 |
5 | export default {
6 | entry: './browser.js',
7 | mode: 'production',
8 | target: 'browserslist:browser',
9 | output: {
10 | path: path.resolve(import.meta.dirname, './browser'),
11 | },
12 | plugins: [new HtmlWebpackPlugin(), new webpack.ProvidePlugin({ Buffer: ['buffer', 'Buffer'] })],
13 | experiments: {
14 | topLevelAwait: true,
15 | },
16 | optimization: {
17 | minimize: false,
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/with-default.ts:
--------------------------------------------------------------------------------
1 | export default function withDefault (
2 | defaultValue: Input,
3 | field: {
4 | serialize: (value: Input, params: unknown) => Binary;
5 | deserialize: (value: Binary, params: unknown) => Output;
6 | },
7 | ): {
8 | serialize: (value: Input | undefined, params: unknown) => Binary;
9 | deserialize: (value: Binary, params: unknown) => Output;
10 | } {
11 | return {
12 | ...field,
13 |
14 | serialize(value, params) {
15 | return field.serialize(value ?? defaultValue, params);
16 | },
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/test/environment/vue-cli-4-autorest/src/main.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/no-unresolved
2 | import { AeSdk, Node, AccountMemory } from '@aeternity/aepp-sdk';
3 |
4 | const aeSdk = new AeSdk({
5 | nodes: [
6 | {
7 | name: 'testnet',
8 | instance: new Node('https://testnet.aeternity.io'),
9 | },
10 | ],
11 | });
12 |
13 | (async () => {
14 | const balance = await aeSdk.getBalance(AccountMemory.generate().address);
15 | if (balance !== '0') console.error('Balance expected to be equal 0');
16 | else console.log('`instanceof RestError` check works correctly');
17 | })();
18 |
--------------------------------------------------------------------------------
/tooling/fetch-aesophia-cli.js:
--------------------------------------------------------------------------------
1 | import { writeFileSync } from 'fs';
2 | // eslint-disable-next-line import/extensions
3 | import restoreFile from './restore-file.js';
4 |
5 | const path = './bin/aesophia_cli';
6 | const hash =
7 | 'RYAgt3BbPt4UlANdcOff68hca0p1q2dK+H1b5BSMNUl6+zb9JjoJIn2/MlMxJAF0WdpjJKlVTkocXY7pMVIzCg==';
8 |
9 | await restoreFile(path, hash, async () => {
10 | const request = await fetch(
11 | 'https://github.com/aeternity/aesophia_cli/releases/download/v8.0.0/aesophia_cli',
12 | );
13 | const body = Buffer.from(await request.arrayBuffer());
14 | writeFileSync(path, body);
15 | });
16 |
--------------------------------------------------------------------------------
/test/environment/vue-cli-4-autorest/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-cli-4-autorest",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build"
8 | },
9 | "dependencies": {
10 | "@aeternity/aepp-sdk": "file:../../..",
11 | "vue": "^3.0.0"
12 | },
13 | "devDependencies": {
14 | "@vue/cli-plugin-babel": "^4.5.19",
15 | "@vue/cli-service": "~4.5.19",
16 | "@vue/compiler-sfc": "^3.0.0"
17 | },
18 | "browserslist": [
19 | "> 1%",
20 | "last 2 versions",
21 | "not dead"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | extends: ['@commitlint/config-conventional'],
3 | rules: {
4 | 'scope-enum': [
5 | 2,
6 | 'always',
7 | [
8 | 'account',
9 | 'aens',
10 | 'aepp',
11 | 'chain',
12 | 'channel',
13 | 'compiler',
14 | 'contract',
15 | 'middleware',
16 | 'deps',
17 | 'deps-dev',
18 | 'node',
19 | 'oracle',
20 | 'release',
21 | 'tx-builder',
22 | 'wallet',
23 | ],
24 | ],
25 | },
26 | ignores: [(message) => /^Bumps \[.+]\(.+\) from .+ to .+\.$/m.test(message)],
27 | };
28 |
--------------------------------------------------------------------------------
/examples/browser/wallet-iframe/README.md:
--------------------------------------------------------------------------------
1 | # iframe-based wallet
2 |
3 | ## Overview
4 |
5 | This is a sample wallet that expects an æpp to be loaded into its iframe.
6 |
7 | ### How it works
8 |
9 | 1. Start this wallet, which will start on port `9000`
10 | 2. Start the [sample contract æpp](../aepp), which will start on port `9001`
11 | 3. Visit [localhost:9000](http://localhost:9000) to see the æpp included into this wallet
12 |
13 | ## Installation and running
14 |
15 | Prerequisite: [refer SDK installation](../README.md#setup-info)
16 |
17 | 1. Install required dependencies with `npm install`
18 | 1. Start the application `npm run serve`
19 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/with-formatting.ts:
--------------------------------------------------------------------------------
1 | export default function withFormatting (
2 | format: (v: Input | undefined) => Input,
3 | field: {
4 | serialize: (value: Input, params: Params, options: Options) => Binary;
5 | deserialize: (value: Binary) => Output;
6 | },
7 | ): {
8 | serialize: (value: Input | undefined, params: Params, options: Options) => Binary;
9 | deserialize: (value: Binary) => Output;
10 | } {
11 | return {
12 | ...field,
13 |
14 | serialize(value, params, options) {
15 | return field.serialize(format(value), params, options);
16 | },
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/examples/browser/wallet-iframe/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Wallet Iframe
8 |
9 |
10 |
11 |
12 | We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it
13 | to continue.
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/tooling/autorest/compiler-prepare.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 |
3 | const swaggerUrl =
4 | 'https://raw.githubusercontent.com/aeternity/aesophia_http/v8.0.0/config/swagger.yaml';
5 |
6 | const response = await fetch(swaggerUrl);
7 | console.assert(response.status === 200, 'Invalid response code', response.status);
8 | let swagger = await response.text();
9 |
10 | swagger = swagger.replace(/basePath: \//, '');
11 | // TODO: Remove after fixing https://github.com/aeternity/aesophia_http/issues/87
12 | swagger = swagger.replace(/'400':.{80,120}?Error'\s+'400':/gms, "'400':");
13 | await fs.promises.writeFile('./tooling/autorest/compiler-swagger.yaml', swagger);
14 |
--------------------------------------------------------------------------------
/src/typings/tweetnacl-auth/index.d.ts:
--------------------------------------------------------------------------------
1 | // TODO: remove after solving https://github.com/dchest/tweetnacl-auth-js/issues/3
2 |
3 | declare module 'tweetnacl-auth' {
4 | interface Api {
5 | /**
6 | * Authenticates the given message with the secret key.
7 | * (In other words, returns HMAC-SHA-512-256 of the message under the key.)
8 | */
9 | (message: Uint8Array, key: Uint8Array): Uint8Array;
10 | /**
11 | * Returns HMAC-SHA-512 (without truncation) of the message under the key
12 | */
13 | full: (message: Uint8Array, key: Uint8Array) => Uint8Array;
14 | }
15 |
16 | const api: Api;
17 |
18 | export default api;
19 | }
20 |
--------------------------------------------------------------------------------
/tooling/restore-file.js:
--------------------------------------------------------------------------------
1 | import { createHash } from 'crypto';
2 | import { dirname } from 'path';
3 | import { readFileSync, mkdirSync } from 'fs';
4 |
5 | function ensureFileHash(path, hash) {
6 | const buffer = readFileSync(path);
7 | const h = createHash('sha512').update(buffer).digest('base64');
8 | if (h !== hash) throw new Error(`Wrong hash ${h}`);
9 | }
10 |
11 | export default async function restoreFile(path, hash, cb) {
12 | try {
13 | ensureFileHash(path, hash);
14 | } catch {
15 | console.info(`Restoring ${path}`);
16 | mkdirSync(dirname(path), { recursive: true });
17 | await cb();
18 | ensureFileHash(path, hash);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/array.ts:
--------------------------------------------------------------------------------
1 | export default function genArrayField (itemHandler: {
2 | serialize: (value: Input, params: unknown) => Binary;
3 | deserialize: (value: Binary, params: unknown) => Output;
4 | }): {
5 | serialize: (value: readonly Input[], params: unknown) => Binary[];
6 | deserialize: (value: Binary[], params: unknown) => Output[];
7 | } {
8 | return {
9 | serialize(items, params) {
10 | return items.map((item) => itemHandler.serialize(item, params));
11 | },
12 |
13 | deserialize(buffers, params) {
14 | return buffers.map((buffer) => itemHandler.deserialize(buffer, params));
15 | },
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/examples/node/_api-high-level.js:
--------------------------------------------------------------------------------
1 | import { AeSdk, Node, AccountMemory, encode, Encoding } from '@aeternity/aepp-sdk';
2 |
3 | const aeSdk = new AeSdk({
4 | nodes: [
5 | {
6 | name: 'testnet',
7 | instance: new Node('https://testnet.aeternity.io'), // host your node for better decentralization
8 | },
9 | ],
10 | accounts: [new AccountMemory('sk_2CuofqWZHrABCrM7GY95YSQn8PyFvKQadnvFnpwhjUnDCFAWmf')],
11 | });
12 |
13 | const transactionInfo = await aeSdk.spend(
14 | 100, // aettos
15 | 'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E',
16 | { payload: encode(Buffer.from('spend tx payload'), Encoding.Bytearray) },
17 | );
18 | console.log(transactionInfo);
19 |
--------------------------------------------------------------------------------
/tooling/eslint-rules/tsdoc-syntax.cjs:
--------------------------------------------------------------------------------
1 | /**
2 | * Remove after fixing https://github.com/microsoft/tsdoc/issues/220
3 | */
4 |
5 | const plugin = require('eslint-plugin-tsdoc');
6 |
7 | const { create } = plugin.rules.syntax;
8 |
9 | plugin.rules.syntax.create = (context) =>
10 | create(
11 | new Proxy(
12 | {},
13 | {
14 | get(target, name) {
15 | if (name !== 'report') return context[name];
16 | return (data) => {
17 | if (data.messageId === 'tsdoc-param-tag-with-invalid-name') return;
18 | context.report(data);
19 | };
20 | },
21 | },
22 | ),
23 | );
24 |
25 | module.exports = plugin.rules.syntax;
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: feature
6 | assignees: ''
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/docker/aeternity.yaml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://github.com/aeternity/aeternity/raw/master/apps/aeutils/priv/aeternity_config_schema.json
2 |
3 | system:
4 | dev_mode: true
5 | plugins:
6 | # TODO: remove after merging https://github.com/aeternity/aeternity/pull/4303
7 | - name: aeplugin_dev_mode
8 |
9 | http:
10 | internal:
11 | debug_endpoints: true
12 | listen_address: 0.0.0.0
13 | endpoints:
14 | dry-run: true
15 |
16 | websocket:
17 | channel:
18 | listen_address: 0.0.0.0
19 |
20 | chain:
21 | persist: false
22 | hard_forks:
23 | '1': 0
24 | '6': 1
25 | genesis_accounts:
26 | ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E: 10000000000000000000000
27 |
--------------------------------------------------------------------------------
/examples/browser/aepp/README.md:
--------------------------------------------------------------------------------
1 | # Sample æpp for contracts
2 |
3 | ## Overview
4 |
5 | This is a sample æpp that compiles contracts using the æternity JavaScript SDK.
6 |
7 | ### How it works
8 |
9 | 1. Choose the wallet example from [examples](..) folder (the simplest is [iframe-based wallet](../wallet-iframe))
10 | 2. Start the wallet according to its readme
11 | 3. Start this æpp, which will start on port [9001](http://localhost:9001)
12 | 4. Connect this æpp to a choosed wallet according to its readme
13 |
14 | ## Installation and running
15 |
16 | Prerequisite: [refer SDK installation](../README.md#setup-info)
17 |
18 | 1. Install required dependencies with `npm install`
19 | 1. Start the application `npm run serve`
20 |
--------------------------------------------------------------------------------
/examples/browser/aepp/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Simple æpp
10 |
11 |
12 |
13 |
14 | We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it
15 | to continue.
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | presets: [
3 | [
4 | '@babel/preset-env',
5 | {
6 | include: [
7 | // compatibility with create-react-app@4
8 | '@babel/plugin-proposal-nullish-coalescing-operator',
9 | // compatibility with vue-cli-plugin-browser-extension@0.25
10 | '@babel/plugin-proposal-logical-assignment-operators',
11 | // compatibility with @vue/cli@4
12 | '@babel/plugin-proposal-class-properties',
13 | '@babel/plugin-proposal-private-methods',
14 | ],
15 | },
16 | ],
17 | '@babel/preset-typescript',
18 | ],
19 | plugins: [
20 | ['@babel/plugin-transform-runtime', { corejs: 3 }],
21 | 'babel-plugin-transform-import-meta',
22 | ],
23 | };
24 |
--------------------------------------------------------------------------------
/tooling/fetch-metamask.js:
--------------------------------------------------------------------------------
1 | import { writeFileSync } from 'fs';
2 | import { resolve } from 'path';
3 | import extractZip from 'extract-zip';
4 | // eslint-disable-next-line import/extensions
5 | import restoreFile from './restore-file.js';
6 |
7 | const path = './test/assets/metamask.zip';
8 | const hash =
9 | 'syt/OJLdXM1al3TdG7s/xXRYFj0mTZ6UDrK/+KzTmvLxhfQdIO8/82MQbep2CR67Gwz8wRaM1TZzpK3dyqjNSg==';
10 |
11 | await restoreFile(path, hash, async () => {
12 | const request = await fetch(
13 | 'https://github.com/MetaMask/metamask-extension/releases/download/v12.3.1/metamask-chrome-12.3.1.zip',
14 | );
15 | const body = Buffer.from(await request.arrayBuffer());
16 | writeFileSync(path, body);
17 | await extractZip(path, { dir: resolve(path).split('.')[0] });
18 | });
19 |
--------------------------------------------------------------------------------
/examples/browser/wallet-iframe/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wallet-iframe",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build"
8 | },
9 | "dependencies": {
10 | "@aeternity/aepp-calldata": "^1.5.1",
11 | "@aeternity/aepp-sdk": "file:../../..",
12 | "buffer": "^6.0.3",
13 | "core-js": "^3.32.1",
14 | "tailwindcss": "^2.2.19",
15 | "vue": "^3.3.4"
16 | },
17 | "devDependencies": {
18 | "@vue/cli-plugin-babel": "~5.0.8",
19 | "@vue/cli-service": "~5.0.8",
20 | "sass": "^1.66.1",
21 | "sass-loader": "^13.3.2"
22 | },
23 | "browserslist": [
24 | "> 1%",
25 | "last 2 versions",
26 | "not dead",
27 | "not ie 11"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/test/unit/string.ts:
--------------------------------------------------------------------------------
1 | import '..';
2 | import { describe, it } from 'mocha';
3 | import { expect } from 'chai';
4 | import { snakeToPascal, pascalToSnake } from '../../src/utils/string';
5 |
6 | describe('Strings', () => {
7 | describe('converts case', () => {
8 | it('from snake to pascal', () => {
9 | expect(snakeToPascal('foo_bar_baz')).to.equal('fooBarBaz');
10 | expect(snakeToPascal('foo_bar_')).to.equal('fooBar_');
11 | expect(snakeToPascal('_bar_baz')).to.equal('BarBaz');
12 | });
13 |
14 | it('from pascal to snake', () => {
15 | expect(pascalToSnake('fooBarBaz')).to.equal('foo_bar_baz');
16 | expect(pascalToSnake('fooBar')).to.equal('foo_bar');
17 | expect(pascalToSnake('BarBaz')).to.equal('_bar_baz');
18 | });
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License (ISC)
2 | Copyright © 2025 aeternity developers
3 |
4 | Permission to use, copy, modify, and/or distribute this software for any purpose
5 | with or without fee is hereby granted, provided that the above copyright notice
6 | and this permission notice appear in all copies.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
10 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
12 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
13 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
14 | THIS SOFTWARE.
15 |
--------------------------------------------------------------------------------
/src/utils/bignumber.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Big Number Helpers
3 | */
4 | import BigNumber from 'bignumber.js';
5 |
6 | /**
7 | * Check if value is BigNumber, Number, BigInt or number string representation
8 | * @param number - number to check
9 | */
10 | export const isBigNumber = (number: string | number | bigint | BigNumber): boolean => {
11 | if (typeof number === 'bigint') return true;
12 | return (
13 | ['number', 'object', 'string'].includes(typeof number) &&
14 | // eslint-disable-next-line no-restricted-globals
15 | (!isNaN(number as number) || Number.isInteger(number) || BigNumber.isBigNumber(number))
16 | );
17 | };
18 |
19 | /**
20 | * BigNumber ceil operation
21 | */
22 | export const ceil = (bigNumber: BigNumber): BigNumber =>
23 | bigNumber.integerValue(BigNumber.ROUND_CEIL);
24 |
--------------------------------------------------------------------------------
/src/aepp-wallet-communication/connection-proxy.ts:
--------------------------------------------------------------------------------
1 | import BrowserConnection from './connection/Browser.js';
2 |
3 | /**
4 | * Browser connection proxy
5 | * Provide functionality to easily forward messages from one connection to another and back
6 | * @category aepp wallet communication
7 | * @param con1 - first connection
8 | * @param con2 - second connection
9 | * @returns a function to stop proxying
10 | */
11 | export default (con1: BrowserConnection, con2: BrowserConnection): (() => void) => {
12 | con1.connect(
13 | (msg: any) => con2.sendMessage(msg),
14 | () => con2.disconnect(),
15 | );
16 | con2.connect(
17 | (msg: any) => con1.sendMessage(msg),
18 | () => con1.disconnect(),
19 | );
20 |
21 | return () => {
22 | con1.disconnect();
23 | con2.disconnect();
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/test/environment/node-unhandled-exception.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /* eslint-disable import/extensions */
3 | import { Node, CompilerHttp } from '../../es/index.js';
4 | import { pause } from '../../es/utils/other.js';
5 | /* eslint-enable import/extensions */
6 |
7 | const invalidUrl = 'https://404.aeternity.io';
8 | const compiler = new CompilerHttp(invalidUrl);
9 | const node = new Node(invalidUrl);
10 |
11 | await pause(2000);
12 |
13 | const message1 = await compiler.version().catch((error) => error.message);
14 | const message2 = await node.getStatus().catch((error) => error.message);
15 | if (message1 !== message2 || message2 !== 'getaddrinfo ENOTFOUND 404.aeternity.io') {
16 | throw new Error('Invalid exception');
17 | }
18 |
19 | console.log("Failure of version check doesn't emit unhandled exception");
20 |
--------------------------------------------------------------------------------
/examples/browser/wallet-web-extension/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "wallet-web-extension",
4 | "homepage_url": "http://localhost:8080/",
5 | "description": "A Vue Browser Extension",
6 | "permissions": ["activeTab", "", "*://*/*"],
7 | "icons": {
8 | "128": "icons/128.png"
9 | },
10 | "background": {
11 | "scripts": ["js/background.js"],
12 | "persistent": false
13 | },
14 | "browser_action": {
15 | "default_popup": "popup.html",
16 | "default_title": "wallet-web-extension",
17 | "default_icon": {
18 | "34": "icons/34.png"
19 | }
20 | },
21 | "content_scripts": [
22 | {
23 | "matches": ["https://*/*", "http://*/*", "file:///*"],
24 | "js": ["js/content-script.js"],
25 | "all_frames": true
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # ⚡ Examples
2 |
3 | This folder contains examples of code samples that you can run autonomously.
4 | Create a [new issue](https://github.com/aeternity/aepp-sdk-js/issues/new) to suggest another example.
5 |
6 | ## Wallet connection
7 |
8 | ### Connect to a wallet
9 |
10 | - [æpp](browser/aepp) (VueJS)
11 |
12 | ### Build a wallet
13 |
14 | - [iframe-based Wallet](browser/wallet-iframe) (VueJS)
15 | - [Wallet WebExtension](browser/wallet-web-extension)
16 |
17 | ## NodeJS
18 |
19 | 1. [Contract interaction](node/contract-interaction.js)
20 | 2. [Transfer AE](node/transfer-ae.js)
21 | 3. [Paying for spend tx](node/paying-for-spend-tx.js)
22 | 4. [Paying for contract call tx](node/paying-for-contract-call-tx.js)
23 | 5. [Dry-run using debug endpoint](node/dry-run-using-debug-endpoint.js)
24 | 6. [Oracle](node/oracle.js)
25 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/coin-amount.ts:
--------------------------------------------------------------------------------
1 | import uInt from './u-int.js';
2 | import { Int } from '../constants.js';
3 | import { AE_AMOUNT_FORMATS, formatAmount } from '../../../utils/amount-formatter.js';
4 |
5 | export default {
6 | ...uInt,
7 |
8 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
9 | serializeAettos(value: string | undefined, params: {}, options: {}): string {
10 | return value ?? '0';
11 | },
12 |
13 | serialize(
14 | value: Int | undefined,
15 | params: {},
16 | { denomination = AE_AMOUNT_FORMATS.AETTOS, ...options }: { denomination?: AE_AMOUNT_FORMATS },
17 | ): Buffer {
18 | return uInt.serialize(
19 | this.serializeAettos(
20 | value != null ? formatAmount(value, { denomination }) : value,
21 | params,
22 | options,
23 | ),
24 | );
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/src/utils/semver-satisfies.ts:
--------------------------------------------------------------------------------
1 | function verCmp(a: string, b: string): number {
2 | const getComponents = (v: string): number[] =>
3 | v
4 | .split(/[-+]/)[0]
5 | .split('.')
6 | .map((i) => +i);
7 |
8 | const aComponents = getComponents(a);
9 | const bComponents = getComponents(b);
10 |
11 | const base = Math.max(...aComponents, ...bComponents) + 1;
12 | const componentsToNumber = (components: number[]): number =>
13 | components.reverse().reduce((acc, n, idx) => acc + n * base ** idx, 0);
14 |
15 | return componentsToNumber(aComponents) - componentsToNumber(bComponents);
16 | }
17 |
18 | export default function semverSatisfies(
19 | version: string,
20 | geVersion: string,
21 | ltVersion?: string,
22 | ): boolean {
23 | return verCmp(version, geVersion) >= 0 && (ltVersion == null || verCmp(version, ltVersion) < 0);
24 | }
25 |
--------------------------------------------------------------------------------
/examples/node/_api-low-level.js:
--------------------------------------------------------------------------------
1 | import { Node, AccountMemory, buildTxAsync, Tag, encode, Encoding } from '@aeternity/aepp-sdk';
2 |
3 | const onNode = new Node('https://testnet.aeternity.io'); // host your node for better decentralization
4 | const account = new AccountMemory('sk_2CuofqWZHrABCrM7GY95YSQn8PyFvKQadnvFnpwhjUnDCFAWmf');
5 |
6 | const spendTx = await buildTxAsync({
7 | tag: Tag.SpendTx,
8 | senderId: account.address,
9 | recipientId: 'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E',
10 | amount: 100, // aettos
11 | payload: encode(Buffer.from('spend tx payload'), Encoding.Bytearray),
12 | onNode,
13 | });
14 |
15 | const signedTx = await account.signTransaction(spendTx, { networkId: await onNode.getNetworkId() });
16 |
17 | // broadcast the signed tx to the node
18 | const { txHash } = await onNode.postTransaction({ tx: signedTx });
19 | console.log(txHash);
20 |
--------------------------------------------------------------------------------
/examples/browser/wallet-web-extension/README.md:
--------------------------------------------------------------------------------
1 | # WebExtension-based wallet
2 |
3 | ## Overview
4 |
5 | This is a sample wallet as an WebExtension. It works with æpp opened in a browser where it is installed.
6 |
7 | ### How it works
8 |
9 | 1. Install this wallet to Chrome or Firefox
10 | 2. Start the [sample contract æpp](../aepp), which will start on port `9001`
11 | 3. Visit [localhost:9001](http://localhost:9001)
12 | 4. This wallet should attempt to connect to the æpp
13 |
14 | ## Installation and running in Google Chrome
15 |
16 | Prerequisite: [refer SDK installation](../README.md#setup-info)
17 |
18 | 1. Install required dependencies with `npm install`
19 | 2. Start the build server in watch mode `npm run serve`
20 | 3. Open [chrome://extensions](chrome://extensions/)
21 | 4. Enable "Developer mode" at the right top conner
22 | 5. Press "Load unpacked" button and choose the `dist` folder
23 |
--------------------------------------------------------------------------------
/examples/browser/wallet-web-extension/src/styles.scss:
--------------------------------------------------------------------------------
1 | @use '~tailwindcss/dist/base';
2 | @use '~tailwindcss/dist/components';
3 | @use '~tailwindcss/dist/utilities';
4 |
5 | body {
6 | @extend .p-4;
7 | }
8 |
9 | button {
10 | @extend .w-44, .p-2, .m-2, .rounded-full, .bg-purple-500, .text-white, .text-xs;
11 |
12 | &:disabled {
13 | @extend .bg-purple-300, .cursor-not-allowed;
14 | }
15 | }
16 |
17 | h2 {
18 | @extend .mt-2, .font-bold, .text-2xl;
19 | }
20 |
21 | .group {
22 | @extend .border, .mt-4, .rounded, .bg-gray-100, .font-mono;
23 |
24 | > div {
25 | @extend .sm\:flex;
26 |
27 | > * {
28 | @extend .p-2;
29 | }
30 |
31 | > :nth-child(1) {
32 | @extend .sm\:w-1\/4, .font-semibold, .sm\:font-normal, .self-center;
33 | }
34 |
35 | > :nth-child(2) {
36 | @extend .sm\:w-3\/4, .break-words, .whitespace-pre-wrap;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/examples/browser/tools/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "skipLibCheck": true,
8 | "paths": {
9 | "react": ["./node_modules/preact/compat/"],
10 | "react-dom": ["./node_modules/preact/compat/"]
11 | },
12 |
13 | /* Bundler mode */
14 | "moduleResolution": "bundler",
15 | "allowImportingTsExtensions": true,
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "noEmit": true,
19 | "jsx": "react-jsx",
20 | "jsxImportSource": "preact",
21 |
22 | /* Linting */
23 | "strict": true,
24 | "noUnusedLocals": true,
25 | "noUnusedParameters": true,
26 | "noFallthroughCasesInSwitch": true
27 | },
28 | "include": ["src"],
29 | "references": [{ "path": "./tsconfig.node.json" }]
30 | }
31 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/Connect.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
23 |
--------------------------------------------------------------------------------
/docker/middleware.yaml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://github.com/aeternity/aeternity/raw/master/apps/aeutils/priv/aeternity_config_schema.json
2 |
3 | system:
4 | dev_mode: true
5 | plugins:
6 | - name: aeplugin_dev_mode
7 |
8 | http:
9 | endpoints:
10 | dry-run: true
11 |
12 | websocket:
13 | channel:
14 | listen_address: 0.0.0.0
15 |
16 | dev_mode:
17 | auto_emit_microblocks: true
18 |
19 | chain:
20 | persist: false
21 | hard_forks:
22 | '1': 0
23 | '6': 1
24 | genesis_accounts:
25 | ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E: 10000000000000000000000
26 |
27 | # TODO: remove after solving https://github.com/aeternity/ae_mdw/issues/1760
28 | fork_management:
29 | network_id: ae_dev
30 |
31 | # TODO remove after solving https://github.com/aeternity/ae_mdw/issues/1760#issuecomment-2102872638
32 | mining:
33 | beneficiary: ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E
34 |
--------------------------------------------------------------------------------
/examples/browser/wallet-web-extension/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wallet-web-extension",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service build --mode development --watch",
7 | "build": "vue-cli-service build"
8 | },
9 | "dependencies": {
10 | "@aeternity/aepp-calldata": "^1.5.1",
11 | "@aeternity/aepp-sdk": "file:../../..",
12 | "core-js": "^3.32.1",
13 | "tailwindcss": "^2.2.19",
14 | "vue": "^2.6.14",
15 | "webextension-polyfill": "^0.9.0"
16 | },
17 | "devDependencies": {
18 | "@vue/cli-plugin-babel": "^4.5.17",
19 | "@vue/cli-service": "^4.5.17",
20 | "sass": "^1.66.1",
21 | "sass-loader": "^8.0.2",
22 | "vue-cli-plugin-browser-extension": "^0.25.2",
23 | "vue-template-compiler": "^2.7.14"
24 | },
25 | "browserslist": [
26 | "> 1%",
27 | "last 2 versions",
28 | "not dead",
29 | "not ie 11"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/encoded.ts:
--------------------------------------------------------------------------------
1 | import { decode, encode, Encoded, Encoding } from '../../../utils/encoder.js';
2 | import { ArgumentError } from '../../../utils/errors.js';
3 |
4 | export default function genEncodedField(
5 | encoding: E,
6 | optional?: Optional,
7 | ): {
8 | serialize: Optional extends true
9 | ? (value?: Encoded.Generic) => Buffer
10 | : (value: Encoded.Generic) => Buffer;
11 | deserialize: (value: Buffer) => Encoded.Generic;
12 | } {
13 | return {
14 | serialize(encodedData?: Encoded.Generic) {
15 | if (encodedData == null) {
16 | if (optional === true) return Buffer.from([]);
17 | throw new ArgumentError('Encoded data', 'provided', encodedData);
18 | }
19 | return decode(encodedData);
20 | },
21 |
22 | deserialize(buffer) {
23 | return encode(buffer, encoding);
24 | },
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/query-fee.ts:
--------------------------------------------------------------------------------
1 | import coinAmount from './coin-amount.js';
2 | import { Int } from '../constants.js';
3 | import Node from '../../../Node.js';
4 | import { Encoded } from '../../../utils/encoder.js';
5 | import { ArgumentError } from '../../../utils/errors.js';
6 |
7 | /**
8 | * Oracle query fee
9 | */
10 | export default {
11 | ...coinAmount,
12 |
13 | async prepare(
14 | value: Int | undefined,
15 | params: {},
16 | options: { oracleId?: Encoded.OracleAddress; onNode?: Node },
17 | ) {
18 | if (value != null) return value;
19 | const { onNode, oracleId } = options;
20 | const requirement = 'provided (or provide `queryFee` instead)';
21 | if (onNode == null) throw new ArgumentError('onNode', requirement, onNode);
22 | if (oracleId == null) throw new ArgumentError('oracleId', requirement, oracleId);
23 | return (await onNode.getOracleByPubkey(oracleId)).queryFee.toString();
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/examples/browser/aepp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aepp",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build"
8 | },
9 | "dependencies": {
10 | "@aeternity/aepp-calldata": "^1.5.1",
11 | "@aeternity/aepp-sdk": "file:../../..",
12 | "@ledgerhq/hw-transport-web-ble": "^6.29.4",
13 | "@ledgerhq/hw-transport-webusb": "^6.29.4",
14 | "buffer": "^6.0.3",
15 | "core-js": "^3.32.1",
16 | "tailwindcss": "^2.2.19",
17 | "vue": "^3.3.4",
18 | "vuex": "^4.1.0"
19 | },
20 | "devDependencies": {
21 | "@vue/cli-plugin-babel": "~5.0.8",
22 | "@vue/cli-service": "~5.0.8",
23 | "sass": "^1.66.1",
24 | "sass-loader": "^13.3.2"
25 | },
26 | "peerDependencies": {
27 | "webpack": "^5.0.0"
28 | },
29 | "browserslist": [
30 | "> 1%",
31 | "last 2 versions",
32 | "not dead",
33 | "not ie 11"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/components/MessageSign.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 |
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Please tell us about your environment:**
27 |
28 | - Node Version: v0.0.0
29 | - Protocol Version: 1
30 | - Compiler version: v0.0.0
31 | - VM Version: fate | fate2
32 | - SDK Version: v0.0.0
33 | - Python version: v3.7.0
34 |
35 | **Other information** (e.g. detailed explanation, stack traces, related issues, suggestions how to fix, links for us to have context, eg. forum, telegram, etc)
36 |
--------------------------------------------------------------------------------
/examples/browser/wallet-web-extension/vue.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | pages: {
5 | popup: {
6 | template: 'public/browser-extension.html',
7 | entry: './src/popup.js',
8 | title: 'Popup',
9 | },
10 | },
11 | pluginOptions: {
12 | browserExtension: {
13 | componentOptions: {
14 | background: {
15 | entry: 'src/background.js',
16 | },
17 | contentScripts: {
18 | entries: {
19 | 'content-script': ['src/content-script.js'],
20 | },
21 | },
22 | },
23 | },
24 | },
25 | // this workaround is only needed when sdk is not in the node_modules folder
26 | chainWebpack: (config) => {
27 | const sdkPath = path.join(__dirname, '..', '..', '..', 'es');
28 | config.module.rule('mjs').include.add(sdkPath);
29 | config.module.rule('js').test(/\.[cm]?jsx?$/);
30 | },
31 | transpileDependencies: ['@aeternity/aepp-calldata'],
32 | };
33 |
--------------------------------------------------------------------------------
/src/utils/json-big.ts:
--------------------------------------------------------------------------------
1 | import JsonBig from 'json-bigint';
2 | import BigNumber from 'bignumber.js';
3 | import { mapObject } from './other.js';
4 |
5 | const jsonBig = JsonBig({ storeAsString: true });
6 |
7 | const convertValuesToBigNumbers = (value: any): any => {
8 | if (typeof value === 'object' && value !== null && value.constructor === Object) {
9 | return mapObject(value, ([k, v]) => [k, convertValuesToBigNumbers(v)]);
10 | }
11 | if (Array.isArray(value)) {
12 | return value.map((item) => convertValuesToBigNumbers(item));
13 | }
14 | if (typeof value === 'string' && new BigNumber(value).toString(10) === value) {
15 | const bn = new BigNumber(value);
16 | bn.toJSON = () => bn.toString(10);
17 | return bn;
18 | }
19 | return value;
20 | };
21 |
22 | export default {
23 | stringify: (...args: Parameters<(typeof JsonBig)['stringify']>): string =>
24 | jsonBig.stringify(convertValuesToBigNumbers(args[0]), ...args.slice(1)),
25 | parse: jsonBig.parse,
26 | };
27 |
--------------------------------------------------------------------------------
/test/unit/semver-satisfies.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { describe, it } from 'mocha';
3 | import semverSatisfies from '../../src/utils/semver-satisfies';
4 |
5 | describe('semverSatisfies', () => {
6 | it('returns a proper value', () => {
7 | expect(semverSatisfies('1.0.0', '1.0.0', '1.0.1')).to.equal(true);
8 | expect(semverSatisfies('1.0.0', '1.0.1', '1.0.2')).to.equal(false);
9 | expect(semverSatisfies('2.4.0', '1.4.0', '3.0.0')).to.equal(true);
10 | expect(semverSatisfies('2.4.0', '2.5.0', '3.0.0')).to.equal(false);
11 | expect(semverSatisfies('1.9.0', '2.0.0', '3.0.0')).to.equal(false);
12 | expect(semverSatisfies('1.9.0', '2.0.0', '3.0.0')).to.equal(false);
13 | expect(semverSatisfies('5.0.0', '3.0.0', '5.0.0')).to.equal(false);
14 | expect(semverSatisfies('6.0.0-rc4', '6.0.0', '7.0.0')).to.equal(true);
15 | expect(semverSatisfies('6.3.0+2.0f7ce80e', '6.0.0', '7.0.0')).to.equal(true);
16 | expect(semverSatisfies('7.0.0', '6.13.0')).to.equal(true);
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: CodeQL
2 |
3 | on:
4 | push:
5 | branches: [develop, master]
6 | pull_request:
7 | branches: [develop]
8 | schedule:
9 | - cron: 59 6 * * 2
10 |
11 | jobs:
12 | analyze:
13 | name: Analyze
14 | runs-on: ubuntu-latest
15 | permissions:
16 | actions: read
17 | contents: read
18 | security-events: write
19 |
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | language: [javascript]
24 |
25 | steps:
26 | - name: Checkout
27 | uses: actions/checkout@v4
28 |
29 | - name: Initialize CodeQL
30 | uses: github/codeql-action/init@v3
31 | with:
32 | languages: ${{ matrix.language }}
33 | queries: +security-and-quality
34 |
35 | - name: Autobuild
36 | uses: github/codeql-action/autobuild@v3
37 |
38 | - name: Perform CodeQL Analysis
39 | uses: github/codeql-action/analyze@v3
40 | with:
41 | category: /language:${{ matrix.language }}
42 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/store.js:
--------------------------------------------------------------------------------
1 | import { shallowRef } from 'vue';
2 | import { createStore } from 'vuex';
3 | import { AeSdk, Node, CompilerHttp } from '@aeternity/aepp-sdk';
4 |
5 | const store = createStore({
6 | state: {
7 | address: undefined,
8 | networkId: undefined,
9 | // AeSdk instance can't be in deep reactive https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/docs/README.md#vue3
10 | aeSdk: shallowRef(
11 | new AeSdk({
12 | nodes: [
13 | { name: 'testnet', instance: new Node('https://testnet.aeternity.io') },
14 | { name: 'mainnet', instance: new Node('https://mainnet.aeternity.io') },
15 | ],
16 | onCompiler: new CompilerHttp('https://v8.compiler.aepps.com'),
17 | }),
18 | ),
19 | },
20 | mutations: {
21 | setAddress(state, address) {
22 | state.address = address;
23 | },
24 | setNetworkId(state, networkId) {
25 | state.networkId = networkId;
26 | },
27 | },
28 | });
29 |
30 | export default store;
31 |
--------------------------------------------------------------------------------
/src/tx/builder/entry/constants.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @category contract
3 | */
4 | export enum CallReturnType {
5 | Ok = 0,
6 | Error = 1,
7 | Revert = 2,
8 | }
9 |
10 | /**
11 | * @category entry builder
12 | */
13 | export enum EntryTag {
14 | Account = 10,
15 | Oracle = 20,
16 | // OracleQuery = 21,
17 | Name = 30,
18 | // NameCommitment = 31,
19 | // NameAuction = 37,
20 | Contract = 40,
21 | ContractCall = 41,
22 | ChannelOffChainUpdateTransfer = 570,
23 | ChannelOffChainUpdateDeposit = 571,
24 | ChannelOffChainUpdateWithdraw = 572,
25 | ChannelOffChainUpdateCreateContract = 573,
26 | ChannelOffChainUpdateCallContract = 574,
27 | // ChannelOffChainUpdateMeta = 576,
28 | Channel = 58,
29 | TreesPoi = 60,
30 | // TreesDb = 61,
31 | StateTrees = 62,
32 | Mtree = 63,
33 | MtreeValue = 64,
34 | ContractsMtree = 621,
35 | CallsMtree = 622,
36 | ChannelsMtree = 623,
37 | NameserviceMtree = 624,
38 | OraclesMtree = 625,
39 | AccountsMtree = 626,
40 | // CompilerSophia = 70,
41 | GaMetaTxAuthData = 810,
42 | }
43 |
--------------------------------------------------------------------------------
/docs/hooks.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import re
3 | import urllib.request
4 |
5 | def pre_build(**kwargs):
6 | subprocess.run(['./docs/build-assets.sh'], check=True)
7 |
8 | def replacer(match):
9 | filename = f"{match.group('filename')}.{match.group('extension')}"
10 | url = f"https://raw.githubusercontent.com/{match.group('user')}/{match.group('commit')}/{filename}"
11 | code = urllib.request.urlopen(url).read().decode('utf-8')
12 | extension = 'js' if match.group('extension') == 'vue' else match.group('extension')
13 | return '\n'.join(
14 | [f'``` {extension} title="{filename}"'] +
15 | code.split('\n')[int(match.group('begin')) - 1:int(match.group('end'))] +
16 | ['```', f'View at [GitHub]({match.group(0)})']
17 | )
18 |
19 | def page_markdown(markdown, **kwargs):
20 | return re.sub(
21 | re.compile(
22 | r'^https://github.com/(?P[\w/\-]+)/blob/(?P[0-9a-f]+)/(?P[\w\d\-/\.]+)\.(?P\w+)#L(?P\d+)-L(?P\d+)$',
23 | re.MULTILINE,
24 | ),
25 | replacer,
26 | markdown,
27 | )
28 |
--------------------------------------------------------------------------------
/src/tx/builder/delegation/index.ts:
--------------------------------------------------------------------------------
1 | import { Encoded, Encoding } from '../../../utils/encoder.js';
2 | import { packRecord, unpackRecord } from '../common.js';
3 | import { DelegationTag, schemas } from './schema.js';
4 | import { DlgParams, DlgUnpacked } from './schema.generated.js';
5 |
6 | /**
7 | * Pack delegation
8 | * @category delegation signature
9 | * @param params - Params of delegation
10 | * @returns Encoded delegation
11 | */
12 | export function packDelegation(params: DlgParams): Encoded.Bytearray {
13 | return packRecord(schemas, DelegationTag, params, {}, Encoding.Bytearray);
14 | }
15 |
16 | /**
17 | * Unpack delegation
18 | * @category delegation signature
19 | * @param encoded - Encoded delegation
20 | * @param expectedTag - Expected delegation signature type
21 | * @returns Params of delegation
22 | */
23 | export function unpackDelegation(
24 | encoded: Encoded.Bytearray,
25 | expectedTag?: T,
26 | ): DlgUnpacked & { tag: T } {
27 | return unpackRecord(schemas, DelegationTag, encoded, expectedTag, {}) as any;
28 | }
29 |
--------------------------------------------------------------------------------
/test/unit/bytes.ts:
--------------------------------------------------------------------------------
1 | import '..';
2 | import { describe, it } from 'mocha';
3 | import { expect } from 'chai';
4 | import BigNumber from 'bignumber.js';
5 | import { toBytes, TypeError } from '../../src';
6 | import { snakeToPascal, pascalToSnake } from '../../src/utils/string';
7 |
8 | describe('Bytes', () => {
9 | it('toBytes: converts null to empty array', () => {
10 | expect(toBytes(null)).to.eql(Buffer.from([]));
11 | });
12 |
13 | const testCase = 'test_test-testTest';
14 |
15 | it('converts snake to pascal case', () =>
16 | expect(snakeToPascal(testCase)).to.equal('testTest-testTest'));
17 |
18 | it('converts pascal to snake case', () =>
19 | expect(pascalToSnake(testCase)).to.equal('test_test-test_test'));
20 |
21 | it('converts BigNumber to Buffer', () =>
22 | expect(toBytes(new BigNumber('1000')).readInt16BE()).to.equal(1000));
23 |
24 | it('throws error if BigNumber is not integer', () =>
25 | expect(() => toBytes(new BigNumber('1.5'))).to.throw(
26 | TypeError,
27 | /Unexpected not integer value:/,
28 | ));
29 | });
30 |
--------------------------------------------------------------------------------
/test/integration/contracts/Includes.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "namespace": { "name": "ListInternal", "typedefs": [] }
4 | },
5 | {
6 | "namespace": { "name": "List", "typedefs": [] }
7 | },
8 | {
9 | "namespace": { "name": "String", "typedefs": [] }
10 | },
11 | {
12 | "namespace": { "name": "Sublibrary", "typedefs": [] }
13 | },
14 | {
15 | "namespace": { "name": "Library", "typedefs": [] }
16 | },
17 | {
18 | "contract": {
19 | "functions": [
20 | {
21 | "arguments": [{ "name": "x", "type": "int" }],
22 | "name": "test",
23 | "payable": false,
24 | "returns": "int",
25 | "stateful": false
26 | },
27 | {
28 | "arguments": [{ "name": "x", "type": "string" }],
29 | "name": "getLength",
30 | "payable": false,
31 | "returns": "int",
32 | "stateful": false
33 | }
34 | ],
35 | "kind": "contract_main",
36 | "name": "Includes",
37 | "payable": false,
38 | "typedefs": []
39 | }
40 | }
41 | ]
42 |
--------------------------------------------------------------------------------
/examples/browser/wallet-web-extension/src/content-script.js:
--------------------------------------------------------------------------------
1 | import browser from 'webextension-polyfill';
2 | import {
3 | BrowserRuntimeConnection,
4 | BrowserWindowMessageConnection,
5 | MESSAGE_DIRECTION,
6 | connectionProxy,
7 | } from '@aeternity/aepp-sdk';
8 |
9 | (async () => {
10 | console.log('Waiting until document is ready');
11 | await new Promise((resolve) => {
12 | const interval = setInterval(() => {
13 | // TODO: ensure that there is no corresponding event
14 | if (document.readyState !== 'complete') return;
15 | clearInterval(interval);
16 | resolve();
17 | }, 100);
18 | });
19 | console.log('Document is ready');
20 |
21 | const port = browser.runtime.connect();
22 | const extConnection = new BrowserRuntimeConnection({ port });
23 | const pageConnection = new BrowserWindowMessageConnection({
24 | target: window,
25 | ...(window.origin !== 'null' && { origin: window.origin }),
26 | sendDirection: MESSAGE_DIRECTION.to_aepp,
27 | receiveDirection: MESSAGE_DIRECTION.to_waellet,
28 | });
29 | connectionProxy(pageConnection, extConnection);
30 | })();
31 |
--------------------------------------------------------------------------------
/tsconfig.src.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "emitDeclarationOnly": true,
5 | "isolatedModules": true,
6 | "outDir": "./es",
7 | "noImplicitOverride": true,
8 | "module": "es2022",
9 | "target": "es2022",
10 | "lib": ["es2022", "dom"],
11 | "moduleResolution": "node",
12 | "preserveConstEnums": true,
13 | "declaration": true,
14 | "downlevelIteration": true,
15 | "allowSyntheticDefaultImports": true,
16 | "typeRoots": ["./node_modules/@types", "./src/typings"],
17 | "strict": true,
18 | "strictFunctionTypes": false // see https://github.com/aeternity/aepp-sdk-js/issues/1793
19 | },
20 | "include": ["src/**/*"],
21 | "typedocOptions": {
22 | "entryPoints": ["src/index.ts"],
23 | "out": "./docs/api",
24 | "excludePrivate": true,
25 | "githubPages": false,
26 | "excludeExternals": true,
27 | "treatWarningsAsErrors": true,
28 | "validation": {
29 | "invalidLink": true
30 | },
31 | "plugin": ["typedoc-plugin-missing-exports"],
32 | "highlightLanguages": ["vue"],
33 | "readme": "none"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/short-u-int-const.ts:
--------------------------------------------------------------------------------
1 | import { ArgumentError } from '../../../utils/errors.js';
2 | import shortUInt from './short-u-int.js';
3 |
4 | export default function genShortUIntConstField<
5 | Value extends number,
6 | Optional extends boolean = false,
7 | >(
8 | constValue: Value,
9 | optional?: Optional,
10 | ): {
11 | serialize: Optional extends true ? (value?: Value) => Buffer : (value: Value) => Buffer;
12 | deserialize: (value: Buffer) => Value;
13 | constValue: Value;
14 | constValueOptional: boolean;
15 | } {
16 | return {
17 | serialize(value?: Value) {
18 | if ((optional !== true || value != null) && value !== constValue) {
19 | throw new ArgumentError('ShortUIntConst', constValue, value);
20 | }
21 | return shortUInt.serialize(constValue);
22 | },
23 |
24 | deserialize(buf) {
25 | const value = shortUInt.deserialize(buf);
26 | if (value !== constValue) throw new ArgumentError('ShortUIntConst', constValue, value);
27 | return constValue;
28 | },
29 |
30 | constValue,
31 |
32 | constValueOptional: optional === true,
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/tooling/autorest/middleware-prepare.js:
--------------------------------------------------------------------------------
1 | import { spawnSync } from 'child_process';
2 | // eslint-disable-next-line import/extensions
3 | import restoreFile from '../restore-file.js';
4 |
5 | const run = (getOutput, command, ...args) => {
6 | const { error, stdout, stderr, status } = spawnSync(command, args, {
7 | shell: true,
8 | ...(!getOutput && { stdio: 'inherit' }),
9 | });
10 | if (error) throw error;
11 | if (status) {
12 | if (getOutput) console.error(stderr?.toString().trimEnd());
13 | process.exit(status);
14 | }
15 | return stdout?.toString().trimEnd();
16 | };
17 |
18 | const name = './tooling/autorest/middleware-openapi.yaml';
19 | const hash =
20 | 'tScz0PvHjtFBNNF7xW8AgnGcwnilGnbXvtU5NtnA1i1cxHT04ClElshOHRo5QkX/r3IddJA4rPGF9ZMElIGamA==';
21 |
22 | await restoreFile(name, hash, () => {
23 | const version = '1.97.1';
24 | const id = run(true, 'docker', 'create', `davidyuk/temp:mdw-dev-mode-${version}-oas-fix`);
25 | const openapi = `/home/aeternity/node/lib/ae_mdw-${version}/priv/static/swagger/swagger_v3.json`;
26 | run(false, 'docker', 'cp', `${id}:${openapi}`, name);
27 | run(false, 'docker', 'rm', '-v', id);
28 | });
29 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Build and publish docs
2 | on:
3 | pull_request:
4 | push:
5 | branches: [develop]
6 | release:
7 | types: [released]
8 | jobs:
9 | main:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | with:
14 | fetch-depth: 0
15 | - uses: actions/setup-python@v5
16 | with:
17 | python-version: 3.x
18 | cache: pip
19 | - uses: actions/setup-node@v4
20 | with:
21 | node-version: 20.x
22 | cache: npm
23 | - run: pip3 install -r docs/requirements.txt
24 | - run: git config --global user.email "github-action@users.noreply.github.com"
25 | - run: git config --global user.name "GitHub Action"
26 | - if: github.event_name == 'pull_request'
27 | run: mkdocs build
28 | - if: github.event_name == 'push'
29 | run: mike deploy --push develop
30 | - if: github.event_name == 'release'
31 | run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV
32 | - if: github.event_name == 'release'
33 | run: mike deploy --push --update-aliases $RELEASE_VERSION latest
34 |
--------------------------------------------------------------------------------
/examples/browser/wallet-iframe/src/Value.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ text }}
3 |
4 |
5 |
43 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/name-fee.ts:
--------------------------------------------------------------------------------
1 | import BigNumber from 'bignumber.js';
2 | import { getMinimumNameFee } from '../helpers.js';
3 | import { InsufficientNameFeeError } from '../../../utils/errors.js';
4 | import coinAmount from './coin-amount.js';
5 | import { AensName, Int } from '../constants.js';
6 |
7 | export default {
8 | ...coinAmount,
9 |
10 | serializeAettos(_value: string | undefined, txFields: { name: AensName }): string {
11 | const minNameFee = getMinimumNameFee(txFields.name);
12 | const value = new BigNumber(_value ?? minNameFee);
13 | if (minNameFee.gt(value)) throw new InsufficientNameFeeError(value, minNameFee);
14 | return value.toFixed();
15 | },
16 |
17 | /**
18 | * @param value - AENS name fee
19 | * @param txFields - Transaction fields
20 | * @param txFields.name - AENS Name in transaction
21 | */
22 | serialize(
23 | value: Int | undefined,
24 | txFields: { name: AensName } & Parameters<(typeof coinAmount)['serialize']>[1],
25 | parameters: Parameters<(typeof coinAmount)['serialize']>[2],
26 | ): Buffer {
27 | return coinAmount.serialize.call(this, value, txFields, parameters);
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/tooling/autorest/compiler.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | directive:
3 | - from: swagger-document
4 | where: $.info.title
5 | set: compiler
6 | reason: the whole SDK already about aeternity
7 |
8 | - from: swagger-document
9 | where: $.paths.*.*.responses
10 | transform: >
11 | Object.entries($)
12 | .filter(([key]) => key !== '200')
13 | .forEach(([, value]) => value['x-ms-error-response'] = true)
14 | reason: >
15 | throw errors even for explained response with not 200 code
16 | https://github.com/Azure/autorest.typescript/issues/463#issuecomment-524203041
17 |
18 | version: ^3.7.1
19 | use-extension:
20 | '@autorest/typescript': ^6.0.39
21 | '@autorest/modelerfour': ^4.27.0
22 | # replace with a link to https://github.com/aeternity/aesophia_http/blob/master/config/swagger.yaml
23 | # at specific version after fixing https://github.com/aeternity/aesophia_http/issues/87
24 | input-file: compiler-swagger.yaml
25 | output-folder: ../../src/apis/compiler
26 | source-code-folder-path: .
27 | generator: typescript
28 | generate-metadata: false
29 | add-credentials: false
30 | modelerfour:
31 | seal-single-value-enum-by-default: false
32 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/components/Value.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ text }}
3 |
4 |
5 |
43 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/ttl.ts:
--------------------------------------------------------------------------------
1 | import shortUInt from './short-u-int.js';
2 | import Node from '../../../Node.js';
3 | import { ArgumentError } from '../../../utils/errors.js';
4 | import { _getPollInterval, getHeight } from '../../../chain.js';
5 |
6 | /**
7 | * Time to leave
8 | */
9 | export default {
10 | ...shortUInt,
11 |
12 | serialize(value: number | undefined): Buffer {
13 | return shortUInt.serialize(value ?? 0);
14 | },
15 |
16 | async prepare(
17 | value: number | undefined,
18 | params: {},
19 | // TODO: { absoluteTtl: true } | { absoluteTtl: false, onNode: Node }
20 | {
21 | onNode,
22 | absoluteTtl,
23 | _isInternalBuild,
24 | ...options
25 | }: {
26 | onNode?: Node;
27 | absoluteTtl?: boolean;
28 | _isInternalBuild?: boolean;
29 | } & Omit[1], 'onNode'>,
30 | ) {
31 | if (absoluteTtl !== true && value !== 0 && (value != null || _isInternalBuild === true)) {
32 | if (onNode == null) throw new ArgumentError('onNode', 'provided', onNode);
33 | value = (value ?? 3) + (await getHeight({ ...options, onNode, cached: true }));
34 | }
35 | return value;
36 | },
37 | };
38 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Test & build
2 | on:
3 | push:
4 | branches: [master, develop]
5 | pull_request:
6 | jobs:
7 | main:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - run: sudo apt update && sudo apt install --no-install-recommends erlang
11 | - uses: actions/checkout@v4
12 | with:
13 | fetch-depth: 100
14 | - uses: actions/setup-node@v4
15 | with:
16 | node-version: 20.x
17 | cache: npm
18 | - run: npm ci
19 | - name: Run Commitlint
20 | if: github.event_name == 'pull_request'
21 | env:
22 | HEAD: ${{ github.event.pull_request.head.sha }}
23 | BASE: ${{ github.event.pull_request.base.sha }}
24 | run: npx commitlint --from $BASE --to $HEAD --verbose
25 | - run: npm run lint
26 | - run: npm run docs:examples && npm run docs:api && ./docs/build-assets.sh
27 | if: contains(github.event.pull_request.title, 'Release')
28 | - run: docker compose up -d --wait --quiet-pull
29 | - run: npx c8 npm test
30 | - uses: codecov/codecov-action@v5
31 | env:
32 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
33 | - run: docker compose logs
34 | if: always()
35 |
--------------------------------------------------------------------------------
/src/tx/transaction-signer.ts:
--------------------------------------------------------------------------------
1 | import { Encoded } from '../utils/encoder.js';
2 | import { buildTx, getSchema, unpackTx } from './builder/index.js';
3 | import { Tag } from './builder/constants.js';
4 | import { TransactionError, UnexpectedTsError } from '../utils/errors.js';
5 |
6 | /**
7 | * Returns account address that signed a transaction
8 | * @param transaction - transaction to get a signer of
9 | * @category utils
10 | */
11 | export default function getTransactionSignerAddress(
12 | transaction: Encoded.Transaction,
13 | ): Encoded.AccountAddress {
14 | const params = unpackTx(transaction);
15 | switch (params.tag) {
16 | case Tag.SignedTx:
17 | return getTransactionSignerAddress(buildTx(params.encodedTx));
18 | case Tag.GaMetaTx:
19 | return params.gaId;
20 | default:
21 | }
22 |
23 | const nonce = getSchema(params.tag, params.version).find(([name]) => name === 'nonce')?.[1];
24 | if (nonce == null)
25 | throw new TransactionError(`Transaction doesn't have nonce: ${Tag[params.tag]}`);
26 | if (!('senderKey' in nonce)) throw new UnexpectedTsError();
27 | const address = params[nonce.senderKey as keyof typeof params] as unknown as string;
28 | return address.replace(/^ok_/, 'ak_') as Encoded.AccountAddress;
29 | }
30 |
--------------------------------------------------------------------------------
/src/account/BaseFactory.ts:
--------------------------------------------------------------------------------
1 | import Node from '../Node.js';
2 | import AccountBase from './Base.js';
3 |
4 | /**
5 | * A factory class that generates instances of AccountBase by index.
6 | * @category account
7 | */
8 | export default abstract class AccountBaseFactory {
9 | /**
10 | * Get an instance of AccountBase for a given account index.
11 | * @param accountIndex - Index of account
12 | */
13 | abstract initialize(accountIndex: number): Promise;
14 |
15 | /**
16 | * Discovers accounts in set that already have been used (has any on-chain transactions).
17 | * It returns an empty array if none of accounts been used.
18 | * If a used account is preceded by an unused account then it would be ignored.
19 | * @param node - Instance of Node to get account information from
20 | */
21 | async discover(node: Node): Promise {
22 | let index = 0;
23 | const result = [];
24 | let account;
25 | do {
26 | if (account != null) result.push(account);
27 | account = await this.initialize(index);
28 | index += 1;
29 | } while (
30 | await node.getAccountByPubkey(account.address).then(
31 | () => true,
32 | () => false,
33 | )
34 | );
35 | return result;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | node:
3 | # TODO: switch to master after merging https://github.com/aeternity/aeternity/pull/4303
4 | image: aeternity/aeternity:v7.3.0-rc5-bundle
5 | # TODO: remove 3313 port after merging https://github.com/aeternity/aeternity/pull/4303
6 | ports: [3013:3013, 3113:3113, 3014:3014, 3313:3313]
7 | # TODO: remove after releasing https://github.com/aeternity/aeternity/pull/4292
8 | healthcheck:
9 | interval: 2s
10 | volumes:
11 | - ./docker/aeternity.yaml:/home/aeternity/node/aeternity.yaml
12 | stop_grace_period: 0s
13 |
14 | emitter:
15 | build: test/emitter
16 | depends_on:
17 | node:
18 | condition: service_healthy
19 |
20 | compiler:
21 | image: aeternity/aesophia_http:v8.0.0
22 | ports: [3080:3080]
23 | # TODO: remove after releasing https://github.com/aeternity/aesophia_http/pull/133
24 | healthcheck:
25 | interval: 2s
26 |
27 | middleware:
28 | # TODO: use upstream after solving https://github.com/aeternity/ae_mdw/issues/1758
29 | image: davidyuk/temp:mdw-dev-mode-1.97.1-oas-fix
30 | ports: [4000:4000, 4001:4001, 4013:3013, 4014:3014, 4313:3313]
31 | volumes:
32 | - ./docker/middleware.yaml:/home/aeternity/aeternity.yaml
33 | stop_grace_period: 0s
34 |
--------------------------------------------------------------------------------
/src/aepp-wallet-communication/WalletConnectorFrame.ts:
--------------------------------------------------------------------------------
1 | import { Network } from './rpc/types.js';
2 | import BrowserConnection from './connection/Browser.js';
3 | import WalletConnectorFrameBase from './WalletConnectorFrameBase.js';
4 |
5 | interface EventsNetworkId {
6 | networkIdChange: (networkId: string) => void;
7 | }
8 |
9 | /**
10 | * Connect to wallet as iframe/web-extension
11 | * @category aepp wallet communication
12 | */
13 | export default class WalletConnectorFrame extends WalletConnectorFrameBase {
14 | #networkId = '';
15 |
16 | /**
17 | * The last network id reported by wallet
18 | */
19 | get networkId(): string {
20 | return this.#networkId;
21 | }
22 |
23 | protected override _updateNetwork(params: Network): void {
24 | this.#networkId = params.networkId;
25 | this.emit('networkIdChange', this.#networkId);
26 | }
27 |
28 | /**
29 | * Connect to wallet
30 | * @param name - Aepp name
31 | * @param connection - Wallet connection object
32 | */
33 | static async connect(name: string, connection: BrowserConnection): Promise {
34 | const connector = new WalletConnectorFrame();
35 | await WalletConnectorFrame._connect(name, connection, connector, false);
36 | return connector;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/components/FieldAction.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ title }}
3 |
4 |
5 |
{{ argTitle }}
6 |
7 |
8 |
9 |
10 |
{
13 | promise = actionHandler(argValue);
14 | }
15 | "
16 | >
17 | {{ actionTitle }}
18 |
19 |
20 |
{{ resultTitle }}
21 |
22 |
23 |
24 |
25 |
26 |
48 |
--------------------------------------------------------------------------------
/test/environment/node.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import {
3 | Node,
4 | AeSdk,
5 | AccountMemory,
6 | CompilerHttp,
7 | Contract,
8 | // eslint-disable-next-line import/extensions
9 | } from '../../es/index.js';
10 |
11 | const contractSourceCode = `
12 | contract Test =
13 | entrypoint getArg(x : map(string, int)) = x
14 | `;
15 | const node = new Node('https://testnet.aeternity.io');
16 | const aeSdk = new AeSdk({
17 | nodes: [{ name: 'testnet', instance: node }],
18 | accounts: [new AccountMemory('sk_2CuofqWZHrABCrM7GY95YSQn8PyFvKQadnvFnpwhjUnDCFAWmf')],
19 | onCompiler: new CompilerHttp('https://v8.compiler.aepps.com'),
20 | });
21 |
22 | console.log('Height:', await aeSdk.getHeight());
23 | console.log('Instanceof works correctly for nodes pool', aeSdk.pool instanceof Map);
24 |
25 | const contract = await Contract.initialize({
26 | ...aeSdk.getContext(),
27 | sourceCode: contractSourceCode,
28 | });
29 | const deployInfo = await contract.$deploy([]);
30 | console.log('Contract deployed at', deployInfo.address);
31 | const map = new Map([
32 | ['foo', 42],
33 | ['bar', 43],
34 | ]);
35 | const { decodedResult } = await contract.getArg(map);
36 | console.log('Call result', decodedResult);
37 | console.log('Instanceof works correctly for returned map', decodedResult instanceof Map);
38 |
--------------------------------------------------------------------------------
/examples/browser/wallet-iframe/src/styles.scss:
--------------------------------------------------------------------------------
1 | @use '~tailwindcss/dist/base';
2 | @use '~tailwindcss/dist/components';
3 | @use '~tailwindcss/dist/utilities';
4 |
5 | body {
6 | @extend .bg-gray-300;
7 |
8 | #app {
9 | @extend .p-4, .flex, .flex-col, .w-full, .min-h-screen;
10 |
11 | iframe {
12 | @extend .flex-grow, .mt-4, .bg-white, .border, .border-black, .border-dashed;
13 | }
14 | }
15 | }
16 |
17 | button {
18 | @extend .w-44, .p-2, .m-2, .rounded-full, .bg-purple-500, .text-white, .text-xs;
19 |
20 | &:disabled {
21 | @extend .bg-purple-300, .cursor-not-allowed;
22 | }
23 | }
24 |
25 | h2 {
26 | @extend .mt-2, .font-bold, .text-2xl;
27 | }
28 |
29 | input:not([type='radio']):not([type='checkbox']),
30 | textarea {
31 | @extend .bg-gray-900, .text-white, .p-2, .w-full;
32 | }
33 |
34 | .group {
35 | @extend .border, .mt-4, .rounded, .bg-gray-100, .font-mono;
36 |
37 | > div {
38 | @extend .sm\:flex;
39 |
40 | > * {
41 | @extend .p-2;
42 | }
43 |
44 | > :nth-child(1) {
45 | @extend .sm\:w-1\/4, .font-semibold, .sm\:font-normal, .self-center;
46 | }
47 |
48 | > :nth-child(2) {
49 | @extend .sm\:w-3\/4, .break-words, .whitespace-pre-wrap;
50 | }
51 | }
52 | }
53 |
54 | .error {
55 | @extend .text-purple-700;
56 | }
57 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/enumeration.ts:
--------------------------------------------------------------------------------
1 | import { ArgumentError } from '../../../utils/errors.js';
2 | import { isItemOfArray } from '../../../utils/other.js';
3 |
4 | export default function genEnumerationField(
5 | enm: Enum,
6 | ): {
7 | serialize: (value: Enum[keyof Enum]) => Buffer;
8 | deserialize: (value: Buffer) => Enum[keyof Enum];
9 | } {
10 | const values = Object.values(enm).filter((v) => typeof v === 'number');
11 | return {
12 | serialize(value) {
13 | if (typeof value !== 'number') throw new ArgumentError('value', 'to be a number', value);
14 | if (value > 0xff) throw new ArgumentError('value', 'to be less than 256', value);
15 | if (!isItemOfArray(value, values)) {
16 | throw new ArgumentError('value', 'to be a value of Enum', value);
17 | }
18 | return Buffer.from([value]);
19 | },
20 |
21 | deserialize(buffer) {
22 | if (buffer.length !== 1) {
23 | throw new ArgumentError('buffer', 'to have single element', buffer.length);
24 | }
25 | const value = buffer[0];
26 | if (!isItemOfArray(value, values)) {
27 | throw new ArgumentError('value', 'to be a value of Enum', value);
28 | }
29 | return value as Enum[keyof Enum];
30 | },
31 | };
32 | }
33 |
--------------------------------------------------------------------------------
/test/environment/node.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env npx tsx
2 | import { Node, AeSdk, AccountMemory, CompilerHttp, Contract } from '../../src';
3 |
4 | const contractSourceCode = `
5 | contract Test =
6 | entrypoint getArg(x : map(string, int)) = x
7 | `;
8 | const node = new Node('https://testnet.aeternity.io');
9 | const aeSdk = new AeSdk({
10 | nodes: [{ name: 'testnet', instance: node }],
11 | accounts: [new AccountMemory('sk_2CuofqWZHrABCrM7GY95YSQn8PyFvKQadnvFnpwhjUnDCFAWmf')],
12 | onCompiler: new CompilerHttp('https://v8.compiler.aepps.com'),
13 | });
14 |
15 | (async () => {
16 | console.log('Height:', await aeSdk.getHeight());
17 | console.log('Instanceof works correctly for nodes pool', aeSdk.pool instanceof Map);
18 |
19 | const contract = await Contract.initialize<{
20 | getArg: (x: Map) => Map;
21 | }>({ ...aeSdk.getContext(), sourceCode: contractSourceCode });
22 | const deployInfo = await contract.$deploy([]);
23 | console.log('Contract deployed at', deployInfo.address);
24 | const map = new Map([
25 | ['foo', 42],
26 | ['bar', 43],
27 | ]);
28 | const { decodedResult } = await contract.getArg(map);
29 | console.log('Call result', decodedResult);
30 | console.log('Instanceof works correctly for returned map', decodedResult instanceof Map);
31 | })();
32 |
--------------------------------------------------------------------------------
/src/utils/bytes.ts:
--------------------------------------------------------------------------------
1 | import BigNumber from 'bignumber.js';
2 | import { NoSerializerFoundError, TypeError } from './errors.js';
3 |
4 | /**
5 | * Convert string, number, or BigNumber to byte array
6 | * @param val - value to convert
7 | * @param big - enables force conversion to BigNumber
8 | * @returns Buffer
9 | * @category utils
10 | * @deprecated use `Buffer.from()` or `Buffer.from(.toString(16), 'hex')` instead
11 | */
12 | // eslint-disable-next-line import/prefer-default-export
13 | export function toBytes(val?: null | string | number | BigNumber, big = false): Buffer {
14 | // Encode a value to bytes.
15 | // If the value is an int it will be encoded as bytes big endian
16 | // Raises ValueError if the input is not an int or string
17 |
18 | if (val == null) return Buffer.from([]);
19 | if (Number.isInteger(val) || BigNumber.isBigNumber(val) || big) {
20 | if (!BigNumber.isBigNumber(val)) val = new BigNumber(val);
21 | if (!val.isInteger()) throw new TypeError(`Unexpected not integer value: ${val.toFixed()}`);
22 | let hexString = val.toString(16);
23 | if (hexString.length % 2 === 1) hexString = `0${hexString}`;
24 | return Buffer.from(hexString, 'hex');
25 | }
26 | if (typeof val === 'string') {
27 | return Buffer.from(val);
28 | }
29 | throw new NoSerializerFoundError();
30 | }
31 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/PayForTx.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
21 |
22 |
23 |
48 |
--------------------------------------------------------------------------------
/test/emitter/main.js:
--------------------------------------------------------------------------------
1 | console.log('Emitter running');
2 |
3 | async function fetchNode(port, path) {
4 | const response = await fetch(`http://node:${port}/${path}`);
5 | if (response.status !== 200) throw new Error(`Unexpected response status: ${response.status}`);
6 | try {
7 | return await response.json();
8 | } catch (error) {
9 | return null;
10 | }
11 | }
12 |
13 | let createKeyBlocks = 0;
14 |
15 | async function emitMicroBlock() {
16 | const { transactions } = await fetchNode(3113, 'v3/debug/transactions/pending');
17 | if (transactions.length === 0) return;
18 | await fetchNode(3313, 'emit_mb');
19 | createKeyBlocks = 5;
20 | }
21 |
22 | async function emitKeyBlock() {
23 | if (createKeyBlocks === 0) return;
24 | await fetchNode(3313, 'emit_kb');
25 | createKeyBlocks -= 1;
26 | }
27 |
28 | function runInInterval(cb, delay) {
29 | let timeout;
30 | const handler = async () => {
31 | await cb();
32 | timeout = setTimeout(handler, delay);
33 | };
34 | handler();
35 | return () => clearTimeout(timeout);
36 | }
37 |
38 | const cancelMicroBlock = runInInterval(emitMicroBlock, 300);
39 | const cancelKeyBlock = runInInterval(emitKeyBlock, 1000);
40 |
41 | ['SIGINT', 'SIGTERM'].forEach((event) =>
42 | process.on(event, () => {
43 | cancelMicroBlock();
44 | cancelKeyBlock();
45 | }),
46 | );
47 |
--------------------------------------------------------------------------------
/src/contract/compiler/HttpNode.ts:
--------------------------------------------------------------------------------
1 | import { readFile } from 'fs/promises';
2 | import HttpBrowser from './Http.js';
3 | import { Aci, CompileResult } from './Base.js';
4 | import { Encoded } from '../../utils/encoder.js';
5 | import getFileSystem from './getFileSystem.js';
6 |
7 | /**
8 | * Contract Compiler over HTTP for Nodejs
9 | *
10 | * Inherits CompilerHttp and implements `compile`, `validate` methods
11 | * @category contract
12 | * @example CompilerHttpNode('COMPILER_URL')
13 | */
14 | export default class CompilerHttpNode extends HttpBrowser {
15 | override async compile(path: string): CompileResult {
16 | const fileSystem = await getFileSystem(path);
17 | const sourceCode = await readFile(path, 'utf8');
18 | return this.compileBySourceCode(sourceCode, fileSystem);
19 | }
20 |
21 | override async generateAci(path: string): Promise {
22 | const fileSystem = await getFileSystem(path);
23 | const sourceCode = await readFile(path, 'utf8');
24 | return this.generateAciBySourceCode(sourceCode, fileSystem);
25 | }
26 |
27 | override async validate(bytecode: Encoded.ContractBytearray, path: string): Promise {
28 | const fileSystem = await getFileSystem(path);
29 | const sourceCode = await readFile(path, 'utf8');
30 | return this.validateBySourceCode(bytecode, sourceCode, fileSystem);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/transaction.ts:
--------------------------------------------------------------------------------
1 | import { decode, encode, Encoded, Encoding } from '../../../utils/encoder.js';
2 | import { Tag } from '../constants.js';
3 | import type { unpackTx as unpackTxType, buildTx as buildTxType } from '../index.js';
4 |
5 | export default function genTransactionField(
6 | tag?: T,
7 | ): {
8 | serialize: (
9 | // TODO: replace with `TxParams & { tag: T }`,
10 | // but fix TS2502 value is referenced directly or indirectly in its own type annotation
11 | value: any,
12 | options: { buildTx: typeof buildTxType },
13 | ) => Buffer;
14 | deserialize: (
15 | value: Buffer,
16 | options: { unpackTx: typeof unpackTxType },
17 | // TODO: replace with `TxUnpacked & { tag: T }`,
18 | // TS2577 Return type annotation circularly references itself
19 | ) => any;
20 | } {
21 | return {
22 | serialize(txParams, { buildTx }) {
23 | if (ArrayBuffer.isView(txParams)) return Buffer.from(txParams as any);
24 | if (typeof txParams === 'string' && txParams.startsWith('tx_')) {
25 | return decode(txParams as Encoded.Transaction);
26 | }
27 | return decode(buildTx({ ...txParams, ...(tag != null && { tag }) }));
28 | },
29 |
30 | deserialize(buf, { unpackTx }) {
31 | return unpackTx(encode(buf, Encoding.Transaction), tag);
32 | },
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/src/utils/wrap-proxy.ts:
--------------------------------------------------------------------------------
1 | import { ArgumentError } from './errors.js';
2 |
3 | export function wrapWithProxy(
4 | valueCb: () => Value,
5 | ): NonNullable {
6 | return new Proxy(
7 | {},
8 | Object.fromEntries(
9 | (
10 | [
11 | 'apply',
12 | 'construct',
13 | 'defineProperty',
14 | 'deleteProperty',
15 | 'getOwnPropertyDescriptor',
16 | 'getPrototypeOf',
17 | 'isExtensible',
18 | 'ownKeys',
19 | 'preventExtensions',
20 | 'set',
21 | 'setPrototypeOf',
22 | 'get',
23 | 'has',
24 | ] as const
25 | ).map((name) => [
26 | name,
27 | (t: {}, ...args: unknown[]) => {
28 | const target = valueCb();
29 | if (target == null) throw new ArgumentError('wrapped value', 'defined', target);
30 | if (name === 'get' && args[0] === '_wrappedValue') return target;
31 | const res = (Reflect[name] as any)(target, ...args);
32 | return typeof res === 'function' && name === 'get' ? res.bind(target) : res;
33 | },
34 | ]),
35 | ),
36 | ) as NonNullable;
37 | }
38 |
39 | export function unwrapProxy(value: Value): Value {
40 | return (value as { _wrappedValue?: Value })._wrappedValue ?? value;
41 | }
42 |
--------------------------------------------------------------------------------
/test/environment/node.cjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const {
3 | Node,
4 | AeSdk,
5 | AccountMemory,
6 | CompilerHttp,
7 | Contract,
8 | // eslint-disable-next-line @typescript-eslint/no-var-requires
9 | } = require('../../dist/aepp-sdk.cjs');
10 |
11 | const contractSourceCode = `
12 | contract Test =
13 | entrypoint getArg(x : map(string, int)) = x
14 | `;
15 | const node = new Node('https://testnet.aeternity.io');
16 | const aeSdk = new AeSdk({
17 | nodes: [{ name: 'testnet', instance: node }],
18 | accounts: [new AccountMemory('sk_2CuofqWZHrABCrM7GY95YSQn8PyFvKQadnvFnpwhjUnDCFAWmf')],
19 | onCompiler: new CompilerHttp('https://v8.compiler.aepps.com'),
20 | });
21 |
22 | (async () => {
23 | console.log('Height:', await aeSdk.getHeight());
24 | console.log('Instanceof works correctly for nodes pool', aeSdk.pool instanceof Map);
25 |
26 | const contract = await Contract.initialize({
27 | ...aeSdk.getContext(),
28 | sourceCode: contractSourceCode,
29 | });
30 | const deployInfo = await contract.$deploy([]);
31 | console.log('Contract deployed at', deployInfo.address);
32 | const map = new Map([
33 | ['foo', 42],
34 | ['bar', 43],
35 | ]);
36 | const { decodedResult } = await contract.getArg(map);
37 | console.log('Call result', decodedResult);
38 | console.log('Instanceof works correctly for returned map', decodedResult instanceof Map);
39 | })();
40 |
--------------------------------------------------------------------------------
/docs/build-assets.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | [ ! -d "node_modules" ] && npm i
5 | npm run docs:examples
6 | npm run docs:api
7 |
8 | # TODO: revisit --ignore-scripts after solving https://github.com/npm/cli/issues/4202
9 | perl -i -pe 's/"prepare"/"rem-prepare"/g' package.json
10 | rm -rf docs/examples/browser
11 | mkdir -p docs/examples/browser
12 |
13 | echo Build example aepp
14 | cd ./examples/browser/aepp
15 | npm i
16 | VUE_APP_WALLET_URL=../wallet-iframe/ PUBLIC_PATH=./ npm run build -- --report
17 | mv dist/ ../../../docs/examples/browser/aepp
18 |
19 | echo Build example wallet-iframe
20 | cd ../wallet-iframe
21 | npm i
22 | VUE_APP_AEPP_URL=../aepp/ PUBLIC_PATH=./ npm run build -- --report
23 | mv dist/ ../../../docs/examples/browser/wallet-iframe
24 |
25 | echo Build example wallet-web-extension
26 | cd ../wallet-web-extension
27 | npm i
28 | NODE_OPTIONS=--openssl-legacy-provider npm run build -- --report
29 | mkdir ../../../docs/examples/browser/wallet-web-extension/
30 | mv artifacts/wallet-web-extension-v0.1.0-production.zip ../../../docs/examples/browser/wallet-web-extension/packed.zip
31 | mv dist/report.html ../../../docs/examples/browser/wallet-web-extension/report.html
32 |
33 | echo Build example tools
34 | cd ../tools
35 | npm i
36 | npm run build
37 | mv dist/ ../../../docs/examples/browser/tools
38 |
39 | cd ../../..
40 | perl -i -pe 's/"rem-prepare"/"prepare"/g' package.json
41 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/styles.scss:
--------------------------------------------------------------------------------
1 | @use '~tailwindcss/dist/base';
2 | @use '~tailwindcss/dist/components';
3 | @use '~tailwindcss/dist/utilities';
4 |
5 | body {
6 | @extend .p-4;
7 | }
8 |
9 | .nav {
10 | @extend .my-4, .bg-green-400, .text-white, .text-center, .flex, .flex-wrap;
11 |
12 | a {
13 | @extend .p-2, .flex-grow;
14 |
15 | &.active {
16 | @extend .bg-green-600;
17 | }
18 | }
19 | }
20 |
21 | button {
22 | @extend .w-44, .p-2, .m-2, .rounded-full, .bg-purple-500, .text-white, .text-xs;
23 |
24 | &:disabled {
25 | @extend .bg-purple-300, .cursor-not-allowed;
26 | }
27 | }
28 |
29 | h2 {
30 | @extend .mt-2, .font-bold, .text-2xl;
31 | }
32 |
33 | input:not([type='radio']):not([type='checkbox']),
34 | textarea {
35 | @extend .bg-gray-900, .text-white, .p-2, .w-full;
36 | }
37 |
38 | textarea {
39 | @extend .h-64;
40 | }
41 |
42 | .group {
43 | @extend .border, .mt-4, .rounded, .bg-gray-100, .font-mono;
44 |
45 | > div {
46 | @extend .sm\:flex;
47 |
48 | > * {
49 | @extend .p-2;
50 | }
51 |
52 | > :nth-child(1) {
53 | @extend .sm\:w-1\/4, .font-semibold, .sm\:font-normal, .self-center;
54 | }
55 |
56 | > :nth-child(2) {
57 | @extend .sm\:w-3\/4, .break-words, .whitespace-pre-wrap;
58 | }
59 | }
60 | }
61 |
62 | .error {
63 | @extend .text-purple-700;
64 | }
65 |
66 | p {
67 | @extend .p-2;
68 | }
69 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/entry.ts:
--------------------------------------------------------------------------------
1 | import { decode, encode, Encoded, Encoding } from '../../../utils/encoder.js';
2 | import { EntryTag } from '../entry/constants.js';
3 | import type { unpackEntry as unpackEntryType, packEntry as packEntryType } from '../entry/index.js';
4 |
5 | export default function genEntryField(
6 | tag?: T,
7 | ): {
8 | serialize: (
9 | // TODO: replace with `TxParams & { tag: T }`,
10 | // but fix TS2502 value is referenced directly or indirectly in its own type annotation
11 | value: any,
12 | options: { packEntry: typeof packEntryType },
13 | ) => Buffer;
14 | deserialize: (
15 | value: Buffer,
16 | options: { unpackEntry: typeof unpackEntryType },
17 | // TODO: replace with `TxUnpacked & { tag: T }`,
18 | // TS2577 Return type annotation circularly references itself
19 | ) => any;
20 | } {
21 | return {
22 | serialize(txParams, { packEntry }) {
23 | if (ArrayBuffer.isView(txParams)) return Buffer.from(txParams as any);
24 | if (typeof txParams === 'string' && txParams.startsWith('tx_')) {
25 | return decode(txParams as Encoded.Transaction);
26 | }
27 | return decode(packEntry({ ...txParams, ...(tag != null && { tag }) }));
28 | },
29 |
30 | deserialize(buf, { unpackEntry }) {
31 | return unpackEntry(encode(buf, Encoding.Bytearray), tag);
32 | },
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/components/SpendCoins.vue:
--------------------------------------------------------------------------------
1 |
2 | Spend coins
3 |
4 |
5 |
Recipient address
6 |
7 |
8 |
9 |
10 |
11 |
Coins amount
12 |
13 |
14 |
18 |
{
21 | spendPromise = spend();
22 | }
23 | "
24 | >
25 | Spend
26 |
27 |
28 |
Spend result
29 |
30 |
31 |
32 |
33 |
34 |
57 |
--------------------------------------------------------------------------------
/examples/browser/README.md:
--------------------------------------------------------------------------------
1 | # How to connect wallet to æpp using æternity's JS SDK
2 |
3 | ## Introduction
4 |
5 | In æternity ecosystem, the app that has access to user's private keys and grants other apps
6 | access to them is called wallet. Respectively, the app that is granted access is called aepp.
7 |
8 | This folder has been created to **showcase the æternity SDK integration** to both wallets and aepps.
9 |
10 | ## Setup info
11 |
12 | If you are trying these examples after checking out this repo,
13 | you want to first run `npm install`, from the repo root, to get all the SDK dependencies installed,
14 | and only then, move to individual apps installations.
15 |
16 | ## Available examples
17 |
18 | ### 1. æpp
19 |
20 | The Sample [æpp](aepp) project (Distributed App or dapp) shows how you can create a simple æternity æpp,
21 | dependent on a Wallet, in this case: offering the possibility to work with contracts.
22 |
23 | ### 2. Wallet WebExtension
24 |
25 | The [Wallet WebExtension](wallet-web-extension) example project shows how you can create a simple
26 | æternity wallet as a Chrome/Firefox browser extension. This approach is actively used in
27 | [Superhero Wallet](https://github.com/aeternity/superhero-wallet).
28 |
29 | ### 3. iframe-based wallet
30 |
31 | The [wallet](wallet-iframe) example project shows how you can create a simple æternity wallet
32 | that opens æpps in iframe. This approach is actively used in [Base æpp](https://github.com/aeternity/aepp-base).
33 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/wrapped.ts:
--------------------------------------------------------------------------------
1 | import { EntryTag } from '../entry/constants.js';
2 | import { encode, Encoding, decode } from '../../../utils/encoder.js';
3 | import type { unpackEntry as unpackEntryType, packEntry as packEntryType } from '../entry/index.js';
4 |
5 | type TagWrapping =
6 | | EntryTag.AccountsMtree
7 | | EntryTag.CallsMtree
8 | | EntryTag.ChannelsMtree
9 | | EntryTag.ContractsMtree
10 | | EntryTag.NameserviceMtree
11 | | EntryTag.OraclesMtree;
12 |
13 | export default function genWrappedField(
14 | tag: T,
15 | ): {
16 | serialize: (
17 | // TODO: replace with `(EntParams & { tag: T })['payload']`,
18 | // but fix TS2502 value is referenced directly or indirectly in its own type annotation
19 | value: any,
20 | options: { packEntry: typeof packEntryType },
21 | ) => Buffer;
22 | deserialize: (
23 | value: Buffer,
24 | options: { unpackEntry: typeof unpackEntryType },
25 | // TODO: replace with `(EntUnpacked & { tag: T })['payload']`,
26 | // TS2577 Return type annotation circularly references itself
27 | ) => any;
28 | recursiveType: true;
29 | } {
30 | return {
31 | serialize(payload, { packEntry }) {
32 | return decode(packEntry({ tag, payload }));
33 | },
34 |
35 | deserialize(buffer, { unpackEntry }) {
36 | return unpackEntry(encode(buffer, Encoding.Bytearray), tag).payload;
37 | },
38 |
39 | recursiveType: true,
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/components/SelectNetwork.vue:
--------------------------------------------------------------------------------
1 |
2 | Select network
3 |
4 |
17 |
18 |
Payload
19 |
20 |
21 |
22 |
23 |
{
26 | promise = selectNetwork();
27 | }
28 | "
29 | >
30 | Select network
31 |
32 |
33 |
Select network result
34 |
35 |
36 |
37 |
38 |
39 |
60 |
--------------------------------------------------------------------------------
/test/unit/entry-packing.ts:
--------------------------------------------------------------------------------
1 | import { describe, it } from 'mocha';
2 | import { expect } from 'chai';
3 | import { decode, encode, Encoding, unpackEntry, EntryTag, packEntry } from '../../src';
4 |
5 | const account = {
6 | tag: EntryTag.Account,
7 | version: 1,
8 | nonce: 0,
9 | balance: '99999999999999998997',
10 | } as const;
11 | const accountEncoded = 'ba_zQoBAIkFa8deLWMP/BW+ZMQO';
12 |
13 | const poi = {
14 | tag: EntryTag.TreesPoi,
15 | accounts: [],
16 | calls: [],
17 | channels: [],
18 | contracts: [],
19 | ns: [],
20 | oracles: [],
21 | } as const;
22 | const poiEncoded = 'pi_yDwBwMDAwMDA5gE8AQ==';
23 |
24 | describe('Entry', () => {
25 | describe('packEntry', () => {
26 | it('packs', () => {
27 | expect(packEntry(account)).to.equal(accountEncoded);
28 | });
29 |
30 | it('packs poi', () => {
31 | expect(packEntry(poi)).to.equal(poiEncoded);
32 | });
33 | });
34 |
35 | describe('unpackEntry', () => {
36 | it('unpacks', () => {
37 | expect(unpackEntry(accountEncoded)).to.eql({ ...account, version: 1 });
38 | });
39 |
40 | it('unpacks poi', () => {
41 | expect(unpackEntry(poiEncoded)).to.eql({ ...poi, version: 1 });
42 | });
43 |
44 | it('fails if payload have incorrect encoding', () => {
45 | const fakePoi = encode(decode(accountEncoded), Encoding.Poi);
46 | expect(() => unpackEntry(fakePoi)).to.throw('Expected TreesPoi tag, got Account instead');
47 | });
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/test/examples.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | echo Run environment/node.cjs
5 | ./test/environment/node.cjs
6 | echo Run environment/node.js
7 | ./test/environment/node.js
8 | echo Run environment/node.ts
9 | ./test/environment/node.ts
10 | echo Run environment/node-unhandled-exception.js
11 | ./test/environment/node-unhandled-exception.js
12 | echo Run environment/name-claim-queue.js
13 | ./test/environment/name-claim-queue.js
14 |
15 | echo Check typescript
16 | cd ./test/environment/typescript/
17 | ./run.sh
18 | cd ../../..
19 |
20 | run_node_example () {
21 | echo Run $1
22 | cat ./examples/node/$1 | sed -e "s|@aeternity/aepp-sdk|./es/index.js|" | node --input-type=module
23 | }
24 |
25 | run_node_example account-generalized.js
26 | run_node_example contract-interaction.js
27 | run_node_example paying-for-contract-call-tx.js
28 | run_node_example paying-for-spend-tx.js
29 | run_node_example transfer-ae.js
30 | run_node_example dry-run-using-debug-endpoint.js
31 | run_node_example oracle.js
32 | run_node_example _api-high-level.js
33 | run_node_example _api-low-level.js
34 |
35 | # TODO: revisit --ignore-scripts after solving https://github.com/npm/cli/issues/4202
36 | perl -i -pe 's/"prepare"/"rem-prepare"/g' package.json
37 |
38 | echo Build vue-cli-4-autorest test
39 | cd ./test/environment/vue-cli-4-autorest
40 | npm i
41 | NODE_OPTIONS=--openssl-legacy-provider npm run build
42 | cd ../../..
43 |
44 | perl -i -pe 's/"rem-prepare"/"prepare"/g' package.json
45 |
46 | ./docs/build-assets.sh
47 |
--------------------------------------------------------------------------------
/docs/tutorials/vuejs/helloworld-blockheight.md:
--------------------------------------------------------------------------------
1 | # Vue.js HelloWorld
2 |
3 | This tutorial shows you how to use the SDK in your Vue.js application.
4 | You will replace the content of the default `HelloWorld` component and display the current block height of the æternity testnet.
5 |
6 | ## 1. Install Vue.js
7 |
8 | ```bash
9 | npm install -g @vue/cli
10 | ```
11 |
12 | ## 2. Create a new Vue.js project
13 |
14 | ```bash
15 | vue create my-project
16 | ```
17 |
18 | ## 3. Switch to the folder of your Vue.js project
19 |
20 | ```bash
21 | cd my-project
22 | ```
23 |
24 | ## 4. Install the SDK
25 |
26 | ```bash
27 | npm install @aeternity/aepp-sdk
28 | ```
29 |
30 | ## 5. Modify the HelloWorld component
31 |
32 | ```js
33 |
34 |
35 |
59 | ```
60 |
61 | ## 6. Run the application
62 |
63 | ```bash
64 | npm run serve
65 | ```
66 |
--------------------------------------------------------------------------------
/examples/browser/tools/public/preact.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/environment/ledger/main.js:
--------------------------------------------------------------------------------
1 | import {
2 | Node,
3 | AeSdk,
4 | CompilerHttp,
5 | AccountLedgerFactory,
6 | Contract,
7 | // eslint-disable-next-line import/extensions
8 | } from '../../../es/index.js';
9 |
10 | export default async function run(transport) {
11 | const accountFactory = new AccountLedgerFactory(transport);
12 |
13 | const account = await accountFactory.initialize(0);
14 | const { status } = await fetch(`https://faucet.aepps.com/account/${account.address}`, {
15 | method: 'POST',
16 | });
17 | console.assert([200, 425].includes(status), 'Invalid faucet response code', status);
18 |
19 | const node = new Node('https://testnet.aeternity.io');
20 | const aeSdk = new AeSdk({
21 | nodes: [{ name: 'testnet', instance: node }],
22 | accounts: [account],
23 | onCompiler: new CompilerHttp('https://v8.compiler.aepps.com'),
24 | });
25 |
26 | const { hash } = await aeSdk.spend(1e17, 'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E');
27 | console.log('Spend tx hash', hash);
28 |
29 | const contractSourceCode = `
30 | contract Test =
31 | entrypoint getArg(x : int) = x + 1
32 | `;
33 | const contract = await Contract.initialize({
34 | ...aeSdk.getContext(),
35 | sourceCode: contractSourceCode,
36 | });
37 | const deployInfo = await contract.$deploy([]);
38 | console.log('Contract deployed at', deployInfo.address);
39 |
40 | const { decodedResult } = await contract.getArg(42, { callStatic: false });
41 | console.assert(decodedResult === 43n, 'Unexpected decodedResult');
42 | console.log('Call result', decodedResult);
43 | }
44 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 | Simple æpp
3 |
4 |
5 |
6 |
28 |
29 |
30 |
31 |
32 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/docs/guides/paying-for-tx.md:
--------------------------------------------------------------------------------
1 | # PayingForTx (Meta-Transactions)
2 |
3 | ## Introduction
4 |
5 | This guide explains you how to perform a `PayingForTx` (also known as meta-transaction) using the SDK.
6 |
7 | It is a very powerful and efficient solution that is crucial for onboarding new users into you ecosystem. By making use of the `PayingForTx` you will be able to cover the fees of your users.
8 |
9 | ## How it works
10 |
11 | Typically somebody that you want to pay the transaction for (e.g. a new user of your decentralized aepp) signs the **inner transaction** (e.g. of type `ContractCallTx`) with a **specific signature** that is used for inner transactions.
12 |
13 | You can then collect the signed inner transaction, wrap it into a `PayingForTx` and broadcast it to the network.
14 |
15 | ## Usage examples
16 |
17 | We provided following two NodeJS examples which you can take a look at:
18 |
19 | - [InnerTx: ContractCallTx](https://sdk.aeternity.io/v14.1.0/examples/node/paying-for-contract-call-tx/)
20 | - [InnerTx: SpendTx](https://sdk.aeternity.io/v14.1.0/examples/node/paying-for-spend-tx/)
21 |
22 | Note:
23 |
24 | - A `PayingForTx` can wrap **any kind** of other [transaction type](https://docs.aeternity.com/developer-documentation/protocol/consensus#transactions-1) supported by the protocol as inner transaction.
25 |
26 | ## UseCases
27 |
28 | - Game developers that want to quickly onboard new users.
29 | - Governance aepps that want people to vote on important proposals without having them to pay anything.
30 | - Custodians that want to offer an additional services to cover the transaction fees of their clients.
31 | - ... many more!
32 |
--------------------------------------------------------------------------------
/src/aepp-wallet-communication/connection/Browser.ts:
--------------------------------------------------------------------------------
1 | import { AlreadyConnectedError, NoWalletConnectedError } from '../../utils/errors.js';
2 |
3 | /**
4 | * Browser connection base interface
5 | * @category aepp wallet communication
6 | */
7 | export default abstract class BrowserConnection {
8 | debug: boolean;
9 |
10 | protected constructor({ debug = false }: { debug?: boolean }) {
11 | this.debug = debug;
12 | }
13 |
14 | /**
15 | * Connect
16 | * @param onMessage - Message handler
17 | * @param onDisconnect - trigger when runtime connection in closed
18 | */
19 | connect(
20 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
21 | onMessage: (message: any, origin: string, source: any) => void,
22 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
23 | onDisconnect: () => void,
24 | ): void {
25 | if (this.isConnected()) throw new AlreadyConnectedError('You already connected');
26 | }
27 |
28 | /**
29 | * Disconnect
30 | */
31 | disconnect(): void {
32 | if (!this.isConnected())
33 | throw new NoWalletConnectedError('You dont have connection. Please connect before');
34 | }
35 |
36 | /**
37 | * Receive message
38 | */
39 | protected receiveMessage(message: any): void {
40 | if (this.debug) console.log('Receive message:', message);
41 | }
42 |
43 | /**
44 | * Send message
45 | */
46 | sendMessage(message: any): void {
47 | if (this.debug) console.log('Send message:', message);
48 | }
49 |
50 | /**
51 | * Check if connected
52 | * @returns Is connected
53 | */
54 | abstract isConnected(): boolean;
55 | }
56 |
--------------------------------------------------------------------------------
/examples/browser/wallet-web-extension/src/Popup.vue:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/aepp-wallet-communication/WalletConnectorFrameWithNode.ts:
--------------------------------------------------------------------------------
1 | import { Network } from './rpc/types.js';
2 | import { RpcConnectionError } from '../utils/errors.js';
3 | import Node from '../Node.js';
4 | import BrowserConnection from './connection/Browser.js';
5 | import WalletConnectorFrameBase from './WalletConnectorFrameBase.js';
6 |
7 | interface EventsWithNode {
8 | nodeChange: (node: Node) => void;
9 | }
10 |
11 | /**
12 | * Connect to wallet as iframe/web-extension, asks wallet to provide node url
13 | * In comparison with WalletConnectorFrame, this would work better for decentralized applications
14 | * @category aepp wallet communication
15 | */
16 | export default class WalletConnectorFrameWithNode extends WalletConnectorFrameBase {
17 | #node: Node = null as unknown as Node;
18 |
19 | /**
20 | * The node instance provided by wallet
21 | */
22 | get node(): Node {
23 | return this.#node;
24 | }
25 |
26 | protected override _updateNetwork(params: Network): void {
27 | if (params.node?.url == null) throw new RpcConnectionError('Missing URLs of the Node');
28 | this.#node = new Node(params.node.url);
29 | this.emit('nodeChange', this.#node);
30 | }
31 |
32 | /**
33 | * Connect to wallet
34 | * @param name - Aepp name
35 | * @param connection - Wallet connection object
36 | */
37 | static async connect(
38 | name: string,
39 | connection: BrowserConnection,
40 | ): Promise {
41 | const connector = new WalletConnectorFrameWithNode();
42 | await super._connect(name, connection, connector, true);
43 | return connector;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/environment/browser.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SDK test in browser
6 |
7 |
8 | Open developer console
9 |
10 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/abi-version.ts:
--------------------------------------------------------------------------------
1 | import { Tag, ConsensusProtocolVersion, AbiVersion } from '../constants.js';
2 | import { getProtocolDetails } from './ct-version.js';
3 | import Node from '../../../Node.js';
4 |
5 | export default {
6 | _getProtocolDetails(c: ConsensusProtocolVersion, tag: Tag): AbiVersion {
7 | const kind =
8 | Tag.ContractCallTx === tag || Tag.GaMetaTx === tag ? 'contract-call' : 'oracle-call';
9 | return getProtocolDetails(c, kind).abiVersion;
10 | },
11 |
12 | serialize(
13 | value: AbiVersion | undefined,
14 | { tag }: { tag: Tag },
15 | {
16 | consensusProtocolVersion = ConsensusProtocolVersion.Ceres,
17 | }: { consensusProtocolVersion?: ConsensusProtocolVersion },
18 | ): Buffer {
19 | const result = value ?? this._getProtocolDetails(consensusProtocolVersion, tag);
20 |
21 | return Buffer.from([result]);
22 | },
23 |
24 | async prepare(
25 | value: AbiVersion | undefined,
26 | { tag }: { tag: Tag },
27 | // TODO: { consensusProtocolVersion: ConsensusProtocolVersion } | { onNode: Node } | {}
28 | options: { consensusProtocolVersion?: ConsensusProtocolVersion; onNode?: Node },
29 | ): Promise {
30 | if (value != null) return value;
31 | if (options.consensusProtocolVersion != null) return undefined;
32 | if (Object.keys(ConsensusProtocolVersion).length === 2) return undefined;
33 | if (options.onNode != null) {
34 | return this._getProtocolDetails(
35 | (await options.onNode.getNodeInfo()).consensusProtocolVersion,
36 | tag,
37 | );
38 | }
39 | return undefined;
40 | },
41 |
42 | deserialize(buffer: Buffer): AbiVersion {
43 | return buffer[0];
44 | },
45 | };
46 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/gas-limit.ts:
--------------------------------------------------------------------------------
1 | import { IllegalArgumentError } from '../../../utils/errors.js';
2 | import { Tag, MAX_AUTH_FUN_GAS } from '../constants.js';
3 | import shortUInt from './short-u-int.js';
4 | import { buildGas } from './fee.js';
5 | import type { unpackTx as unpackTxType, buildTx as buildTxType } from '../index.js';
6 |
7 | function calculateGasLimitMax(
8 | gasMax: number,
9 | rebuildTx: (value: number) => any,
10 | unpackTx: typeof unpackTxType,
11 | buildTx: typeof buildTxType,
12 | ): number {
13 | return gasMax - +buildGas(rebuildTx(gasMax), unpackTx, buildTx);
14 | }
15 |
16 | export default {
17 | ...shortUInt,
18 |
19 | serialize(
20 | _value: number | undefined,
21 | {
22 | tag,
23 | rebuildTx,
24 | unpackTx,
25 | buildTx,
26 | _computingGasLimit,
27 | }: {
28 | tag: Tag;
29 | rebuildTx: (params: any) => any;
30 | unpackTx: typeof unpackTxType;
31 | buildTx: typeof buildTxType;
32 | _computingGasLimit?: number;
33 | },
34 | { gasMax = 6e6 }: { gasMax?: number },
35 | ): Buffer {
36 | if (_computingGasLimit != null) return shortUInt.serialize(_computingGasLimit);
37 |
38 | const gasLimitMax =
39 | tag === Tag.GaMetaTx
40 | ? MAX_AUTH_FUN_GAS
41 | : calculateGasLimitMax(
42 | gasMax,
43 | (gasLimit) => rebuildTx({ _computingGasLimit: gasLimit, _canIncreaseFee: true }),
44 | unpackTx,
45 | buildTx,
46 | );
47 | const value = _value ?? gasLimitMax;
48 | if (value > gasLimitMax) {
49 | throw new IllegalArgumentError(`Gas limit ${value} must be less or equal to ${gasLimitMax}`);
50 | }
51 | return shortUInt.serialize(value);
52 | },
53 | };
54 |
--------------------------------------------------------------------------------
/docs/guides/build-wallet.md:
--------------------------------------------------------------------------------
1 | # How to build a wallet
2 |
3 | This guide shows how to build either an **WebExtension Wallet** or a **iFrame-based Wallet**.
4 |
5 | ## WebExtension wallet
6 |
7 | The full implementation of this example can be found here:
8 |
9 | - [WebExtension Wallet Example](https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/examples/browser/wallet-web-extension)
10 |
11 | Note:
12 |
13 | - If you want to see a more advanced implementation you can take a look into the repository of the [Superhero Wallet](https://github.com/aeternity/superhero-wallet)
14 |
15 | ### 1. Create bridge between extension and page
16 |
17 | First you need to create a bridge between your extension and the page. This can be done as follows:
18 |
19 | https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/examples/browser/wallet-web-extension/src/content-script.js#L1-L30
20 |
21 | ### 2. Initialize `AeSdkWallet` class
22 |
23 | Then you need to initialize `AeSdkWallet` class in your extension and subscribe for new `runtime` connections.
24 | After the connection is established you can share the wallet details with the application.
25 |
26 | https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/examples/browser/wallet-web-extension/src/background.js#L1-L163
27 |
28 | ## iFrame-based Wallet
29 |
30 | The **iFrame-based** approach works similar to the **WebExtension** approach except that the `connectionProxy` in between isn't needed.
31 |
32 | You can take a look into the implementation of the following example to see how it works:
33 |
34 | - [iFrame-based Wallet Example](https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/examples/browser/wallet-iframe)
35 |
--------------------------------------------------------------------------------
/docs/guides/migration/7.md:
--------------------------------------------------------------------------------
1 | # Migration to 7.0.0
2 |
3 | This guide describes the process of migrating to SDK version 7.0.0
4 |
5 | ## Step 1
6 |
7 | SDK will not accept `url`, `internalUrl` init arguments anymore:
8 |
9 | #### Before
10 |
11 | ```js
12 | Universal({
13 | url,
14 | internalUrl,
15 | });
16 | ```
17 |
18 | #### After
19 |
20 | ```js
21 | const nodeInstance = await Node({ url, internalUrl });
22 | Universal({
23 | nodes: [{ name: 'testnet', instance: nodeInstance }],
24 | });
25 | ```
26 |
27 | ## Step 2
28 |
29 | Remove deprecated function `setKeypair`
30 | `SDK` will not accept `keypair` init argument anymore:
31 |
32 | #### Before
33 |
34 | ```js
35 | Universal({ keypair });
36 | ```
37 |
38 | #### After
39 |
40 | ```js
41 | Universal({
42 | accounts: [MemoryAccount({ keypair })],
43 | });
44 | ```
45 |
46 | ## Step 3
47 |
48 | Change all of `AENS` method's first argument from `nameId` to `name`
49 |
50 | ### Before
51 |
52 | ```js
53 | const client = Universal({ ... })
54 |
55 | await client.aensUpdate('cm_ad1wdsa...', ...)
56 | await client.aensTransfer('cm_ad1wdsa...', ...)
57 | await client.aensRevoke('cm_ad1wdsa...', ...)
58 | ```
59 |
60 | ### After
61 |
62 | ```js
63 | const client = Universal({ ... })
64 |
65 | await client.aensUpdate('testname.chain', ...)
66 | await client.aensTransfer('testname.chain', ...)
67 | await client.aensRevoke('testname.chain', ...)
68 | ```
69 |
70 | ## Other Breaking Changes
71 |
72 | - Add new compiler `methods` to RPC `communication` (base-app update required)
73 | - Drop compiler version to `version >= 4.0.0 && version < 5.0.0`
74 | - Change node compatibility range to `node >= 5.0.0 && node < 6.0.0`
75 | - Always `verify` transactions before sending them to the node (can be disabled using the option `verify: false`)
76 |
--------------------------------------------------------------------------------
/src/contract/compiler/getFileSystem.ts:
--------------------------------------------------------------------------------
1 | import { readFile } from 'fs/promises';
2 | import { dirname, resolve, basename } from 'path';
3 | import { InternalError } from '../../utils/errors.js';
4 |
5 | const defaultIncludes = [
6 | 'List.aes',
7 | 'Option.aes',
8 | 'String.aes',
9 | 'Func.aes',
10 | 'Pair.aes',
11 | 'Triple.aes',
12 | 'BLS12_381.aes',
13 | 'Frac.aes',
14 | 'Set.aes',
15 | 'Bitwise.aes',
16 | ];
17 | const includeRegExp = /^include\s*"([\w/.-]+)"/im;
18 | const includesRegExp = new RegExp(includeRegExp.source, `${includeRegExp.flags}g`);
19 |
20 | async function getFileSystemRec(root: string, relative: string): Promise> {
21 | const sourceCode = await readFile(resolve(root, relative), 'utf8');
22 | const filesystem: Record = {};
23 | await Promise.all(
24 | (sourceCode.match(includesRegExp) ?? [])
25 | .map((include) => {
26 | const m = include.match(includeRegExp);
27 | if (m?.length !== 2) throw new InternalError('Unexpected match length');
28 | return m[1];
29 | })
30 | .filter((include) => !defaultIncludes.includes(include))
31 | .map(async (include) => {
32 | const includePath = resolve(root, include);
33 | filesystem[include] = await readFile(includePath, 'utf8');
34 | Object.assign(filesystem, await getFileSystemRec(root, include));
35 | }),
36 | );
37 | return filesystem;
38 | }
39 |
40 | /**
41 | * Reads all files included in the provided contract
42 | * Available only in Node.js
43 | * @param path - a path to the main contract source code
44 | * @category contract
45 | */
46 | export default async function getFileSystem(path: string): Promise> {
47 | return getFileSystemRec(dirname(path), basename(path));
48 | }
49 |
--------------------------------------------------------------------------------
/test/unit/utils.ts:
--------------------------------------------------------------------------------
1 | import { describe, it } from 'mocha';
2 | import { expect } from 'chai';
3 | import { wrapWithProxy, unwrapProxy } from '../../src/utils/wrap-proxy';
4 | import { ArgumentError } from '../../src';
5 |
6 | describe('Utils', () => {
7 | describe('wrapWithProxy', () => {
8 | it('wraps value', () => {
9 | let t = { test: 'foo' };
10 | const wrapped = wrapWithProxy(() => t);
11 | expect(wrapped).to.not.be.equal(t);
12 | expect(wrapped.test).to.equal('foo');
13 | t.test = 'bar';
14 | expect(wrapped.test).to.equal('bar');
15 | t = { test: 'baz' };
16 | expect(wrapped.test).to.equal('baz');
17 | });
18 |
19 | it('throws error if value undefined', () => {
20 | const wrapped = wrapWithProxy<{ test: string } | undefined>(() => undefined);
21 | expect(() => wrapped.test).to.throw(
22 | ArgumentError,
23 | 'wrapped value should be defined, got undefined instead',
24 | );
25 | });
26 |
27 | it('can call private method', () => {
28 | class Entity {
29 | readonly t = 5;
30 |
31 | #bar(): number {
32 | return this.t;
33 | }
34 |
35 | foo(): number {
36 | return this.#bar();
37 | }
38 | }
39 |
40 | const entity = new Entity();
41 | const wrapped = wrapWithProxy(() => entity);
42 | expect(wrapped.foo()).to.equal(5);
43 | });
44 | });
45 |
46 | describe('unwrapProxy', () => {
47 | const t = { test: 'foo' };
48 |
49 | it('unwraps proxy to value', () => {
50 | const wrapped = wrapWithProxy(() => t);
51 | expect(unwrapProxy(wrapped)).to.equal(t);
52 | });
53 |
54 | it('does nothing if not wrapped', () => {
55 | expect(unwrapProxy(t)).to.equal(t);
56 | });
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/docs/guides/connect-aepp-to-wallet.md:
--------------------------------------------------------------------------------
1 | # Connect an æpp to a wallet
2 |
3 | This guide describes the 4 steps that are necessary to connect your application to a wallet using the RPC API.
4 |
5 | ## Prerequisites
6 |
7 | - Install [Superhero Wallet extension](https://wallet.superhero.com/) for simplicity of example.
8 | You can build your own wallet in the next example
9 |
10 | ## 1. Specify imports and constants
11 |
12 |
13 |
14 | https://github.com/aeternity/aepp-sdk-js/blob/f60d1b8a1124b32781769342e4941c8dacf6ad53/examples/browser/aepp/src/StoreAeSdkPlugin.js#L1-L5
15 |
16 | ## 2. Initialize the `AeSdkAepp` class
17 |
18 | https://github.com/aeternity/aepp-sdk-js/blob/f60d1b8a1124b32781769342e4941c8dacf6ad53/examples/browser/aepp/src/StoreAeSdkPlugin.js#L34-L49
19 |
20 | ## 3. Scan for wallets and connect to a wallet
21 |
22 | https://github.com/aeternity/aepp-sdk-js/blob/f60d1b8a1124b32781769342e4941c8dacf6ad53/examples/browser/aepp/src/Connect.vue#L66-L85
23 |
24 | Alternatively, aepp can request wallet to share node url it connected to. If agreed, then aepp can
25 | connect to the wallet's node.
26 |
27 | ```js
28 | await this.aeSdk.connectToWallet(wallet.getConnection(), {
29 | connectNode: true,
30 | name: 'wallet-node',
31 | select: true,
32 | });
33 | ```
34 |
35 | It can be used to
36 |
37 | - improve responsiveness by connecting to the exact node that wallet uses;
38 | - allow to connect aepps to private/development networks without changing their configuration;
39 | - simplify configuration on aepp side.
40 |
41 | Note:
42 |
43 | - The steps above are snippets taken from the full implementation of
44 | the [Simple æpp](https://github.com/aeternity/aepp-sdk-js/blob/f60d1b8a1124b32781769342e4941c8dacf6ad53/examples/browser/aepp)
45 |
--------------------------------------------------------------------------------
/test/environment/name-claim-queue.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import {
3 | Node,
4 | AeSdk,
5 | AccountMemory,
6 | Name,
7 | // eslint-disable-next-line import/extensions
8 | } from '../../es/index.js';
9 |
10 | const aeSdk = new AeSdk({
11 | nodes: [{ name: 'testnet', instance: new Node('https://testnet.aeternity.io') }],
12 | accounts: [AccountMemory.generate()],
13 | });
14 |
15 | const { address } = aeSdk;
16 | const { status } = await fetch(`https://faucet.aepps.com/account/${address}`, { method: 'POST' });
17 | console.assert(status === 200, 'Invalid faucet response code', status);
18 |
19 | const pauseUntilLoadBalancerGetSynced = () =>
20 | new Promise((resolve) => {
21 | setTimeout(resolve, 1000);
22 | });
23 | const name = new Name(`test-${Math.random().toString(16).slice(2)}.chain`, aeSdk.getContext());
24 | const options = { waitMined: false };
25 | const txHashes = [];
26 |
27 | const preclaim = await name.preclaim(options);
28 | txHashes.push(preclaim.hash);
29 |
30 | await pauseUntilLoadBalancerGetSynced();
31 | const claim = await name.claim(options);
32 | txHashes.push(claim.hash);
33 |
34 | let res;
35 |
36 | await pauseUntilLoadBalancerGetSynced();
37 | res = await aeSdk.spend(0.5e18, 'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E', options);
38 | txHashes.push(res.hash);
39 |
40 | await pauseUntilLoadBalancerGetSynced();
41 | res = await aeSdk.spend(0.5e18, 'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E', options);
42 | txHashes.push(res.hash);
43 |
44 | await pauseUntilLoadBalancerGetSynced();
45 | res = await aeSdk.spend(0.5e18, 'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E', options);
46 | txHashes.push(res.hash);
47 |
48 | console.log('All transactions submitted');
49 |
50 | await Promise.all(txHashes.map((hash) => aeSdk.poll(hash)));
51 | console.log('All transactions mined');
52 |
--------------------------------------------------------------------------------
/examples/node/oracle.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /*
3 | # Register and query Oracle
4 |
5 | Here we will register and query an oracle returning [factorial] of a number.
6 | Read more about oracles in the [guide] section.
7 |
8 | [factorial]: https://en.wikipedia.org/wiki/Factorial
9 | [guide]: ../../guides/oracles.md
10 | */
11 | import { Node, AeSdk, AccountMemory, Oracle, OracleClient } from '@aeternity/aepp-sdk';
12 |
13 | // Let's prepare sdk and account for Oracle
14 | const node = new Node('https://testnet.aeternity.io');
15 | const oracleAccount = AccountMemory.generate();
16 | const aeSdk = new AeSdk({
17 | nodes: [{ name: 'testnet', instance: node }],
18 | accounts: [new AccountMemory('sk_2CuofqWZHrABCrM7GY95YSQn8PyFvKQadnvFnpwhjUnDCFAWmf')],
19 | });
20 | await aeSdk.spend(2e14, oracleAccount.address);
21 | console.log('Spend done');
22 |
23 | // Creating and registering Oracle
24 | const oracle = new Oracle(oracleAccount, aeSdk.getContext());
25 | await oracle.register('factorial argument', 'factorial value');
26 | console.log('Oracle registered');
27 |
28 | // Start listening for queries and handle them
29 | const stop = oracle.handleQueries((query) => {
30 | const arg = BigInt(query.decodedQuery);
31 | if (arg < 0) return "argument can't be negative";
32 | let res = 1n;
33 | for (let i = 2n; i <= arg; i += 1n) {
34 | res *= i;
35 | }
36 | return res.toString();
37 | });
38 |
39 | // Creating an Oracle client, making some queries.
40 | // Assume it is done in a separate script/process/computer.
41 | const oracleClient = new OracleClient(oracle.address, aeSdk.getContext());
42 | for (const el of [1, 4, 20, 70, -5]) {
43 | const response = await oracleClient.query(el.toString());
44 | console.log(`query ${el}, response ${response}`);
45 | }
46 |
47 | // We don't need to handle queries anymore, so we can stop the listener.
48 | stop();
49 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/Basic.vue:
--------------------------------------------------------------------------------
1 |
2 | General information
3 |
4 |
5 |
Address
6 |
{{ address }}
7 |
8 |
12 |
16 |
20 |
21 |
Compiler version
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
68 |
--------------------------------------------------------------------------------
/examples/browser/tools/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --font-sans:
3 | ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue,
4 | Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol,
5 | Noto Color Emoji;
6 | --font-mono:
7 | ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
8 | }
9 |
10 | body {
11 | margin: 0;
12 | padding: 1rem;
13 | font-family: var(--font-sans);
14 | }
15 |
16 | button {
17 | width: 10rem;
18 | margin: 0 0.5rem 0.5rem 0;
19 | padding: 0.5rem;
20 | border: none;
21 | border-radius: 9999px;
22 | background-color: #8b5cf6;
23 | color: #fff;
24 | font-size: 0.75rem;
25 | line-height: 1rem;
26 | font-family: var(--font-mono);
27 |
28 | &:disabled {
29 | cursor: not-allowed;
30 | background-color: #c4b5fd;
31 | }
32 | }
33 |
34 | h2 {
35 | margin: 0;
36 | font-weight: bold;
37 | font-size: 1.5rem;
38 | }
39 |
40 | select,
41 | input,
42 | textarea {
43 | background: #111827;
44 | color: #fff;
45 | padding: 0.5rem;
46 | border: none;
47 | font-size: 100%;
48 | line-height: 1.5;
49 | font-family: var(--font-mono);
50 | }
51 |
52 | textarea {
53 | height: calc(1.5rem * 4);
54 | resize: vertical;
55 | }
56 |
57 | .group {
58 | margin: 1rem 0;
59 | border: 1px solid #e5e7eb;
60 | border-radius: 0.25rem;
61 | background: #f3f4f6;
62 | font-family: var(--font-mono);
63 | display: grid;
64 | grid-template-columns: 1fr 3fr;
65 | align-items: center;
66 | padding: 0.5rem;
67 | grid-gap: 0.5rem;
68 |
69 | @media (max-width: 640px) {
70 | grid-template-columns: 1fr;
71 |
72 | > :nth-child(2n - 1):not(button) {
73 | font-weight: 600;
74 | }
75 | }
76 |
77 | > * {
78 | overflow: hidden;
79 | overflow-wrap: break-word;
80 | }
81 | }
82 |
83 | .error,
84 | .separator {
85 | color: #6d28d9;
86 | }
87 |
--------------------------------------------------------------------------------
/examples/browser/aepp/public/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/examples/browser/tools/src/components/DataEncoder.tsx:
--------------------------------------------------------------------------------
1 | import { Buffer } from 'buffer';
2 | import { useState } from 'preact/hooks';
3 | import { decode, encode, Encoded, Encoding } from '@aeternity/aepp-sdk';
4 |
5 | export function DataEncoder() {
6 | const [input, setInput] = useState('');
7 | const [encoding, setEncoding] = useState(Encoding.AccountAddress);
8 | const [error, setError] = useState('');
9 |
10 | const isInputEncoded = input[2] === '_';
11 | let output;
12 | try {
13 | if (isInputEncoded) {
14 | output = decode(input as Encoded.Any).toString('hex');
15 | setEncoding(input.substring(0, 2) as Encoding);
16 | } else {
17 | output = encode(Buffer.from(input, 'hex'), encoding);
18 | }
19 | setError(input === '' ? 'No data' : '');
20 | } catch (error) {
21 | setError(String(error));
22 | }
23 |
24 | return (
25 | <>
26 | Encode or decode prefixed data
27 |
60 | >
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/src/aepp-wallet-communication/connection/BrowserRuntime.ts:
--------------------------------------------------------------------------------
1 | import { Runtime } from 'webextension-polyfill';
2 | import BrowserConnection from './Browser.js';
3 | import { UnexpectedTsError } from '../../utils/errors.js';
4 |
5 | /**
6 | * BrowserRuntimeConnection
7 | * Handle browser runtime communication
8 | * @category aepp wallet communication
9 | */
10 | export default class BrowserRuntimeConnection extends BrowserConnection {
11 | port: Runtime.Port;
12 |
13 | #listeners?: [(message: any, port: Runtime.Port) => void, () => void];
14 |
15 | /**
16 | * @param options - Options
17 | */
18 | constructor({ port, ...options }: { port: Runtime.Port; debug?: boolean }) {
19 | super(options);
20 | this.port = port;
21 | }
22 |
23 | override disconnect(): void {
24 | super.disconnect();
25 | this.port.disconnect();
26 | if (this.#listeners == null) throw new UnexpectedTsError();
27 | this.port.onMessage.removeListener(this.#listeners[0]);
28 | this.port.onDisconnect.removeListener(this.#listeners[1]);
29 | this.#listeners = undefined;
30 | }
31 |
32 | override connect(
33 | onMessage: (message: any, origin: string, source: Runtime.Port) => void,
34 | onDisconnect: () => void,
35 | ): void {
36 | super.connect(onMessage, onDisconnect);
37 | this.#listeners = [
38 | (message, port) => {
39 | this.receiveMessage(message);
40 | // TODO: make `origin` optional because sender url is not available on aepp side
41 | onMessage(message, port.sender?.url ?? '', port);
42 | },
43 | onDisconnect,
44 | ];
45 | this.port.onMessage.addListener(this.#listeners[0]);
46 | this.port.onDisconnect.addListener(this.#listeners[1]);
47 | }
48 |
49 | override sendMessage(message: any): void {
50 | super.sendMessage(message);
51 | this.port.postMessage(message);
52 | }
53 |
54 | isConnected(): boolean {
55 | return this.#listeners != null;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/test/unit/pretty-numbers.ts:
--------------------------------------------------------------------------------
1 | import BigNumber from 'bignumber.js';
2 | import { describe, it } from 'mocha';
3 | import { expect } from 'chai';
4 | import { prefixedAmount } from '../../src';
5 |
6 | const MAGNITUDE = 18;
7 | describe('prefixedAmount', () => {
8 | it('removes trailing zeros', () => {
9 | expect(prefixedAmount(new BigNumber('1.0000'))).to.equal('1');
10 | });
11 |
12 | it('displays fees', () => {
13 | expect(prefixedAmount(new BigNumber(17120).shiftedBy(-MAGNITUDE))).to.equal('0.01712 pico');
14 | });
15 | it('displays balance', () => {
16 | expect(prefixedAmount(new BigNumber('89.99999999000924699'))).to.equal('90');
17 | });
18 |
19 | it('generates proper values', () => {
20 | const t = new BigNumber(`0.${'123456789'.repeat(3)}`).shiftedBy(-MAGNITUDE);
21 | [
22 | '0.00000012 pico',
23 | '0.00000123 pico',
24 | '0.00001235 pico',
25 | '0.00012346 pico',
26 | '0.00123457 pico',
27 | '0.01234568 pico',
28 | '0.12345679 pico',
29 | '1.23456789 pico',
30 | '12.3456789 pico',
31 | '123.456789 pico',
32 | '1234.56789 pico',
33 | '12345.6789 pico',
34 | '123456.789 pico',
35 | '0.00000123',
36 | '0.00001235',
37 | '0.00012346',
38 | '0.00123457',
39 | '0.01234568',
40 | '0.12345679',
41 | '1.23456789',
42 | '12.3456789',
43 | '123.456789',
44 | '1234.56789',
45 | '12345.6789',
46 | '123456.789',
47 | '1234567.89',
48 | '12345678.9',
49 | '123456789',
50 | '1.23456789 giga',
51 | '12.3456789 giga',
52 | '123.456789 giga',
53 | '1234.56789 giga',
54 | '12345.6789 giga',
55 | '123456.789 giga',
56 | '1234567.89 giga',
57 | '12345678.9 giga',
58 | '123456789 giga',
59 | '1.23456789 exa',
60 | '12.3456789 exa',
61 | ].forEach((res, idx) => expect(prefixedAmount(t.shiftedBy(idx))).to.equal(res));
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/examples/browser/tools/src/components/ConvertSk.tsx:
--------------------------------------------------------------------------------
1 | import { Buffer } from 'buffer';
2 | import { useState } from 'preact/hooks';
3 | import { isEncoded, Encoding, Encoded, decode, encode, AccountMemory } from '@aeternity/aepp-sdk';
4 |
5 | function parseSecretKey(secretKey: string): Encoded.AccountSecretKey | undefined {
6 | if (isEncoded(secretKey, Encoding.AccountSecretKey)) return secretKey;
7 | const buffer = Buffer.from(secretKey, 'hex');
8 | if (buffer.length === 64) return encode(buffer.subarray(0, 32), Encoding.AccountSecretKey);
9 | }
10 |
11 | export function ConvertSk() {
12 | const [secretKeyRaw, setSecretKey] = useState('');
13 |
14 | const secretKey = parseSecretKey(secretKeyRaw);
15 | let address;
16 | let secretKeyOtherFormat;
17 | let secretKeyOtherValue;
18 | if (secretKey) {
19 | address = new AccountMemory(secretKey).address;
20 | [secretKeyOtherFormat, secretKeyOtherValue] = isEncoded(secretKeyRaw, Encoding.AccountSecretKey)
21 | ? ['Secret key in hex', Buffer.concat([decode(secretKey), decode(address)]).toString('hex')]
22 | : ['sk_-prefixed secret key', secretKey];
23 | }
24 |
25 | return (
26 | <>
27 | Convert between hex secret key and sk_-prefixed
28 |
29 |
Secret key in any format
30 |
setSecretKey(event.currentTarget.value)}
34 | />
35 |
36 |
Address
37 |
{address || "Can't parse secret key"}
38 |
39 |
40 |
setSecretKey(AccountMemory.generate().secretKey)}>
41 | Generate account
42 |
43 |
44 | {secretKeyOtherFormat && (
45 | <>
46 |
{secretKeyOtherFormat}
47 |
{secretKeyOtherValue}
48 | >
49 | )}
50 |
51 | >
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/docs/quick-start.md:
--------------------------------------------------------------------------------
1 | # Quick Start
2 |
3 | In this example we will send 1 _AE_ coin from one account to another.
4 | For more specific information on setups with Frameworks and TypeScript, please refer to the [installation instructions](./README.md).
5 |
6 | ## 1. Specify imports
7 |
8 | For the following snippets in the guide you need to specify multiple imports.
9 |
10 | ```js
11 | const { AeSdk, AccountMemory, Node } = require('@aeternity/aepp-sdk');
12 | ```
13 |
14 | ## 2. Create a sender account
15 |
16 | ```js
17 | const sender = AccountMemory.generate();
18 | console.log('Sender address:', sender.address);
19 | console.log('Sender secret key:', sender.secretKey);
20 | ```
21 |
22 | ## 3. Get some _AE_ using the Faucet
23 |
24 | To receive some _AE_ you can use the [Faucet](https://faucet.aepps.com/). Just paste sender's address, hit `Top UP` and you'll immediately get some test coins.
25 |
26 | ## 4. Interact with the æternity blockchain
27 |
28 | This example shows:
29 |
30 | - how to create an instance of the SDK using the `AeSdk` class
31 | - how to spend (send) 1 _AE_ from the account the SDK instance was initialized with to some other AE address
32 |
33 | ```js
34 | const NODE_URL = 'https://testnet.aeternity.io';
35 | // replace with the generated secretKey from step 2
36 | const sender = new AccountMemory('');
37 |
38 | (async function () {
39 | const node = new Node(NODE_URL);
40 | const aeSdk = new AeSdk({
41 | nodes: [{ name: 'testnet', instance: node }],
42 | accounts: [sender],
43 | });
44 |
45 | // spend one AE
46 | await aeSdk.spend(1e18, '');
47 | // replace , Ideally you use address from Superhero Wallet you have created before
48 | })();
49 | ```
50 |
51 | Note:
52 |
53 | - You may remove code from Step 2 as this serves only for one-time creation
54 | - The `spend` function expects the amount to be spent in `aettos` (the smallest possible unit, 1 _AE_ equal to 1 000 000 000 000 000 000 `aettos`)
55 | - See [Testnet Explorer](https://testnet.aescan.io/) and track your transactions
56 |
--------------------------------------------------------------------------------
/test/integration/AeSdk.ts:
--------------------------------------------------------------------------------
1 | import { describe, it } from 'mocha';
2 | import { expect } from 'chai';
3 | import { getSdk } from '.';
4 | import { Node } from '../../src';
5 |
6 | describe('AeSdk', () => {
7 | describe('_getPollInterval', () => {
8 | it('returns value based on options', async () => {
9 | const aeSdk = await getSdk(0);
10 | aeSdk._options._expectedMineRate = 1000;
11 | aeSdk._options._microBlockCycle = 300;
12 | expect(await aeSdk._getPollInterval('key-block')).to.equal(333);
13 | expect(await aeSdk._getPollInterval('micro-block')).to.equal(100);
14 | });
15 |
16 | (
17 | [
18 | [
19 | 'devnet',
20 | 0,
21 | 0,
22 | (node: Node) => (node.getNetworkId = async () => Promise.resolve('ae_dev')),
23 | ],
24 | [
25 | 'hyperchains',
26 | 1000,
27 | 1000,
28 | (node: Node) => {
29 | node.getNetworkId = async () => Promise.resolve('ae_random');
30 | node._isHyperchain = async () => Promise.resolve(true);
31 | },
32 | ],
33 | [
34 | 'mainnet',
35 | 60000,
36 | 1000,
37 | (node: Node) => {
38 | node.getNetworkId = async () => Promise.resolve('ae_mainnet');
39 | node._isHyperchain = () => {
40 | throw new Error("Shouldn't be called");
41 | };
42 | },
43 | ],
44 | [
45 | 'default case',
46 | 60000,
47 | 1000,
48 | (node: Node) => (node.getNetworkId = async () => Promise.resolve('ae_random')),
49 | ],
50 | ] as const
51 | ).forEach(([name, kb, mb, cb]) =>
52 | it(`handles ${name}`, async () => {
53 | const aeSdk = await getSdk(0);
54 | cb(aeSdk.api);
55 | delete aeSdk._options._expectedMineRate;
56 | delete aeSdk._options._microBlockCycle;
57 | expect(await aeSdk._getPollInterval('key-block')).to.equal(kb);
58 | expect(await aeSdk._getPollInterval('micro-block')).to.equal(mb);
59 | }),
60 | );
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/components/GenerateSpendTx.vue:
--------------------------------------------------------------------------------
1 |
2 | Generate spend transaction
3 |
4 |
5 |
Recipient address
6 |
7 |
8 |
9 |
10 |
11 |
Coins amount
12 |
13 |
14 |
18 |
19 |
Increment nonce by 1
20 |
21 |
22 | (only if you want to pay for this transaction yourself)
23 |
24 |
25 |
{
28 | generatePromise = generate();
29 | }
30 | "
31 | >
32 | Generate
33 |
34 |
35 |
Spend transaction
36 |
37 |
38 |
39 |
40 |
41 |
75 |
--------------------------------------------------------------------------------
/src/tx/builder/entry/index.ts:
--------------------------------------------------------------------------------
1 | import { Encoded, Encoding } from '../../../utils/encoder.js';
2 | import { packRecord, unpackRecord } from '../common.js';
3 | import { schemas } from './schema.js';
4 | import { EntryTag } from './constants.js';
5 | import { EntParams, EntUnpacked } from './schema.generated.js';
6 |
7 | const encodingTag = [
8 | [EntryTag.CallsMtree, Encoding.CallStateTree],
9 | [EntryTag.StateTrees, Encoding.StateTrees],
10 | [EntryTag.TreesPoi, Encoding.Poi],
11 | ] as const;
12 |
13 | export function packEntry(params: EntParams & { tag: EntryTag.CallsMtree }): Encoded.CallStateTree;
14 | export function packEntry(params: EntParams & { tag: EntryTag.StateTrees }): Encoded.StateTrees;
15 | export function packEntry(params: EntParams & { tag: EntryTag.TreesPoi }): Encoded.Poi;
16 | /**
17 | * Pack entry
18 | * @category entry builder
19 | * @param params - Params of entry
20 | * @returns Encoded entry
21 | */
22 | export function packEntry(params: EntParams): Encoded.Any;
23 | export function packEntry(params: EntParams): Encoded.Any {
24 | const encoding = encodingTag.find(([tag]) => tag === params.tag)?.[1] ?? Encoding.Bytearray;
25 | return packRecord(schemas, EntryTag, params, { packEntry }, encoding);
26 | }
27 |
28 | export function unpackEntry(
29 | encoded: Encoded.CallStateTree,
30 | ): EntUnpacked & { tag: EntryTag.CallsMtree };
31 | export function unpackEntry(
32 | encoded: Encoded.StateTrees,
33 | ): EntUnpacked & { tag: EntryTag.StateTrees };
34 | export function unpackEntry(encoded: Encoded.Poi): EntUnpacked & { tag: EntryTag.TreesPoi };
35 | /**
36 | * Unpack entry
37 | * @category entry builder
38 | * @param encoded - Encoded entry
39 | * @param expectedTag - Expected entry type
40 | * @returns Params of entry
41 | */
42 | export function unpackEntry(
43 | encoded: Encoded.Any,
44 | expectedTag?: T,
45 | ): EntUnpacked & { tag: T };
46 | export function unpackEntry(encoded: Encoded.Any, expectedTag?: EntryTag): EntUnpacked {
47 | expectedTag ??= encodingTag.find(([, enc]) => encoded.startsWith(enc))?.[0];
48 | return unpackRecord(schemas, EntryTag, encoded, expectedTag, { unpackEntry }) as any;
49 | }
50 |
--------------------------------------------------------------------------------
/src/utils/MiddlewarePage.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-classes-per-file */
2 | import type Middleware from '../Middleware.js';
3 | import { BaseError } from './errors.js';
4 |
5 | interface MiddlewareRawPage {
6 | data: T[];
7 | next: string | null;
8 | prev: string | null;
9 | }
10 |
11 | export function isMiddlewareRawPage(maybePage: unknown): maybePage is MiddlewareRawPage {
12 | const testPage = maybePage as MiddlewareRawPage;
13 | return (
14 | testPage?.data != null &&
15 | Array.isArray(testPage.data) &&
16 | 'next' in testPage &&
17 | 'prev' in testPage
18 | );
19 | }
20 |
21 | /**
22 | * @category exception
23 | */
24 | export class MiddlewarePageMissed extends BaseError {
25 | constructor(isNext: boolean) {
26 | super(`There is no ${isNext ? 'next' : 'previous'} page`);
27 | this.name = 'MiddlewarePageMissed';
28 | }
29 | }
30 |
31 | /**
32 | * A wrapper around the middleware's page allowing to get the next/previous pages.
33 | */
34 | export class MiddlewarePage- {
35 | readonly data: Item[];
36 |
37 | readonly nextPath: null | string;
38 |
39 | readonly prevPath: null | string;
40 |
41 | readonly #middleware: Middleware;
42 |
43 | constructor(rawPage: MiddlewareRawPage
- , middleware: Middleware) {
44 | this.data = rawPage.data;
45 | this.nextPath = rawPage.next;
46 | this.prevPath = rawPage.prev;
47 | this.#middleware = middleware;
48 | }
49 |
50 | /**
51 | * Get the next page.
52 | * Check the presence of `nextPath` to not fall outside existing pages.
53 | * @throws MiddlewarePageMissed
54 | */
55 | async next(): Promise
> {
56 | if (this.nextPath == null) throw new MiddlewarePageMissed(true);
57 | return this.#middleware.requestByPath(this.nextPath);
58 | }
59 |
60 | /**
61 | * Get the previous page.
62 | * Check the presence of `prevPath` to not fall outside existing pages.
63 | * @throws MiddlewarePageMissed
64 | */
65 | async prev(): Promise> {
66 | if (this.prevPath == null) throw new MiddlewarePageMissed(false);
67 | return this.#middleware.requestByPath(this.prevPath);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/map.ts:
--------------------------------------------------------------------------------
1 | import { EntryTag } from '../entry/constants.js';
2 | import { encode, Encoding, Encoded, decode } from '../../../utils/encoder.js';
3 | import type { unpackEntry as unpackEntryType, packEntry as packEntryType } from '../entry/index.js';
4 |
5 | export default function genMapField(
6 | encoding: E,
7 | tag: T,
8 | ): {
9 | serialize: (
10 | // TODO: replace with `TxParams & { tag: T }`,
11 | // but fix TS2502 value is referenced directly or indirectly in its own type annotation
12 | value: Record, any>,
13 | options: { packEntry: typeof packEntryType },
14 | ) => Buffer;
15 | deserialize: (
16 | value: Buffer,
17 | options: { unpackEntry: typeof unpackEntryType },
18 | // TODO: replace with `TxUnpacked & { tag: T }`,
19 | // TS2577 Return type annotation circularly references itself
20 | ) => Record, any>;
21 | recursiveType: true;
22 | } {
23 | return {
24 | serialize(object, { packEntry }) {
25 | return decode(
26 | packEntry({
27 | tag: EntryTag.Mtree,
28 | values: Object.entries(object).map(
29 | ([key, value]) =>
30 | ({
31 | tag: EntryTag.MtreeValue,
32 | key: decode(key as Encoded.Generic),
33 | value: decode(packEntry({ ...(value as any), tag })),
34 | }) as const,
35 | ),
36 | }),
37 | );
38 | },
39 |
40 | deserialize(buffer, { unpackEntry }) {
41 | const { values } = unpackEntry(encode(buffer, Encoding.Bytearray), EntryTag.Mtree);
42 | return Object.fromEntries(
43 | values
44 | // TODO: remove after resolving https://github.com/aeternity/aeternity/issues/4066
45 | .filter(({ key }) => encoding !== Encoding.ContractAddress || key.length === 32)
46 | .map(({ key, value }) => [
47 | encode(key, encoding),
48 | unpackEntry(encode(value, Encoding.Bytearray), tag),
49 | ]),
50 | ) as Record, any>;
51 | },
52 |
53 | recursiveType: true,
54 | };
55 | }
56 |
--------------------------------------------------------------------------------
/examples/browser/tools/src/components/AccountsByMnemonic.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'preact/hooks';
2 | import { AccountMnemonicFactory, AccountMemory } from '@aeternity/aepp-sdk';
3 | import { validateMnemonic, generateMnemonic } from '@scure/bip39';
4 | import { wordlist } from '@scure/bip39/wordlists/english';
5 |
6 | export function AccountsByMnemonic() {
7 | const [mnemonic, setMnemonic] = useState('');
8 | const [count, setCount] = useState(1);
9 | const [accounts, setAccounts] = useState([]);
10 |
11 | useEffect(() => {
12 | setAccounts([]);
13 | const factory = new AccountMnemonicFactory(mnemonic);
14 | (async () => {
15 | try {
16 | setAccounts(
17 | await Promise.all(new Array(count).fill(0).map((_, idx) => factory.initialize(idx))),
18 | );
19 | } catch (error) {}
20 | })();
21 | }, [mnemonic, count]);
22 |
23 | let validation = 'invalid';
24 | if (accounts.length) {
25 | validation = (validateMnemonic(mnemonic, wordlist) ? '' : 'not ') + 'in english wordlist';
26 | }
27 |
28 | return (
29 | <>
30 | Generate accounts by mnemonic phrase
31 |
32 |
Mnemonic phrase
33 |
setMnemonic(event.currentTarget.value)}
37 | />
38 |
39 |
40 |
Mnemonic {validation}
41 |
42 |
43 |
44 | setCount((count) => count + 1)}>
45 | Add account
46 |
47 | setMnemonic(generateMnemonic(wordlist))}>Generate mnemonic
48 |
49 |
50 | {accounts.map(({ address, secretKey }, idx) => (
51 | <>
52 |
Account #{idx}
53 |
54 | {address}
55 |
56 | {secretKey}
57 |
58 | >
59 | ))}
60 |
61 | >
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/nonce.ts:
--------------------------------------------------------------------------------
1 | import { isAccountNotFoundError } from '../../../utils/other.js';
2 | import shortUInt from './short-u-int.js';
3 | import Node from '../../../Node.js';
4 | import { ArgumentError } from '../../../utils/errors.js';
5 | import { Enum1 as NextNonceStrategy } from '../../../apis/node/index.js';
6 | import { Tag } from '../constants.js';
7 |
8 | export default function genNonceField(
9 | senderKey: SenderKey,
10 | ): {
11 | serialize: (value: number, params: { tag: Tag }) => Buffer;
12 | // TODO: (value: number) => Promise | (value: undefined, ...) => Promise
13 | prepare: (
14 | value: number | undefined,
15 | params: {},
16 | // TODO: replace `string` with AddressEncodings
17 | options: { [key in SenderKey]: string } & {
18 | strategy?: NextNonceStrategy;
19 | onNode?: Node;
20 | _isInternalBuild?: boolean;
21 | },
22 | ) => Promise;
23 | deserialize: (value: Buffer) => number;
24 | senderKey: string;
25 | } {
26 | return {
27 | ...shortUInt,
28 |
29 | serialize(value: number, { tag }): Buffer {
30 | if (Tag.GaAttachTx === tag && value !== 1) {
31 | throw new ArgumentError('nonce', 'equal 1 if GaAttachTx', value);
32 | }
33 | return shortUInt.serialize(value);
34 | },
35 |
36 | async prepare(value, params, options) {
37 | if (value != null) return value;
38 | // TODO: uncomment the below line
39 | // if (options._isInternalBuild === true) return 0;
40 | const { onNode, strategy } = options;
41 | const senderId = options[senderKey];
42 | const requirement = 'provided (or provide `nonce` instead)';
43 | if (onNode == null) throw new ArgumentError('onNode', requirement, onNode);
44 | if (senderId == null) throw new ArgumentError('senderId', requirement, senderId);
45 | return (
46 | await onNode
47 | .getAccountNextNonce(senderId.replace(/^ok_/, 'ak_'), { strategy })
48 | .catch((error) => {
49 | if (!isAccountNotFoundError(error)) throw error;
50 | return { nextNonce: 1 };
51 | })
52 | ).nextNonce;
53 | },
54 |
55 | senderKey,
56 | };
57 | }
58 |
--------------------------------------------------------------------------------
/src/aepp-wallet-communication/wallet-detector.ts:
--------------------------------------------------------------------------------
1 | import BrowserConnection from './connection/Browser.js';
2 | import BrowserWindowMessageConnection from './connection/BrowserWindowMessage.js';
3 | import { MESSAGE_DIRECTION, METHODS } from './schema.js';
4 | import { WalletInfo } from './rpc/types.js';
5 | import { UnsupportedPlatformError } from '../utils/errors.js';
6 |
7 | interface Wallet {
8 | info: WalletInfo;
9 | getConnection: () => BrowserWindowMessageConnection;
10 | }
11 | interface Wallets {
12 | [key: string]: Wallet;
13 | }
14 |
15 | /**
16 | * A function to detect available wallets
17 | * @category aepp wallet communication
18 | * @param connection - connection to use to detect wallets
19 | * @param onDetected - call-back function which trigger on new wallet
20 | * @returns a function to stop scanning
21 | */
22 | export default (
23 | connection: BrowserConnection,
24 | onDetected: ({ wallets, newWallet }: { wallets: Wallets; newWallet: Wallet }) => void,
25 | ): (() => void) => {
26 | if (window == null)
27 | throw new UnsupportedPlatformError(
28 | 'Window object not found, you can run wallet detector only in browser',
29 | );
30 | const wallets: Wallets = {};
31 |
32 | connection.connect(
33 | (
34 | { method, params }: { method: string; params: WalletInfo },
35 | origin: string,
36 | source: Window,
37 | ) => {
38 | if (method !== METHODS.readyToConnect || wallets[params.id] != null) return;
39 |
40 | const wallet = {
41 | info: params,
42 | getConnection() {
43 | return new BrowserWindowMessageConnection({
44 | target: source,
45 | ...(params.type === 'extension'
46 | ? {
47 | sendDirection: MESSAGE_DIRECTION.to_waellet,
48 | receiveDirection: MESSAGE_DIRECTION.to_aepp,
49 | ...(window.origin !== 'null' && { origin: window.origin }),
50 | }
51 | : {
52 | origin: params.origin,
53 | }),
54 | });
55 | },
56 | };
57 | wallets[wallet.info.id] = wallet;
58 | onDetected({ wallets, newWallet: wallet });
59 | },
60 | () => {},
61 | );
62 |
63 | return () => connection.disconnect();
64 | };
65 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/components/DataSign.vue:
--------------------------------------------------------------------------------
1 |
2 | Sign raw data (unsafe)
3 |
4 |
5 |
Data as text
6 |
7 |
12 |
13 |
14 |
15 |
Data as hex
16 |
17 |
22 |
23 |
24 |
25 |
Data encoded
26 |
27 |
28 |
29 |
30 |
{
33 | promise = dataSign();
34 | }
35 | "
36 | >
37 | Sign data
38 |
39 |
40 |
Data sign result
41 |
42 |
43 |
44 |
45 |
46 |
85 |
--------------------------------------------------------------------------------
/src/utils/other.ts:
--------------------------------------------------------------------------------
1 | import { RestError } from '@azure/core-rest-pipeline';
2 |
3 | export const pause = async (duration: number): Promise =>
4 | new Promise((resolve) => {
5 | setTimeout(resolve, duration);
6 | });
7 |
8 | export const mapObject = (
9 | object: { [k: string]: InputV },
10 | fn: (
11 | value: [string, InputV],
12 | index: number,
13 | array: Array<[string, InputV]>,
14 | ) => [number | string, OutputV],
15 | ): { [k: string]: OutputV } => Object.fromEntries(Object.entries(object).map(fn));
16 |
17 | // remove after dropping webpack4 support
18 | const isWebpack4Buffer = (() => {
19 | try {
20 | Buffer.concat([Uint8Array.from([])]);
21 | return false;
22 | } catch (error) {
23 | return true;
24 | }
25 | })();
26 |
27 | export const concatBuffers = isWebpack4Buffer
28 | ? (list: readonly Uint8Array[], totalLength?: number): Buffer =>
29 | Buffer.concat(
30 | list.map((el) => Buffer.from(el)),
31 | totalLength,
32 | )
33 | : Buffer.concat;
34 |
35 | /**
36 | * Object key type guard
37 | * @param key - Maybe object key
38 | * @param object - Object
39 | */
40 | export function isKeyOfObject(
41 | key: string | number | symbol,
42 | object: T,
43 | ): key is keyof T {
44 | return key in object;
45 | }
46 |
47 | /**
48 | * Array item type guard
49 | * @param item - Maybe array item
50 | * @param array - Array
51 | */
52 | export function isItemOfArray(item: any, array: readonly T[]): item is T {
53 | return array.includes(item);
54 | }
55 |
56 | export function isAccountNotFoundError(error: Error): boolean {
57 | return (
58 | error instanceof RestError &&
59 | error.statusCode === 404 &&
60 | error.message.includes('Account not found')
61 | );
62 | }
63 |
64 | // based on https://stackoverflow.com/a/50375286
65 | export type UnionToIntersection = (Union extends any ? (k: Union) => void : never) extends (
66 | k: infer Intersection,
67 | ) => void
68 | ? Intersection
69 | : never;
70 |
71 | // based on https://stackoverflow.com/a/61108377
72 | export type Optional = Pick, K> & Omit;
73 |
74 | export function ensureError(error: unknown): asserts error is Error {
75 | if (error instanceof Error) return;
76 | throw error;
77 | }
78 |
--------------------------------------------------------------------------------
/src/oracle/OracleBase.ts:
--------------------------------------------------------------------------------
1 | import { decode, Encoded } from '../utils/encoder.js';
2 | import Node from '../Node.js';
3 |
4 | /**
5 | * @category oracle
6 | */
7 | type OracleQueryNode = Awaited>;
8 |
9 | /**
10 | * @category oracle
11 | */
12 | export interface OracleQuery extends OracleQueryNode {
13 | // TODO: type should be corrected in node api
14 | id: Encoded.OracleQueryId;
15 | decodedQuery: string;
16 | decodedResponse: string;
17 | }
18 |
19 | function decodeQuery(queryEntry: OracleQueryNode): OracleQuery {
20 | return {
21 | ...queryEntry,
22 | id: queryEntry.id as Encoded.OracleQueryId,
23 | decodedQuery: decode(queryEntry.query as Encoded.OracleQuery).toString(),
24 | decodedResponse: decode(queryEntry.response as Encoded.OracleResponse).toString(),
25 | };
26 | }
27 |
28 | /**
29 | * This class is needed because `getOracleQuery` would return different values depending on the
30 | * oracle type.
31 | * @category oracle
32 | */
33 | export default class OracleBase {
34 | /**
35 | * @param address - Oracle public key
36 | */
37 | constructor(
38 | public readonly address: Encoded.OracleAddress,
39 | public options: { onNode: Node },
40 | ) {}
41 |
42 | /**
43 | * Get oracle entry from the node
44 | * @param options - Options object
45 | */
46 | async getState(options: { onNode?: Node } = {}): ReturnType {
47 | const opt = { ...this.options, ...options };
48 | return opt.onNode.getOracleByPubkey(this.address);
49 | }
50 |
51 | /**
52 | * Get oracle queries from the node
53 | * @param options - Options object
54 | */
55 | async getQueries(options: { onNode?: Node } = {}): Promise {
56 | const opt = { ...this.options, ...options };
57 | return (await opt.onNode.getOracleQueriesByPubkey(this.address)).oracleQueries.map(decodeQuery);
58 | }
59 |
60 | /**
61 | * Get oracle query entry from the node
62 | * @param queryId - Oracle query ID
63 | * @param options - Options object
64 | */
65 | async getQuery(
66 | queryId: Encoded.OracleQueryId,
67 | options: { onNode?: Node } = {},
68 | ): Promise {
69 | const { onNode } = { ...this.options, ...options };
70 | const queryEntry = await onNode.getOracleQueryByPubkeyAndQueryId(this.address, queryId);
71 | return decodeQuery(queryEntry);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/docs/guides/typed-data.md:
--------------------------------------------------------------------------------
1 | # Typed data hashing and signing
2 |
3 | ## Common structure
4 |
5 | The whole idea is heavily inspired by [EIP-712](https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator). To get a signature needed to calculate `hash(hash(domain), hash(aci), hash(data))`.
6 |
7 | `hash` function is `blake2b`.
8 |
9 | `domain` is a record containing not required properties:
10 |
11 | - `name` as string,
12 | - `version` as integer,
13 | - `networkId` as string,
14 | - `contractAddress` as ct-encoded string.
15 |
16 | `aci` is part of a complete contract ACI. It defines a type of data to sign. For example, the ACI
17 |
18 | ```json
19 | {
20 | "record": [
21 | { "name": "foo", "type": "string" },
22 | { "name": "bar", "type": "int" }
23 | ]
24 | }
25 | ```
26 |
27 | corresponds to the data
28 |
29 | ```json
30 | { "foo": "test", "bar": 42 }
31 | ```
32 |
33 | `domain` and `data` are fate-encoded before hashing. `aci` is prepared for hashing according to [RFC8785](https://tools.ietf.org/html/rfc8785).
34 |
35 | ## Implementation
36 |
37 | - [AccountBase:signTypedData](https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/src/account/Base.ts#L64-L71) — calculates signature, supported in AccountMemory and in aepp-wallet connection;
38 | - [hashTypedData](https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/src/utils/typed-data.ts#L87-L101) — calculates the overall hash of typed data to sign;
39 | - [hashJson](https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/src/utils/typed-data.ts#L11-L13) — deterministic hashing of an arbitrary JS value, used to calculate `hash(aci)`;
40 | - [hashDomain](https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/src/utils/typed-data.ts#L58-L82) — use for debugging or to prepare the hash value for smart contract.
41 |
42 | ## Examples
43 |
44 | - [signing and verifying on aepp side](https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/examples/browser/aepp/src/TypedData.vue)
45 | - [signing confirmation on wallet side](https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/examples/browser/wallet-iframe/src/App.vue#L168-L176)
46 | - [verifying a signature on contract side](https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/test/integration/typed-data.ts#L76-L106)
47 |
--------------------------------------------------------------------------------
/test/integration/channel-utils.ts:
--------------------------------------------------------------------------------
1 | import { channelUrl } from '.';
2 | import { Encoded, Channel, AccountMemory, AeSdk } from '../../src';
3 | import { ChannelOptions, SignTxWithTag } from '../../src/channel/internal';
4 |
5 | export async function waitForChannel(channel: Channel, statuses: string[]): Promise {
6 | return new Promise((resolve, reject) => {
7 | function handler(status: string): void {
8 | const expectedStatus = statuses.shift();
9 | if (status !== expectedStatus) {
10 | reject(new Error(`Expected SC status ${expectedStatus}, got ${status} instead`));
11 | channel.off('statusChanged', handler);
12 | } else if (statuses.length === 0) {
13 | resolve();
14 | channel.off('statusChanged', handler);
15 | }
16 | }
17 | channel.on('statusChanged', handler);
18 | });
19 | }
20 |
21 | export const sharedParams = {
22 | url: channelUrl,
23 | pushAmount: 1e13,
24 | initiatorAmount: 5e14,
25 | responderAmount: 5e14,
26 | channelReserve: 0,
27 | port: 3114,
28 | lockPeriod: 1,
29 | initiatorId: 'ak_' as Encoded.AccountAddress,
30 | responderId: 'ak_' as Encoded.AccountAddress,
31 | minimumDepth: 0,
32 | minimumDepthStrategy: 'plain' as const,
33 | };
34 |
35 | export async function initializeChannels(
36 | initiatorParams: {
37 | role: 'initiator';
38 | host: string;
39 | sign: SignTxWithTag;
40 | } & Partial,
41 | responderParams: { role: 'responder'; sign: SignTxWithTag } & Partial,
42 | ): Promise<[Channel, Channel]> {
43 | const initiatorCh = await Channel.initialize({
44 | ...sharedParams,
45 | ...initiatorParams,
46 | });
47 | const responderCh = await Channel.initialize({
48 | ...sharedParams,
49 | ...responderParams,
50 | });
51 | await Promise.all([
52 | waitForChannel(initiatorCh, ['accepted', 'signed', 'open']),
53 | waitForChannel(responderCh, ['halfSigned', 'open']),
54 | ]);
55 | return [initiatorCh, responderCh];
56 | }
57 |
58 | export async function recreateAccounts(aeSdk: AeSdk): Promise<[AccountMemory, AccountMemory]> {
59 | const initiator = AccountMemory.generate();
60 | const responder = AccountMemory.generate();
61 | await aeSdk.spend(3e15, initiator.address);
62 | await aeSdk.spend(3e15, responder.address);
63 | sharedParams.initiatorId = initiator.address;
64 | sharedParams.responderId = responder.address;
65 | return [initiator, responder];
66 | }
67 |
--------------------------------------------------------------------------------
/docs/guides/low-vs-high-usage.md:
--------------------------------------------------------------------------------
1 | # Low vs High level API
2 |
3 | ## Interactions
4 |
5 | `AeSdk` is a general, high-level interface that wraps multiple low-level interfaces. A general interface is preferred for its simplicity and resilience to breaking changes.
6 |
7 | But there is also low-level interfaces. It's excellent for additional control, and as a teaching tool to understand the underlying operations. Most real-world requirements involves a series of low-level operations, so the SDK provides abstractions for these.
8 |
9 | ### Node API
10 |
11 | The aeternity node exposes [a REST API]. This API is described in the [OpenAPI document]. SDK uses this document to generate a TypeScript client. The result client (implemented in [`Node` class][node]) a basically a mapping of all node endpoints as functions.
12 |
13 | [a REST API]: https://api-docs.aeternity.io/
14 | [OpenAPI document]: https://mainnet.aeternity.io/api?oas3
15 | [node]: https://sdk.aeternity.io/v14.1.0/api/classes/Node.html
16 |
17 | So to get a transaction based on its hash you would invoke `node.getTransactionByHash('th_fWEsg152BNYcrqA9jDh9VVpacYojCUb1yu45zUnqhmQ3dAAC6')`. In this way the SDK is simply a mapping of the raw API calls into JavaScript.
18 |
19 | ### Transaction builder
20 |
21 | Any blockchain state change requires signing a transaction. Transaction should be built according to the [protocol]. SDK implements it in [`buildTx`][buildTx], [`buildTxAsync`][buildTxAsync], and [`unpackTx`][unpackTx]. [`buildTxAsync`][buildTxAsync] requires fewer arguments than [`buildTx`][buildTx], but it expects the node instance provided in arguments.
22 |
23 | [protocol]: https://github.com/aeternity/protocol/blob/c007deeac4a01e401238412801ac7084ac72d60e/serializations.md#accounts-version-1-basic-accounts
24 | [buildTx]: https://sdk.aeternity.io/v14.1.0/api/functions/buildTx.html
25 | [buildTxAsync]: https://sdk.aeternity.io/v14.1.0/api/functions/buildTxAsync.html
26 | [unpackTx]: https://sdk.aeternity.io/v14.1.0/api/functions/unpackTx.html
27 |
28 | ## High-level SDK usage (preferable)
29 |
30 | Example spend call, using æternity's SDK abstraction:
31 |
32 | https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/examples/node/_api-high-level.js#L1-L18
33 |
34 | ## Low-level SDK usage
35 |
36 | The same spend execution, but using low-level SDK functions:
37 |
38 | https://github.com/aeternity/aepp-sdk-js/blob/1cd128798018d98bdd41eff9104442b44b385d46/examples/node/_api-low-level.js#L1-L19
39 |
--------------------------------------------------------------------------------
/docs/compatibility.md:
--------------------------------------------------------------------------------
1 | # Compatibility Table
2 |
3 | This package is expected to work in these environments:
4 |
5 | | Environment | Comment |
6 | | ------------------------------------- | ----------------------------------------------------------------------------- |
7 | | nodejs>=18.19 | |
8 | | Browser using script tag, umd | |
9 | | webpack@4 | requires a fix to work with mjs build [webpack-4] |
10 | | webpack@5 | |
11 | | @vue/cli@4 (webpack@4) | requires aliases to ESM versions of autorest deps [vue-cli4] |
12 | | @vue/cli@5 (webpack@5) | |
13 | | vue@3 | AeSdk, Contract instances can't be reactive [vue-3] |
14 | | create-react-app@4 (webpack@4) | mjs build is not compatible with webpack@4 [cra-webpack-4] |
15 | | create-react-app@5 (webpack@5) | |
16 | | create-react-native-app@3 (webpack@4) | mjs build is not compatible with webpack@4 [cra-webpack-4] |
17 | | meteor@2 | |
18 | | jest@27.5.1 | requires an environment where Buffer is instanceof Uint8Array [jest] |
19 | | typescript>=4.8 | requires `tsconfig.json` adjustments [typescript] |
20 | | vite@3 | requires `build.target: 'es2020'` and `bigint: true` in vite.config.js [vite] |
21 |
22 | [webpack-4]: https://github.com/webpack/webpack/issues/7482#issuecomment-394884837
23 | [cra-webpack-4]: https://github.com/aeternity/aepp-sdk-js/issues/1529
24 | [jest]: https://github.com/facebook/jest/issues/4422#issuecomment-770274099
25 | [typescript]: README.md#typescript-projects
26 | [vue-cli4]: README.md#vue-cli4
27 | [vue-3]: README.md#vue3
28 | [vite]: https://github.com/vitejs/vite/issues/9062#issuecomment-1202167352
29 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/address.ts:
--------------------------------------------------------------------------------
1 | import { ArgumentError, PrefixNotFoundError, TagNotFoundError } from '../../../utils/errors.js';
2 | import { toBytes } from '../../../utils/bytes.js';
3 | import { decode, encode, Encoded, Encoding } from '../../../utils/encoder.js';
4 | import { isItemOfArray } from '../../../utils/other.js';
5 |
6 | /**
7 | * Map of prefix to ID tag constant
8 | * @see {@link https://github.com/aeternity/protocol/blob/master/serializations.md#the-id-type}
9 | * @see {@link https://github.com/aeternity/aeserialization/blob/eb68fe331bd476910394966b7f5ede7a74d37e35/src/aeser_id.erl#L97-L102}
10 | * @see {@link https://github.com/aeternity/aeserialization/blob/eb68fe331bd476910394966b7f5ede7a74d37e35/src/aeser_api_encoder.erl#L163-L168}
11 | */
12 | export const idTagToEncoding = [
13 | Encoding.AccountAddress,
14 | Encoding.Name,
15 | Encoding.Commitment,
16 | Encoding.OracleAddress,
17 | Encoding.ContractAddress,
18 | Encoding.Channel,
19 | ] as const;
20 |
21 | export type AddressEncodings = (typeof idTagToEncoding)[number];
22 |
23 | export default function genAddressField(
24 | ...encodings: Encoding[]
25 | ): {
26 | serialize: (value: Encoded.Generic) => Buffer;
27 | deserialize: (value: Buffer) => Encoded.Generic;
28 | } {
29 | return {
30 | /**
31 | * Utility function to create and _id type
32 | * @param hashId - Encoded hash
33 | * @returns Buffer Buffer with ID tag and decoded HASh
34 | */
35 | serialize(hashId) {
36 | const enc = hashId.slice(0, 2);
37 | if (!isItemOfArray(enc, idTagToEncoding)) throw new TagNotFoundError(enc);
38 | if (!isItemOfArray(enc, encodings)) {
39 | throw new ArgumentError('Address encoding', encodings.join(', '), enc);
40 | }
41 | const idTag = idTagToEncoding.indexOf(enc) + 1;
42 | return Buffer.from([...toBytes(idTag), ...decode(hashId)]);
43 | },
44 |
45 | /**
46 | * Utility function to read and _id type
47 | * @param buf - Data
48 | * @returns Encoded hash string with prefix
49 | */
50 | deserialize(buf) {
51 | const idTag = Buffer.from(buf).readUIntBE(0, 1);
52 | const enc = idTagToEncoding[idTag - 1];
53 | if (enc == null) throw new PrefixNotFoundError(idTag);
54 | if (!isItemOfArray(enc, encodings)) {
55 | throw new ArgumentError('Address encoding', encodings.join(', '), enc);
56 | }
57 | return encode(buf.subarray(1), enc) as Encoded.Generic;
58 | },
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/tooling/docs/examples-to-md.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import fs from 'fs/promises';
3 |
4 | function splitCodeIntoBlocks(_text) {
5 | const content = [];
6 | let text = _text;
7 | while (text) {
8 | const commentIndex = text.search(/\n *\//);
9 | switch (commentIndex) {
10 | case -1:
11 | content.push({ type: 'code', content: text });
12 | return content;
13 | case 0:
14 | text = text.slice(commentIndex).trimLeft().slice(1);
15 | break;
16 | default:
17 | content.push({ type: 'code', content: text.slice(0, commentIndex) });
18 | text = text.slice(commentIndex).trimLeft().slice(1);
19 | break;
20 | }
21 | switch (text[0]) {
22 | case '/':
23 | content.push({ type: 'comment', content: text.slice(1, text.indexOf('\n')) });
24 | text = text.slice(text.indexOf('\n'));
25 | break;
26 | case '*':
27 | content.push({ type: 'comment', content: text.slice(1, text.indexOf('*/')) });
28 | text = text.slice(text.indexOf('*/') + 2);
29 | break;
30 | default:
31 | throw new Error(`Parsing failed, unknown char: ${text[0]}`);
32 | }
33 | }
34 | return content;
35 | }
36 |
37 | const directory = process.argv[2];
38 | const files = (await fs.readdir(directory))
39 | .filter((file) => file.endsWith('.js'))
40 | .filter((file) => !file.startsWith('_'));
41 |
42 | await Promise.all(
43 | files.map(async (fileName) => {
44 | const inputFilePath = path.resolve(process.cwd(), directory, fileName);
45 | const text = await fs.readFile(inputFilePath, 'utf8');
46 |
47 | const textMd = splitCodeIntoBlocks(text)
48 | .map(({ type, content }) => ({
49 | type,
50 | content: type === 'code' ? content.replace(/^\n+|\n+$/g, '') : content.replace(/^ /, ''),
51 | }))
52 | .filter(({ type, content }) => type !== 'code' || content)
53 | .filter(({ content }) => !content.includes('License'))
54 | .filter(({ content }) => !content.includes('#!/'))
55 | .map(({ type, content }) => (type === 'code' ? `\`\`\`js\n${content}\n\`\`\`` : content))
56 | .join('\n');
57 |
58 | const fileParsedPath = path.parse(path.resolve(process.cwd(), 'docs', directory, fileName));
59 | await fs.mkdir(fileParsedPath.dir, { recursive: true });
60 |
61 | const outputFilePath = path.format({ ...fileParsedPath, base: undefined, ext: '.md' });
62 | await fs.writeFile(outputFilePath, Buffer.from(textMd));
63 | console.log(`${inputFilePath} -> ${outputFilePath}`);
64 | }),
65 | );
66 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import webpack from 'webpack';
3 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
4 | import pkg from './package.json' with { type: 'json' };
5 | import babelConfig from './babel.config.js';
6 |
7 | const { dependencies } = pkg;
8 |
9 | function configure(filename, opts = {}) {
10 | const isNode = opts.target.includes('node');
11 | return (env) => ({
12 | entry: `./src/index${isNode ? '' : '-browser'}.ts`,
13 | mode: 'production',
14 | devtool: 'source-map',
15 | module: {
16 | rules: [
17 | {
18 | test: /\.(js|ts)$/,
19 | include: path.resolve(import.meta.dirname, 'src'),
20 | loader: 'babel-loader',
21 | options: { ...babelConfig, browserslistEnv: opts.target.split(':')[1] },
22 | },
23 | ],
24 | },
25 | optimization: {
26 | minimize: !isNode,
27 | },
28 | resolve: {
29 | extensions: ['.ts', '.js'],
30 | extensionAlias: { '.js': ['.ts', '.js'] },
31 | fallback: isNode
32 | ? {}
33 | : {
34 | buffer: path.resolve(import.meta.dirname, 'node_modules/buffer/index.js'),
35 | child_process: false,
36 | os: false,
37 | path: false,
38 | 'fs/promises': false,
39 | url: false,
40 | },
41 | },
42 | plugins: [
43 | ...(isNode
44 | ? []
45 | : [
46 | new webpack.ProvidePlugin({
47 | process: 'process',
48 | Buffer: ['buffer', 'Buffer'],
49 | }),
50 | ]),
51 | ...(env.REPORT
52 | ? [
53 | new BundleAnalyzerPlugin({
54 | analyzerMode: 'static',
55 | reportFilename: `${filename}.html`,
56 | openAnalyzer: false,
57 | }),
58 | ]
59 | : []),
60 | ],
61 | output: {
62 | path: path.resolve(import.meta.dirname, 'dist'),
63 | filename,
64 | library: {
65 | name: 'Aeternity',
66 | type: 'umd',
67 | },
68 | },
69 | externals: Object.fromEntries(
70 | Object.keys(dependencies).map((dependency) => [dependency, dependency]),
71 | ),
72 | ...opts,
73 | });
74 | }
75 |
76 | export default [
77 | configure('aepp-sdk.cjs', { target: 'browserslist:node' }),
78 | configure('aepp-sdk.browser.cjs', { target: 'browserslist:browser' }),
79 | configure('aepp-sdk.browser-script.cjs', {
80 | target: 'browserslist:browser',
81 | externals: undefined,
82 | }),
83 | ];
84 |
--------------------------------------------------------------------------------
/docs/guides/migration/9.md:
--------------------------------------------------------------------------------
1 | # Migration to 9.0.0
2 |
3 | This guide describes all breaking changes introduced with `v9.0.0`.
4 |
5 | ## drop `waitMined` static method
6 |
7 | If you used it like
8 |
9 | ```js
10 | const sdk = await Universal({ ... })
11 | sdk.waitMined(false)
12 | ```
13 |
14 | then you have to rewrite it using Stamp composition
15 |
16 | ```js
17 | const sdk = await Universal.compose({
18 | deepProps: { Ae: { defaults: { waitMined: false } } }
19 | })({ ... })
20 | ```
21 |
22 | or pass it to specific methods, like
23 |
24 | ```js
25 | sdk.spend(amount, receiver, { waitMined: false });
26 | ```
27 |
28 | or even
29 |
30 | ```js
31 | const sdk = await Universal({ ... })
32 | sdk.deepProps({ Ae: { defaults: { waitMined: false } } })
33 | ```
34 |
35 | ## drop `assertedType`, use `decode` instead
36 |
37 | If you used it like
38 |
39 | ```js
40 | const payload = Crypto.decodeBase64Check(Crypto.assertedType('tx_...', 'tx'));
41 | ```
42 |
43 | then you have to rewrite it using `decode` method
44 |
45 | ```js
46 | const payload = TxBuilderHelper.decode('tx_...', 'tx');
47 | ```
48 |
49 | ## **validator:** recursive validator, simplify schema
50 |
51 | Instead of `TransactionValidator` stamp use `verifyTransaction` function. The function accepts
52 | a transaction, and a Node instance for validation (instead of network id), it doesn't return
53 | an unpacked transaction anymore, just an array of errors. Each error contains a verbose `message`
54 | (`msg` before), unique `key` (for easy comparison), `checkedKeys` array (`txKey` before). Using
55 | `node` instead of `networkId` allows to ensure transaction validation, so warnings are errors
56 | now (`type` field removed).
57 |
58 | `SCHEMA` doesn't contain validation schema anymore. This wasn't supposed to be used by external
59 | developers.
60 |
61 | ## simplify buildTxHash helper
62 |
63 | If you used `buildHash` like
64 |
65 | ```js
66 | const hash = TxBuilderHelper.buildHash('xx', Buffer.from([1, 2, 3]), { raw: true });
67 | ```
68 |
69 | then use
70 |
71 | ```js
72 | const hash = Crypto.hash(Buffer.from([1, 2, 3]));
73 | ```
74 |
75 | If you used it with a falsy `raw` then
76 |
77 | ```js
78 | const hash = TxBuilderHelper.encode(Crypto.hash(Buffer.from([1, 2, 3])), 'xx');
79 | ```
80 |
81 | `buildTxHash` don't have `raw` switch anymore, it returns `th_`-encoded string in all cases,
82 | but it still accepts transactions as a string and as a buffer.
83 |
84 | ## enable verification in deep props instead of extra variable
85 |
86 | If you were passing `verifyTx: false` to sdk factory then use `verify: false` instead.
87 |
--------------------------------------------------------------------------------
/src/tx/builder/field-types/ct-version.ts:
--------------------------------------------------------------------------------
1 | import { ConsensusProtocolVersion, VmVersion, AbiVersion } from '../constants.js';
2 | import Node from '../../../Node.js';
3 |
4 | /*
5 | * First abi/vm by default
6 | * @see {@link https://github.com/aeternity/protocol/blob/71cf111/contracts/contract_vms.md#virtual-machines-on-the-æternity-blockchain}
7 | */
8 | export const ProtocolToVmAbi = {
9 | [ConsensusProtocolVersion.Ceres]: {
10 | 'contract-create': {
11 | vmVersion: [VmVersion.Fate3],
12 | abiVersion: [AbiVersion.Fate],
13 | },
14 | 'contract-call': {
15 | vmVersion: [],
16 | abiVersion: [AbiVersion.Fate],
17 | },
18 | 'oracle-call': {
19 | vmVersion: [],
20 | abiVersion: [AbiVersion.NoAbi, AbiVersion.Fate],
21 | },
22 | },
23 | } as const;
24 |
25 | export interface CtVersion {
26 | vmVersion: VmVersion;
27 | abiVersion: AbiVersion;
28 | }
29 |
30 | export function getProtocolDetails(
31 | protocolVersion: ConsensusProtocolVersion,
32 | type: 'contract-create' | 'contract-call' | 'oracle-call',
33 | ): CtVersion {
34 | const protocol = ProtocolToVmAbi[protocolVersion][type];
35 | return {
36 | vmVersion: protocol.vmVersion[0] ?? VmVersion.Fate2,
37 | abiVersion: protocol.abiVersion[0],
38 | };
39 | }
40 |
41 | export default {
42 | serialize(
43 | value: CtVersion | undefined,
44 | params: {},
45 | {
46 | consensusProtocolVersion = ConsensusProtocolVersion.Ceres,
47 | }: { consensusProtocolVersion?: ConsensusProtocolVersion },
48 | ): Buffer {
49 | value ??= getProtocolDetails(consensusProtocolVersion, 'contract-create');
50 |
51 | return Buffer.from([value.vmVersion, 0, value.abiVersion]);
52 | },
53 |
54 | async prepare(
55 | value: CtVersion | undefined,
56 | params: {},
57 | // TODO: { consensusProtocolVersion: ConsensusProtocolVersion } | { onNode: Node } | {}
58 | options: { consensusProtocolVersion?: ConsensusProtocolVersion; onNode?: Node },
59 | ): Promise {
60 | if (value != null) return value;
61 | if (options.consensusProtocolVersion != null) return undefined;
62 | if (Object.keys(ConsensusProtocolVersion).length === 2) return undefined;
63 | if (options.onNode != null) {
64 | return getProtocolDetails(
65 | (await options.onNode.getNodeInfo()).consensusProtocolVersion,
66 | 'contract-create',
67 | );
68 | }
69 | return undefined;
70 | },
71 |
72 | deserialize(buffer: Buffer): CtVersion {
73 | const [vm, , abi] = buffer;
74 | return { vmVersion: +vm, abiVersion: +abi };
75 | },
76 | };
77 |
--------------------------------------------------------------------------------
/docs/guides/jwt.md:
--------------------------------------------------------------------------------
1 | # JWT usage
2 |
3 | ## Generating JWT
4 |
5 | Use `signJwt` to generate a JWT signed by an account provided in arguments.
6 |
7 | ```ts
8 | import { AccountMemory, signJwt } from '@aeternity/aepp-sdk';
9 |
10 | const account = AccountMemory.generate();
11 | const payload = { test: 'data' };
12 | const jwt = await signJwt(payload, account);
13 | ```
14 |
15 | Provide `sub_jwk: undefined` in payload to omit signer public key added by default.
16 | Do it to make JWT shorter.
17 |
18 | ```ts
19 | const jwt = await signJwt({ test: 'data', sub_jwk: undefined }, account);
20 | ```
21 |
22 | Or if you using a different way to encode a signer address.
23 |
24 | ```ts
25 | const payload = {
26 | test: 'data',
27 | sub_jwk: undefined,
28 | address: 'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E',
29 | };
30 | const jwt = await signJwt(payload, account);
31 | ```
32 |
33 | ## Verifying JWT
34 |
35 | Let's assume we got a JWT as string. Firstly we need to ensure that it has the right format.
36 |
37 | ```ts
38 | import { isJwt, ensureJwt } from '@aeternity/aepp-sdk';
39 |
40 | if (!isJwt(jwt)) throw new Error('Invalid JWT');
41 | // alternatively,
42 | ensureJwt(jwt);
43 | ```
44 |
45 | After that we can pass JWT to other SDK's methods, for example to get JWT payload and signer address
46 | in case JWT has the signer public key included in `"sub_jwk"`.
47 |
48 | ```ts
49 | import { unpackJwt } from '@aeternity/aepp-sdk';
50 |
51 | const { payload, signer } = unpackJwt(jwt);
52 | console.log(payload); // { test: 'data', sub_jwk: { ... } }
53 | console.log(signer); // 'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E'
54 | ```
55 |
56 | `unpackJwt` will also check the JWT signature in this case.
57 |
58 | Alternatively, if `"sub_jwk"` is not included then we can provide signer address to `unpackJwt`.
59 |
60 | ```ts
61 | const knownSigner = 'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E';
62 | const { payload, signer } = unpackJwt(jwt, knownSigner);
63 | console.log(payload); // { test: 'data' }
64 | console.log(signer); // 'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E'
65 | ```
66 |
67 | If we need to a get signer address based on JWT payload then we need to unpack it without checking
68 | the signature. Don't forget to check signature after that using `verifyJwt`.
69 |
70 | ```ts
71 | import { verifyJwt } from '@aeternity/aepp-sdk';
72 |
73 | const { payload, signer } = unpackJwt(jwt);
74 | console.log(payload); // { address: 'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E' }
75 | console.log(signer); // undefined
76 | if (!verifyJwt(jwt, payload.address)) throw new Error('JWT signature is invalid');
77 | ```
78 |
--------------------------------------------------------------------------------
/examples/browser/aepp/src/Jwt.vue:
--------------------------------------------------------------------------------
1 |
2 | Generate a JWT
3 |
4 |
5 |
Payload as JSON
6 |
7 |
8 |
9 |
10 |
11 |
Include "sub_jwk"
12 |
13 |
14 |
15 |
16 |
{
19 | signPromise = sign();
20 | }
21 | "
22 | >
23 | Sign
24 |
25 |
26 |
Signed JWT
27 |
28 |
29 |
30 |
31 | Unpack and verify JWT
32 |
33 |
34 |
JWT to unpack
35 |
36 |
37 |
38 |
39 |
40 |
Signer address
41 |
42 |
43 |
44 |
45 |
{
48 | unpackPromise = unpack();
49 | }
50 | "
51 | >
52 | Unpack
53 |
54 |
55 |
Unpack result
56 |
57 |
58 |
59 |
60 |
61 |
90 |
--------------------------------------------------------------------------------