@asentum/sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/README.md +280 -142
- package/dist/index.d.ts +977 -174
- package/dist/index.js +1335 -308
- package/dist/index.js.map +1 -1
- package/package.json +32 -11
- package/src/client.ts +0 -272
- package/src/contract.ts +0 -138
- package/src/index.ts +0 -75
- package/src/wallet.ts +0 -352
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 (2026-10-20)
|
|
4
|
+
|
|
5
|
+
Current with node 0.8.1. Full read coverage of the node API, confirmation
|
|
6
|
+
helpers that follow a transaction from in-block to final, typed errors, and
|
|
7
|
+
ase1 addresses everywhere.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Chain heads: `getHeads()`, `getTipNumber()`, `getFinalizedNumber()` (`/v3/heads`).
|
|
12
|
+
- Blocks: `getBlock(n, { tip })`, `getBlockReceipts(n, { tip })`, `getRawBlock(n)`, `getFinalizedStatus(h)`.
|
|
13
|
+
- Receipts: `getReceipt(hash, { finalizedOnly })`; the `Receipt` type now lists `finalized`, `confirmations`, `blockHash`, `auraHash` and `error`.
|
|
14
|
+
- `waitForReceipt(hash, { until: 'included' | 'final', timeoutMs, pollIntervalMs, onIncluded, onFinal, onReorg, signal, throwOnFailure, pauseWhenHidden })`. It polls about once a second, backs off on 429 / 5xx, and reports a receipt that disappears or moves to another block (`onReorg`).
|
|
15
|
+
- `tx.wait({ until: 'final' })` and the other wait options. `tx.wait()` and `tx.wait(ms)` keep their 0.1.x meaning.
|
|
16
|
+
- Accounts: `getAccount` (`/asentum/account`: nonce, codeHash, storageRoot), `getPublicKeyStatus` (`/public-key`), `getNonce(addr, { pending: false })`.
|
|
17
|
+
- Contracts: `client.view()` (returns the value, throws `ViewError`), `blockTag` on `viewCall` / `view` / `contract.view`, `getStorageAt`, `getLogs` (decoded `eth_getLogs`, splits ranges over 10,000 blocks), `contract.getEvents`, `contract.getAccount`, `eventTopic(name)`.
|
|
18
|
+
- Validators and consensus: `getParticipation`, `getCommittee`, `getLiveness`, `getTombstones`; `getValidators` types now include the epoch and committee state.
|
|
19
|
+
- Governance reads: `getProposals`, `getProposal`, `getGovernanceLocked`, `getGovernanceWithdrawable`, `getGovernanceLibraries`, `getGovernanceLibrary`.
|
|
20
|
+
- `getApiInfo` (`/api`), `getGenesis` (`/genesis`), `getTransaction` (`eth_getTransactionByHash`).
|
|
21
|
+
- Submission: `submitRawTxBatch` (`/tx/batch`), `wallet.signTransfer` / `signCall` / `signDeploy` (sign without sending), `wallet.sendBatch`.
|
|
22
|
+
- Wei variants: `amountWei` / `valueWei` on every send, `sendTransferWei`, `sendCallWei`, and the strict `aseToWei` and `toWei` helpers.
|
|
23
|
+
- Nonce handling: the wallet allocates consecutive nonces from `pendingNonce`, serialises concurrent sends, and resyncs after a rejection (`wallet.resetNonce()`).
|
|
24
|
+
- Key-less (key-once) transactions: `keyless: 'auto' | 'always'` on the wallet or per send. Default `'off'`.
|
|
25
|
+
- Typed errors: `AsentumError` and `TxRejectedError`, `TxFailedError`, `TimeoutError`, `AbortError`, `ViewError`, `RpcError`, `JsonRpcError`, `RateLimitError`, `HttpError`, `NetworkError`.
|
|
26
|
+
- Transport options: `retries`, `requestTimeoutMs`, `backoffBaseMs`, `backoffMaxMs`, `headers`, custom `fetch`. Every method takes `{ signal, retries }`.
|
|
27
|
+
- Address helpers matching @asentum/connect: `isAse1`, `ase1ToHex`, `hexToAse1`, `toAse1`, `shortAse1`, plus `toHexAddress`, `isHexAddress`, `isAddress`, `addressToBytes`, `bytesToAse1`, `getContractAddress`.
|
|
28
|
+
- `wallet.addressHex`, `contract.addressHex`, `DEFAULT_RPC`, `GOVERNANCE_CONTRACT`, `VERSION`.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- `getBlock` returned the node's `{"error":"not found"}` object instead of `null`.
|
|
33
|
+
- `isReady` returned true for any answer, including a 503.
|
|
34
|
+
- `getBalance` and other reads returned the node's HTTP 200 error body (for example `invalid address`) as if it were data; they now throw `RpcError`.
|
|
35
|
+
- `getReceipt` returned `null` for every error, so a rate limit or a bad hash looked like "not mined yet".
|
|
36
|
+
- `parseAse(number)` lost precision (`1.1` became 1100000000000000128 wei).
|
|
37
|
+
- Every send fetched the chain id again; it is cached now.
|
|
38
|
+
- 429 and 5xx responses were not retried; the gateway's HTML 429 body broke JSON parsing.
|
|
39
|
+
- The package no longer depends on `@asentum/crypto` and `@asentum/types` from npm: their published 0.1.0 builds lack the bech32 and key-less code that the unreleased 0.1.1 source already imported, so a 0.1.1 build would not have loaded outside the monorepo. The SDK now carries the small parts it needs and depends only on `@noble/hashes`, `@noble/post-quantum` and `@chainsafe/ssz`.
|
|
40
|
+
- `tsconfig.json` no longer extends a file outside the repository.
|
|
41
|
+
|
|
42
|
+
### Changed (breaking)
|
|
43
|
+
|
|
44
|
+
- `wallet.address` is ase1 (it was checksummed 0x hex). Use `wallet.addressHex` for hex.
|
|
45
|
+
- `contract.address` is always ase1 (a deploy used to give 0x hex). Use `contract.addressHex` for hex.
|
|
46
|
+
- Receipt `sender` and `recipient` are ase1 (the node sends hex). The same for participation, committee, liveness and governance proposer addresses. Addresses inside event data and contract return values are left as the contract wrote them.
|
|
47
|
+
- `getReceipt` throws on transport and RPC errors (after retries) instead of returning `null`. `null` still means "not in a block yet".
|
|
48
|
+
- `new AsentumContract(client, address)` throws on an invalid address (it used to accept any string).
|
|
49
|
+
- Reads that get an HTTP 200 error body throw `RpcError` instead of returning the body.
|
|
50
|
+
- `contract.view` throws `ViewError` (the message format is unchanged, so `catch` blocks that read the message keep working).
|
|
51
|
+
- The default timeout of `tx.wait()` and `waitForDeploy()` is 60 s (was 15 s).
|
|
52
|
+
- `dist` no longer ships `src`; source maps still include the sources.
|
|
53
|
+
|
|
54
|
+
### Deprecated
|
|
55
|
+
|
|
56
|
+
- `client.faucet()` and `wallet.requestFaucet()`. `POST /dev/faucet` is off on every public node and only works on a local devnet that turns it on. For test ASE use the airdrop dashboard. They now return `{ accepted: false, reason }` instead of throwing on a 404.
|
|
57
|
+
|
|
58
|
+
## 0.1.1 (git only, not published to npm)
|
|
59
|
+
|
|
60
|
+
- Convert ase1 addresses in call arguments to hex before signing and in view calls.
|
|
61
|
+
- A failed transaction's receipt (which carries `error`) is returned instead of being treated as "not found".
|
|
62
|
+
- npm package metadata.
|
|
63
|
+
|
|
64
|
+
## 0.1.0
|
|
65
|
+
|
|
66
|
+
- Initial release: `AsentumClient`, `AsentumWallet`, `AsentumContract`.
|
package/README.md
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# @asentum/sdk
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for
|
|
4
|
-
in
|
|
3
|
+
TypeScript SDK for Asentum. Read the chain, sign with Dilithium3 (ML-DSA-65),
|
|
4
|
+
send transactions, follow them from "in a block" to "final", and work with
|
|
5
|
+
contracts and their events. Works in Node.js 18+ and modern browsers.
|
|
6
|
+
|
|
7
|
+
Current with node 0.8.1.
|
|
5
8
|
|
|
6
9
|
```bash
|
|
7
10
|
npm install @asentum/sdk
|
|
@@ -10,192 +13,327 @@ npm install @asentum/sdk
|
|
|
10
13
|
## Quick start
|
|
11
14
|
|
|
12
15
|
```ts
|
|
13
|
-
import { AsentumClient, AsentumWallet } from '@asentum/sdk';
|
|
16
|
+
import { AsentumClient, AsentumWallet, formatAse } from '@asentum/sdk';
|
|
14
17
|
|
|
15
18
|
const client = new AsentumClient('https://testnet.asentum.com');
|
|
16
|
-
const wallet = AsentumWallet.create(client);
|
|
17
19
|
|
|
18
|
-
//
|
|
19
|
-
await
|
|
20
|
+
// Reads need no keys
|
|
21
|
+
const { finalized, tip } = await client.getHeads();
|
|
22
|
+
const bal = await client.getBalance('ase1v0myp2de3dhj3crvkhxqlnjaw2mvp64a9efkpl');
|
|
23
|
+
console.log(formatAse(bal.balance), 'ASE at block', tip);
|
|
20
24
|
|
|
21
|
-
//
|
|
22
|
-
const
|
|
23
|
-
console.log(
|
|
25
|
+
// A wallet signs locally; keys never leave your process
|
|
26
|
+
const wallet = AsentumWallet.fromSecretKey(client, SECRET_KEY_HEX, PUBLIC_KEY_HEX);
|
|
27
|
+
console.log(wallet.address); // ase1...
|
|
24
28
|
|
|
25
|
-
// Send ASE
|
|
26
29
|
const tx = await wallet.sendTransfer({
|
|
27
|
-
to: '
|
|
28
|
-
|
|
30
|
+
to: 'ase1wtc569gfwk87hplaawr46uj4suwsx2wndgf8ue',
|
|
31
|
+
amountWei: 10n ** 18n, // 1 ASE
|
|
29
32
|
});
|
|
30
|
-
const receipt = await tx.wait();
|
|
31
|
-
|
|
33
|
+
const receipt = await tx.wait(); // in a block, about 2.5 s
|
|
34
|
+
if (!receipt.success) throw new Error(receipt.error);
|
|
35
|
+
await tx.wait({ until: 'final' }); // final, a few blocks later
|
|
32
36
|
```
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
Test ASE comes from the airdrop dashboard (see "Get Test ASE" in the docs).
|
|
39
|
+
The node's `/dev/faucet` route is off on every public node.
|
|
40
|
+
|
|
41
|
+
## Things to know about the chain
|
|
42
|
+
|
|
43
|
+
- **Two heads.** The *tip* is the newest block on the best chain. The
|
|
44
|
+
*finalized* height trails it by about 3 blocks and never changes.
|
|
45
|
+
`getHeads()` returns both.
|
|
46
|
+
- **Accepted is not included.** `POST /tx` answering `accepted: true` only
|
|
47
|
+
means the node put the transaction in its mempool. Wait for the receipt.
|
|
48
|
+
- **Receipts use `success`**, a boolean. There is no `status` on the REST
|
|
49
|
+
receipt. A failed transaction has `success: false` and an `error` reason.
|
|
50
|
+
- **Receipts read the tip by default.** A receipt with `finalized: false` is
|
|
51
|
+
in a block; rarely that block loses to a sibling and the transaction lands
|
|
52
|
+
again in a later block. Only `finalized: true` is permanent.
|
|
53
|
+
- **Addresses.** The SDK accepts `ase1...` or `0x...` everywhere and returns
|
|
54
|
+
ase1 for every address it surfaces, also where the node answers with hex
|
|
55
|
+
(receipts, participation, committee, governance). Contracts work with
|
|
56
|
+
lowercase hex internally, so the SDK converts ase1 addresses inside
|
|
57
|
+
contract arguments to hex before signing or viewing. Hashes and raw bytes
|
|
58
|
+
stay 0x hex (they have no ase1 form), and so do addresses inside contract
|
|
59
|
+
return values and event data: convert those with `toAse1` before showing
|
|
60
|
+
them.
|
|
61
|
+
- **Amounts** are wei (1 ASE = 10^18 wei) as decimal strings in responses.
|
|
62
|
+
- **Rate limits.** The public gateway allows about 25 requests a second per
|
|
63
|
+
IP and answers 429 with an HTML page; nodes limit block and receipt reads
|
|
64
|
+
to a burst of 240, then 4 a second. The SDK retries 429 and 5xx with
|
|
65
|
+
backoff (honouring Retry-After when sent) before it throws `RateLimitError`.
|
|
66
|
+
|
|
67
|
+
## AsentumClient
|
|
35
68
|
|
|
36
69
|
```ts
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
function getGreeting() {
|
|
45
|
-
return storage.get('greeting');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function setGreeting(value) {
|
|
49
|
-
storage.set('greeting', value);
|
|
50
|
-
emit('Updated', { value });
|
|
51
|
-
}
|
|
52
|
-
`);
|
|
53
|
-
console.log('contract:', contract.address);
|
|
54
|
-
|
|
55
|
-
// Initialize
|
|
56
|
-
const initTx = await contract.send('init');
|
|
57
|
-
await initTx.wait();
|
|
58
|
-
|
|
59
|
-
// Read (free, no tx)
|
|
60
|
-
const greeting = await contract.view('getGreeting');
|
|
61
|
-
console.log(greeting); // "hello world"
|
|
62
|
-
|
|
63
|
-
// Write (sends a tx)
|
|
64
|
-
const setTx = await contract.send('setGreeting', ['gm asentum']);
|
|
65
|
-
await setTx.wait();
|
|
66
|
-
|
|
67
|
-
console.log(await contract.view('getGreeting')); // "gm asentum"
|
|
70
|
+
const client = new AsentumClient(url?, {
|
|
71
|
+
retries: 3, // retries for 429 / 5xx / network errors
|
|
72
|
+
requestTimeoutMs: 30000,
|
|
73
|
+
fetch, // custom fetch, optional
|
|
74
|
+
headers: {}, // e.g. Authorization for a private RPC
|
|
75
|
+
});
|
|
68
76
|
```
|
|
69
77
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
###
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
Every method takes an optional last argument `{ signal?: AbortSignal, retries?: number }`.
|
|
79
|
+
|
|
80
|
+
### Chain and health
|
|
81
|
+
|
|
82
|
+
| Method | Route | Returns |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `getHeads()` | `GET /v3/heads` | `{ finalized: number, tip: number }` |
|
|
85
|
+
| `getTipNumber()` / `getFinalizedNumber()` | `GET /v3/heads` | `number` |
|
|
86
|
+
| `getBlockNumber()` | `GET /chain` | finalized height, `number` |
|
|
87
|
+
| `getChainInfo()` | `GET /chain` | chain id, finalized height, mempool size, header |
|
|
88
|
+
| `getChainId()` | `GET /chain` | `bigint` (cached) |
|
|
89
|
+
| `getMetadata()` | `GET /metadata` | EIP-3085 metadata |
|
|
90
|
+
| `getApiInfo()` | `GET /api` | node identity |
|
|
91
|
+
| `getGenesis()` | `GET /genesis` | genesis spec |
|
|
92
|
+
| `isHealthy()` | `GET /health` | `boolean` |
|
|
93
|
+
| `isReady()` | `GET /ready` | `boolean` (false on 503; note that node 0.8.1 reports ready even when stalled, so watch `getHeads()`) |
|
|
94
|
+
|
|
95
|
+
### Accounts
|
|
96
|
+
|
|
97
|
+
| Method | Route | Returns |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| `getBalance(addr)` | `GET /balance/<addr>` | `{ address, balance, nonce, pendingNonce }` |
|
|
100
|
+
| `getNonce(addr, { pending? })` | `GET /balance/<addr>` | `bigint`, pendingNonce by default |
|
|
101
|
+
| `getAccount(addr)` | `GET /asentum/account/<addr>` | `{ exists, balance, nonce, codeHash, storageRoot, height }` |
|
|
102
|
+
| `getPublicKeyStatus(addr)` | `GET /public-key/<addr>` | `{ recorded, canOmitKey, ... }` |
|
|
103
|
+
|
|
104
|
+
### Blocks
|
|
105
|
+
|
|
106
|
+
| Method | Route | Returns |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `getBlock(n, { tip? })` | `GET /block/<n>[?tip=1]` | `Block \| null` (`finalized`, and `confirmations`/`auraHash` on tip blocks) |
|
|
109
|
+
| `getBlockReceipts(n, { tip? })` | `GET /block/<n>/receipts` | `{ receipts, finalized?, available }` |
|
|
110
|
+
| `getRawBlock(n)` | `GET /block-raw/<n>` | SSZ hex or `null` |
|
|
111
|
+
| `getFinalizedStatus(h)` | `GET /v3/finalized?height=` | `{ finalized, auraHash? }` |
|
|
112
|
+
|
|
113
|
+
### Transactions and receipts
|
|
114
|
+
|
|
115
|
+
| Method | Route | Returns |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `getReceipt(hash, { finalizedOnly? })` | `GET /receipts/<hash>[?tip=0]` | `Receipt \| null` (`success`, `error?`, `finalized`, `confirmations`, `blockHash`, `auraHash?`) |
|
|
118
|
+
| `waitForReceipt(hash, opts)` | polls `GET /receipts` | `Receipt` (see below) |
|
|
119
|
+
| `getTransaction(hash)` | `eth_getTransactionByHash` | `EthTransaction \| null` |
|
|
120
|
+
| `submitRawTx(rawTx)` | `POST /tx` | `{ accepted, reason?, txHash? }` |
|
|
121
|
+
| `submitRawTxBatch(rawTxs)` | `POST /tx/batch` | `{ accepted, rejected, total, results }` |
|
|
122
|
+
|
|
123
|
+
### Contracts
|
|
124
|
+
|
|
125
|
+
| Method | Route | Returns |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `viewCall(contract, method, args, { blockTag? })` | `POST /view` | `{ ok, returnValue, reason?, blockNumber, finalized, ... }` |
|
|
128
|
+
| `view(contract, method, args, { blockTag? })` | `POST /view` | the return value; throws `ViewError` |
|
|
129
|
+
| `getCode(addr)` / `getContractSource(addr)` | `eth_getCode` | hex / source text |
|
|
130
|
+
| `getStorageAt(addr, key)` | `eth_getStorageAt` | stored text or `null` |
|
|
131
|
+
| `getLogs({ address, event, fromBlock, toBlock })` | `eth_getLogs` | decoded `Log[]` (finalized blocks only; ranges over 10,000 blocks are split) |
|
|
132
|
+
|
|
133
|
+
`blockTag` is `'tip'` (default; `'latest'` and `'pending'` mean the same) or
|
|
134
|
+
`'finalized'` (`'safe'`). There are no historical state reads.
|
|
135
|
+
|
|
136
|
+
### Validators, consensus, governance
|
|
137
|
+
|
|
138
|
+
| Method | Route |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `getValidators()` | `GET /validators` (about 260 KB) |
|
|
141
|
+
| `getParticipation({ lookback })` | `GET /participation` |
|
|
142
|
+
| `getCommittee({ epoch })` | `GET /v3/committee` |
|
|
143
|
+
| `getLiveness()` | `GET /consensus/liveness` |
|
|
144
|
+
| `getTombstones()` | `GET /slashing/tombstones` |
|
|
145
|
+
| `getProposals()` / `getProposal(id)` | `GET /governance/proposals`, `/governance/proposal/<id>` |
|
|
146
|
+
| `getGovernanceLocked(addr)` / `getGovernanceWithdrawable(addr)` | `GET /governance/locked/<addr>`, `/governance/withdrawable/<addr>` |
|
|
147
|
+
| `getGovernanceLibraries()` / `getGovernanceLibrary(name)` | `GET /governance/libraries`, `/governance/library/<name>` |
|
|
148
|
+
| `jsonRpc(method, params)` | `POST /` (any JSON-RPC method) |
|
|
149
|
+
|
|
150
|
+
`faucet()` is deprecated: it calls `/dev/faucet`, which only works on a local
|
|
151
|
+
devnet that turned it on.
|
|
152
|
+
|
|
153
|
+
## AsentumWallet
|
|
75
154
|
|
|
76
155
|
```ts
|
|
77
|
-
const
|
|
156
|
+
const wallet = AsentumWallet.create(client); // random key
|
|
157
|
+
const wallet2 = AsentumWallet.fromSeed(client, seed32); // deterministic
|
|
158
|
+
const wallet3 = AsentumWallet.fromSecretKey(client, skHex, pkHex);
|
|
78
159
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
await client.getNonce('0x...'); // bigint
|
|
84
|
-
await client.getReceipt('0xabc...'); // Receipt | null
|
|
85
|
-
await client.getCode('0x...'); // hex string
|
|
86
|
-
await client.getContractSource('0x...'); // JS source string | null
|
|
87
|
-
await client.viewCall('0x...', 'name'); // ViewCallResult
|
|
88
|
-
await client.getValidators(); // { count, validators[] }
|
|
89
|
-
await client.getMetadata(); // EIP-3085 metadata
|
|
90
|
-
await client.isHealthy(); // boolean
|
|
91
|
-
await client.faucet('0x...'); // TxResult (testnet only)
|
|
92
|
-
```
|
|
160
|
+
wallet.address; // ase1...
|
|
161
|
+
wallet.addressHex; // 0x... lowercase, what contracts see as msg.sender
|
|
162
|
+
wallet.secretKeyHex; // back this up like a password
|
|
163
|
+
wallet.publicKeyHex;
|
|
93
164
|
|
|
94
|
-
|
|
165
|
+
await wallet.getBalance(); // bigint wei
|
|
166
|
+
await wallet.getNonce(); // bigint, the node's pendingNonce
|
|
167
|
+
```
|
|
95
168
|
|
|
96
|
-
|
|
169
|
+
### Sending
|
|
97
170
|
|
|
98
171
|
```ts
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// From exported keys
|
|
106
|
-
const wallet3 = AsentumWallet.fromSecretKey(client, '0xsk...', '0xpk...');
|
|
172
|
+
await wallet.sendTransfer({ to, amountWei: 10n ** 18n });
|
|
173
|
+
await wallet.sendCall({ to: contract, method: 'swap', args: [...], valueWei: 0n });
|
|
174
|
+
const c = await wallet.deploy(source); // c.address is known right away
|
|
175
|
+
await c.waitForDeploy();
|
|
176
|
+
```
|
|
107
177
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
178
|
+
Every send returns `{ hash, nonce, from, contractAddress?, wait() }` once the
|
|
179
|
+
node has accepted it, and throws `TxRejectedError` (with `reason`) when it
|
|
180
|
+
did not. A rejected transaction never reaches the chain.
|
|
111
181
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
182
|
+
**Amounts.** `amountWei` / `valueWei` are exact wei. The older `amount` /
|
|
183
|
+
`value` fields go through `parseAse`: a number or decimal string is ASE
|
|
184
|
+
(`'1.5'` is 1.5 ASE), a bigint is wei, and an integer string longer than 18
|
|
185
|
+
digits is read as wei. `sendTransferWei` and `sendCallWei` take wei only.
|
|
115
186
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
187
|
+
**Nonces.** The wallet asks the node for `pendingNonce` and hands out
|
|
188
|
+
consecutive nonces itself, so back-to-back and concurrent sends from one
|
|
189
|
+
wallet get n, n+1, n+2. After a rejection it asks the node again
|
|
190
|
+
(`wallet.resetNonce()` does the same by hand). Pass `nonce` to override.
|
|
191
|
+
Submit and poll through the same RPC: `pendingNonce` is per node.
|
|
119
192
|
|
|
120
|
-
|
|
121
|
-
const contract = await wallet.deploy('function init() { ... }');
|
|
193
|
+
**Batches.** Sign first, then submit in one request:
|
|
122
194
|
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
});
|
|
195
|
+
```ts
|
|
196
|
+
const a = await wallet.signCall({ to, method: 'a' });
|
|
197
|
+
const b = await wallet.signCall({ to, method: 'b' });
|
|
198
|
+
const res = await wallet.sendBatch([a, b]); // POST /tx/batch
|
|
199
|
+
res.results[0].accepted; res.results[0].wait();
|
|
129
200
|
```
|
|
130
201
|
|
|
131
|
-
|
|
202
|
+
**Key-less transactions.** Once the chain has an account's public key on
|
|
203
|
+
record, its transactions can leave the key out and are about a third
|
|
204
|
+
smaller. `AsentumWallet.create(client, { keyless: 'auto' })` switches to that
|
|
205
|
+
as soon as `getPublicKeyStatus().canOmitKey` is true. The default is `'off'`.
|
|
132
206
|
|
|
133
|
-
|
|
207
|
+
Other overrides on every send: `gasLimit` (defaults: 21,000 for transfers,
|
|
208
|
+
2,000,000 for calls, at least 2,000,000 for deploys; contract calls need
|
|
209
|
+
at least 50,000), `maxFeePerGas` (default 1), `signal`.
|
|
134
210
|
|
|
135
|
-
|
|
136
|
-
// From a deploy
|
|
137
|
-
const contract = await wallet.deploy(source);
|
|
211
|
+
## AsentumContract
|
|
138
212
|
|
|
139
|
-
|
|
213
|
+
```ts
|
|
140
214
|
import { AsentumContract } from '@asentum/sdk';
|
|
141
|
-
const contract = new AsentumContract(client, '0xcontract...');
|
|
142
|
-
|
|
143
|
-
// Read (free)
|
|
144
|
-
const result = await contract.view('balanceOf', ['0x...']);
|
|
145
215
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
const tx = await
|
|
151
|
-
await
|
|
216
|
+
const pool = new AsentumContract(client, 'ase1v0myp2de3dhj3crvkhxqlnjaw2mvp64a9efkpl');
|
|
217
|
+
await pool.view('getPool', ['3']); // reads the tip
|
|
218
|
+
await pool.view('getPool', ['3'], { blockTag: 'finalized' });
|
|
219
|
+
await pool.viewFull('getPool', ['3']); // value, gas, events, blockNumber
|
|
220
|
+
const tx = await pool.connect(wallet).send('swapExactIn', [...], { valueWei });
|
|
221
|
+
await pool.getEvents('Swap', { fromBlock: 570000, toBlock: 571000 });
|
|
222
|
+
await pool.getSource();
|
|
223
|
+
await pool.getAccount(); // codeHash = BLAKE3 of the source
|
|
224
|
+
```
|
|
152
225
|
|
|
153
|
-
|
|
154
|
-
const signed = contract.connect(wallet);
|
|
155
|
-
await signed.send('transfer', ['0x...', '50']);
|
|
226
|
+
## Fast confirmations
|
|
156
227
|
|
|
157
|
-
|
|
158
|
-
|
|
228
|
+
A transaction is in a block about one block after you send it (roughly 2.5
|
|
229
|
+
seconds) and final a few blocks later (roughly 7 to 10 seconds). Show
|
|
230
|
+
success at the first point, act on it off chain only at the second.
|
|
159
231
|
|
|
160
|
-
|
|
161
|
-
|
|
232
|
+
```ts
|
|
233
|
+
import { TimeoutError } from '@asentum/sdk';
|
|
234
|
+
|
|
235
|
+
const tx = await contract.send('swapExactIn', args);
|
|
236
|
+
setStatus(tx.hash, 'pending');
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
await tx.wait({
|
|
240
|
+
until: 'final',
|
|
241
|
+
onIncluded: (r) => setStatus(tx.hash, r.success ? 'in-block' : 'failed'),
|
|
242
|
+
onReorg: () => setStatus(tx.hash, 'pending'), // its block lost; it will land again
|
|
243
|
+
onFinal: (r) => setStatus(tx.hash, r.success ? 'final' : 'failed'),
|
|
244
|
+
timeoutMs: 120_000,
|
|
245
|
+
signal, // AbortSignal, optional
|
|
246
|
+
});
|
|
247
|
+
} catch (e) {
|
|
248
|
+
if (e instanceof TimeoutError) setStatus(tx.hash, 'still-pending'); // not a failure
|
|
249
|
+
else throw e;
|
|
250
|
+
}
|
|
162
251
|
```
|
|
163
252
|
|
|
164
|
-
|
|
253
|
+
`client.waitForReceipt(hash, options)` is the same for any hash. Options:
|
|
254
|
+
|
|
255
|
+
| Option | Default | Meaning |
|
|
256
|
+
|---|---|---|
|
|
257
|
+
| `until` | `'included'` | `'included'`: resolve at the first receipt. `'final'`: resolve once `finalized: true`. |
|
|
258
|
+
| `timeoutMs` | 60,000 / 120,000 | Rejects with `TimeoutError` (`lastReceipt` set if it was in a block). |
|
|
259
|
+
| `pollIntervalMs` | 1,000 | Blocks are about 2.5 s apart; faster buys nothing. |
|
|
260
|
+
| `onIncluded(r)` | | In a block. Fires again if a reorg moves it to another block. |
|
|
261
|
+
| `onReorg(prev)` | | The block it was in lost to a sibling. |
|
|
262
|
+
| `onFinal(r)` | | Final (with `until: 'final'`). |
|
|
263
|
+
| `throwOnFailure` | `false` | Reject with `TxFailedError` on `success: false` instead of resolving. |
|
|
264
|
+
| `signal` | | Stop waiting (`AbortError`). The transaction is not affected. |
|
|
265
|
+
| `pauseWhenHidden` | `true` | In a browser, no polling while the tab is hidden. |
|
|
266
|
+
|
|
267
|
+
The poller backs off on 429 and 5xx (Retry-After when sent, otherwise
|
|
268
|
+
doubling up to 10 s) and keeps going through network blips.
|
|
269
|
+
|
|
270
|
+
`tx.wait()` with no argument behaves as in 0.1.x: it resolves at the first
|
|
271
|
+
receipt (in a block), including a failed one. `tx.wait(ms)` still takes a
|
|
272
|
+
timeout.
|
|
273
|
+
|
|
274
|
+
## Errors
|
|
275
|
+
|
|
276
|
+
All errors the SDK throws on purpose extend `AsentumError` and carry a
|
|
277
|
+
stable `code`:
|
|
278
|
+
|
|
279
|
+
| Class | When |
|
|
280
|
+
|---|---|
|
|
281
|
+
| `TxRejectedError` | `POST /tx` answered `accepted: false` (`reason`, `isNonceError`) |
|
|
282
|
+
| `TxFailedError` | the receipt has `success: false` (with `throwOnFailure`) |
|
|
283
|
+
| `TimeoutError` | waiting ran out (`lastReceipt`); the tx may still land |
|
|
284
|
+
| `AbortError` | your AbortSignal fired |
|
|
285
|
+
| `ViewError` | `POST /view` answered `ok: false` |
|
|
286
|
+
| `RpcError` | HTTP 200 with an `error` body (bad address, bad hash, failed governance read) |
|
|
287
|
+
| `JsonRpcError` | a JSON-RPC error object (`rpcCode`) |
|
|
288
|
+
| `RateLimitError` | HTTP 429 after retries (`retryAfterMs`) |
|
|
289
|
+
| `HttpError` | any other non-2xx (`status`, `body`) |
|
|
290
|
+
| `NetworkError` | fetch failed or timed out |
|
|
291
|
+
|
|
292
|
+
"Not found" is not an error: `getReceipt`, `getBlock`, `getTransaction`,
|
|
293
|
+
`getRawBlock` and `getProposal` return `null`.
|
|
294
|
+
|
|
295
|
+
## Helpers
|
|
165
296
|
|
|
166
297
|
```ts
|
|
167
|
-
import {
|
|
168
|
-
|
|
169
|
-
parseAse
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
298
|
+
import {
|
|
299
|
+
toAse1, toHexAddress, hexToAse1, ase1ToHex, isAse1, isHexAddress, isAddress, shortAse1,
|
|
300
|
+
ase1ArgsToHex, aseToWei, parseAse, formatAse, toWei, eventTopic, getContractAddress,
|
|
301
|
+
} from '@asentum/sdk';
|
|
302
|
+
|
|
303
|
+
toAse1('0x72f14d1509758feb87fdeb875d7255871d0329d3'); // 'ase1wtc569gfwk87hplaawr46uj4suwsx2wndgf8ue'
|
|
304
|
+
toHexAddress('ase1wtc569gfwk87hplaawr46uj4suwsx2wndgf8ue'); // '0x72f1...29d3'
|
|
305
|
+
ase1ArgsToHex(['ase1...', { owner: 'ase1...' }]); // every ase1 inside becomes 0x hex
|
|
306
|
+
aseToWei('1.5'); // 1500000000000000000n (always ASE)
|
|
307
|
+
formatAse(15n * 10n ** 17n); // '1.5'
|
|
308
|
+
eventTopic('Swap'); // topics[0] for eth_getLogs: BLAKE3 of the name
|
|
309
|
+
getContractAddress(wallet.address, 3n); // where a deploy with nonce 3 lands
|
|
175
310
|
```
|
|
176
311
|
|
|
177
|
-
##
|
|
312
|
+
## Upgrading from 0.1.x
|
|
178
313
|
|
|
179
|
-
|
|
314
|
+
See [CHANGELOG.md](./CHANGELOG.md). The short list: `wallet.address` and
|
|
315
|
+
`contract.address` are ase1 now (`addressHex` has the hex), receipt
|
|
316
|
+
`sender`/`recipient` are ase1, `getBlock` returns `null` for a missing block,
|
|
317
|
+
`getReceipt` throws on transport errors instead of returning `null`, and the
|
|
318
|
+
faucet helpers are deprecated.
|
|
180
319
|
|
|
181
|
-
|
|
182
|
-
|------------|-------------|
|
|
183
|
-
| `getBlock(n)` | `GET /block/{n}` |
|
|
184
|
-
| `getBalance(addr)` | `GET /balance/{addr}` |
|
|
185
|
-
| `viewCall(addr, method, args)` | `POST /view` |
|
|
186
|
-
| `sendTransfer/sendCall/deploy` | `POST /tx` (signed SSZ bytes) |
|
|
187
|
-
| `getCode(addr)` | `POST / eth_getCode` (JSON-RPC) |
|
|
188
|
-
| `faucet(addr)` | `POST /dev/faucet` |
|
|
320
|
+
## How it works
|
|
189
321
|
|
|
190
|
-
Transactions are
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
322
|
+
Transactions are SSZ encoded, hashed with BLAKE3, signed with ML-DSA-65
|
|
323
|
+
(Dilithium3) locally and submitted as hex to `POST /tx`. The transaction
|
|
324
|
+
hash is BLAKE3 of the signed bytes, so the SDK knows it before the node
|
|
325
|
+
answers. Reads use the node's REST routes, plus JSON-RPC for code, storage,
|
|
326
|
+
transactions and logs. See the [Node API Reference](https://asentum.com/docs/reference/api)
|
|
327
|
+
and [Fast Confirmations](https://asentum.com/docs/guides/fast-confirmations).
|
|
194
328
|
|
|
195
|
-
##
|
|
329
|
+
## Development
|
|
196
330
|
|
|
197
|
-
|
|
198
|
-
|
|
331
|
+
```bash
|
|
332
|
+
npm test # unit tests (mocked fetch)
|
|
333
|
+
npm run test:live # read-only smoke test against https://testnet.asentum.com
|
|
334
|
+
npm run typecheck
|
|
335
|
+
npm run build
|
|
336
|
+
```
|
|
199
337
|
|
|
200
338
|
## License
|
|
201
339
|
|