Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update low/high level api docs #2031

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 34 additions & 106 deletions docs/guides/low-vs-high-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,109 +2,37 @@

## Interactions

> "There are two approaches, purist and high-level."

_Alexander Kahl._

The purist uses the functions generated out of the Swagger
file. After creating the SDK instance `aeSdk` with the AeSdk class it exposes a mapping of all `operationId`s as functions, converted to camelCase (from PascalCase). So e.g. in order to get a transaction
based on its hash you would invoke `aeSdk.api.getTransactionByHash('th_...')`.

In this way the SDK is simply a mapping of the raw API calls into
JavaScript. It's excellent for low-level control, and as a teaching tool to
understand the node's operations. Most real-world requirements involves a series
of chain operations, so the SDK provides abstractions for these.

## (**Recommended**) High-level SDK usage

Example spend function, using æternity's SDK abstraction.

```js
import { MemoryAccount, Node, AeSdk } from '@aeternity/aepp-sdk';

async function init() {
const node = new Node('https://testnet.aeternity.io'); // ideally host your own node!

const aeSdk = new AeSdk({
nodes: [{ name: 'testnet', instance: node }],
accounts: [new MemoryAccount('<SECRET_KEY_HERE>')],
});

// log transaction info
console.log(await aeSdk.spend(100, 'ak_...'));
}
```

## Low-level SDK usage (use [API](https://docs.aeternity.com/protocol/node/api) endpoints directly)

Example spend function, using the SDK, talking directly to the [**API**](https://docs.aeternity.com/protocol/node/api):

```js
import { MemoryAccount, Node, AeSdk, Tag } from '@aeternity/aepp-sdk';

async function spend(amount, recipient) {
const node = new Node('https://testnet.aeternity.io'); // ideally host your own node!
const aeSdk = new AeSdk({
nodes: [{ name: 'testnet', instance: node }],
accounts: [new MemoryAccount('<SECRET_KEY_HERE>')],
});

// builds an unsigned SpendTx using integrated transaction builder
const spendTx = await aeSdk.buildTx(Tag.SpendTx, {
senderId: aeSdk.address,
recipientId: recipient,
amount, // aettos
payload: 'using low-level api is funny',
});

// sign the encoded transaction
const signedTx = await aeSdk.signTransaction(spendTx);

// broadcast the signed tx to the node
console.log(await aeSdk.api.postTransaction({ tx: signedTx }));
}
```

Following functions are available with the low-level API right now:

```js
console.log(aeSdk.api);
/*
{
getTopHeader: [AsyncFunction (anonymous)],
getCurrentKeyBlock: [AsyncFunction (anonymous)],
getCurrentKeyBlockHash: [AsyncFunction (anonymous)],
getCurrentKeyBlockHeight: [AsyncFunction (anonymous)],
getPendingKeyBlock: [AsyncFunction (anonymous)],
getKeyBlockByHash: [AsyncFunction (anonymous)],
getKeyBlockByHeight: [AsyncFunction (anonymous)],
getMicroBlockHeaderByHash: [AsyncFunction (anonymous)],
getMicroBlockTransactionsByHash: [AsyncFunction (anonymous)],
getMicroBlockTransactionByHashAndIndex: [AsyncFunction (anonymous)],
getMicroBlockTransactionsCountByHash: [AsyncFunction (anonymous)],
getCurrentGeneration: [AsyncFunction (anonymous)],
getGenerationByHash: [AsyncFunction (anonymous)],
getGenerationByHeight: [AsyncFunction (anonymous)],
getAccountByPubkey: [AsyncFunction (anonymous)],
getAccountByPubkeyAndHeight: [AsyncFunction (anonymous)],
getAccountByPubkeyAndHash: [AsyncFunction (anonymous)],
getPendingAccountTransactionsByPubkey: [AsyncFunction (anonymous)],
getAccountNextNonce: [AsyncFunction (anonymous)],
protectedDryRunTxs: [AsyncFunction (anonymous)],
getTransactionByHash: [AsyncFunction (anonymous)],
getTransactionInfoByHash: [AsyncFunction (anonymous)],
postTransaction: [AsyncFunction (anonymous)],
getContract: [AsyncFunction (anonymous)],
getContractCode: [AsyncFunction (anonymous)],
getContractPoI: [AsyncFunction (anonymous)],
getOracleByPubkey: [AsyncFunction (anonymous)],
getOracleQueriesByPubkey: [AsyncFunction (anonymous)],
getOracleQueryByPubkeyAndQueryId: [AsyncFunction (anonymous)],
getNameEntryByName: [AsyncFunction (anonymous)],
getChannelByPubkey: [AsyncFunction (anonymous)],
getPeerPubkey: [AsyncFunction (anonymous)],
getStatus: [AsyncFunction (anonymous)],
getChainEnds: [AsyncFunction (anonymous)]
}
*/
```
`AeSdk` is a general, high-level interface that wraps multiple low-level interfaces. A general interface is preferred for its simplicity and resilience to breaking changes.

But there is also low-level interfaces. It's excellent for additional control, and as a teaching tool to understand the underlying operations. Most real-world requirements involves a series of low-level operations, so the SDK provides abstractions for these.

### Node API

The aeternity node exposes [a REST API]. This API is described in the [OpenAPI document]. SDK uses this document to generate a TypeScript client. The result client (implemented in [`Node` class]) a basically a mapping of all node endpoints as functions.

[a REST API]: https://api-docs.aeternity.io/
[OpenAPI document]: https://mainnet.aeternity.io/api?oas3
[`Node` class]: https://docs.aeternity.com/aepp-sdk-js/v14.0.0/api/classes/Node.html

So to get a transaction based on its hash you would invoke `node.getTransactionByHash('th_fWEsg152BNYcrqA9jDh9VVpacYojCUb1yu45zUnqhmQ3dAAC6')`. In this way the SDK is simply a mapping of the raw API calls into JavaScript.

### Transaction builder

Any blockchain state change requires signing a transaction. Transaction should be built according to the [protocol]. SDK implements it in [`buildTx`], [`buildTxAsync`], and [`unpackTx`]. [`buildTxAsync`] requires fewer arguments than [`buildTx`], but it expects the node instance provided in arguments.

[protocol]: https://github.com/aeternity/protocol/blob/c007deeac4a01e401238412801ac7084ac72d60e/serializations.md#accounts-version-1-basic-accounts
[`buildTx`]: https://docs.aeternity.com/aepp-sdk-js/v14.0.0/api/functions/buildTx.html
[`buildTxAsync`]: https://docs.aeternity.com/aepp-sdk-js/v14.0.0/api/functions/buildTxAsync.html
[`unpackTx`]: https://docs.aeternity.com/aepp-sdk-js/v14.0.0/api/functions/unpackTx.html

## High-level SDK usage (preferable)

Example spend call, using æternity's SDK abstraction:

https://github.com/aeternity/aepp-sdk-js/blob/cb80689a4aa10c3f3f0f57494c825533bbe6d01e/examples/node/_api-high-level.js#L1-L18

## Low-level SDK usage

The same spend execution, but using low-level SDK functions:

https://github.com/aeternity/aepp-sdk-js/blob/cb80689a4aa10c3f3f0f57494c825533bbe6d01e/examples/node/_api-low-level.js#L1-L19
18 changes: 18 additions & 0 deletions examples/node/_api-high-level.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { AeSdk, Node, MemoryAccount, encode, Encoding } from '@aeternity/aepp-sdk';

const aeSdk = new AeSdk({
nodes: [
{
name: 'testnet',
instance: new Node('https://testnet.aeternity.io'), // host your node for better decentralization
},
],
accounts: [new MemoryAccount('sk_2CuofqWZHrABCrM7GY95YSQn8PyFvKQadnvFnpwhjUnDCFAWmf')],
});

const transactionInfo = await aeSdk.spend(
100, // aettos
'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E',
{ payload: encode(Buffer.from('spend tx payload'), Encoding.Bytearray) },
);
console.log(transactionInfo);
19 changes: 19 additions & 0 deletions examples/node/_api-low-level.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Node, MemoryAccount, buildTxAsync, Tag, encode, Encoding } from '@aeternity/aepp-sdk';

const onNode = new Node('https://testnet.aeternity.io'); // host your node for better decentralization
const account = new MemoryAccount('sk_2CuofqWZHrABCrM7GY95YSQn8PyFvKQadnvFnpwhjUnDCFAWmf');

const spendTx = await buildTxAsync({
tag: Tag.SpendTx,
senderId: account.address,
recipientId: 'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E',
amount: 100, // aettos
payload: encode(Buffer.from('spend tx payload'), Encoding.Bytearray),
onNode,
});

const signedTx = await account.signTransaction(spendTx, { networkId: await onNode.getNetworkId() });

// broadcast the signed tx to the node
const { txHash } = await onNode.postTransaction({ tx: signedTx });
console.log(txHash);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"build:api:middleware": "autorest tooling/autorest/middleware.yaml && node tooling/autorest/postprocessing.js middleware",
"build:generate": "tsx tooling/generate-schema.ts",
"build": "run-p build:api:* build:assets build:generate && run-p build:dist build:es build:types",
"docs:examples": "node tooling/docs/examples-to-md.js examples/node/*.js",
"docs:examples": "node tooling/docs/examples-to-md.js examples/node/[^_]*.js",
"docs:api": "typedoc",
"commitlint": "commitlint --from develop",
"lint": "run-p lint:*",
Expand Down
2 changes: 2 additions & 0 deletions test/examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ run_node_example paying-for-spend-tx.js
run_node_example transfer-ae.js
run_node_example dry-run-using-debug-endpoint.js
run_node_example oracle.js
run_node_example _api-high-level.js
run_node_example _api-low-level.js

# TODO: revisit --ignore-scripts after solving https://github.com/npm/cli/issues/4202
perl -i -pe 's/"prepare"/"rem-prepare"/g' package.json
Expand Down