├── 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 31 | 32 | 54 | 55 |