@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 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 building on Asentum. Three classes, one import, works
4
- in Node.js and browsers.
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
- // Fund from the testnet faucet
19
- await wallet.requestFaucet();
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
- // Check balance
22
- const balance = await wallet.getBalance();
23
- console.log(balance); // 100000000000000000000n (100 ASE)
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: '0x1111111111111111111111111111111111111111',
28
- amount: '10',
30
+ to: 'ase1wtc569gfwk87hplaawr46uj4suwsx2wndgf8ue',
31
+ amountWei: 10n ** 18n, // 1 ASE
29
32
  });
30
- const receipt = await tx.wait();
31
- console.log('confirmed in block', receipt.blockNumber);
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
- ## Deploy and interact with a contract
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
- // Deploy
38
- const contract = await wallet.deploy(`
39
- function init() {
40
- storage.set('greeting', 'hello world');
41
- emit('Init', { sender: msg.sender });
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
- ## API
71
-
72
- ### `AsentumClient`
73
-
74
- Read-only interface to the chain. No keys, no signing: just reads.
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 client = new AsentumClient('https://testnet.asentum.com');
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
- await client.getBlockNumber(); // number
80
- await client.getChainId(); // bigint
81
- await client.getBlock(42); // Block | null
82
- await client.getBalance('0x...'); // { address, balance, nonce }
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
- ### `AsentumWallet`
165
+ await wallet.getBalance(); // bigint wei
166
+ await wallet.getNonce(); // bigint, the node's pendingNonce
167
+ ```
95
168
 
96
- A client + Dilithium3 keypair. Can sign and submit transactions.
169
+ ### Sending
97
170
 
98
171
  ```ts
99
- // Create random
100
- const wallet = AsentumWallet.create(client);
101
-
102
- // From seed (deterministic)
103
- const wallet2 = AsentumWallet.fromSeed(client, seed32bytes);
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
- wallet.address; // '0x...' checksummed
109
- wallet.secretKeyHex; // for backup
110
- wallet.publicKeyHex;
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
- await wallet.getBalance(); // bigint (wei)
113
- await wallet.getNonce(); // bigint
114
- await wallet.requestFaucet(); // TxResult
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
- // Send transfer
117
- const tx = await wallet.sendTransfer({ to: '0x...', amount: '1.5' });
118
- const receipt = await tx.wait();
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
- // Deploy contract
121
- const contract = await wallet.deploy('function init() { ... }');
193
+ **Batches.** Sign first, then submit in one request:
122
194
 
123
- // Call contract (write)
124
- const tx2 = await wallet.sendCall({
125
- to: '0x...',
126
- method: 'transfer',
127
- args: ['0xbob...', '100'],
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
- ### `AsentumContract`
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
- A deployed contract with view + send helpers.
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
- ```ts
136
- // From a deploy
137
- const contract = await wallet.deploy(source);
211
+ ## AsentumContract
138
212
 
139
- // Or wrap an existing address
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
- // Read with full result (includes gas + events)
147
- const full = await contract.viewFull('balanceOf', ['0x...']);
148
-
149
- // Write (needs wallet)
150
- const tx = await contract.send('transfer', ['0x...', '100']);
151
- await tx.wait();
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
- // Attach a wallet for future send() calls
154
- const signed = contract.connect(wallet);
155
- await signed.send('transfer', ['0x...', '50']);
226
+ ## Fast confirmations
156
227
 
157
- // Get source code
158
- const source = await contract.getSource();
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
- // Wait for deploy confirmation
161
- await contract.waitForDeploy();
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
- ### Helpers
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 { parseAse, formatAse } from '@asentum/sdk';
168
-
169
- parseAse('1'); // 1000000000000000000n (1 ASE in wei)
170
- parseAse('0.5'); // 500000000000000000n
171
- parseAse(100n); // 100n (already wei)
172
-
173
- formatAse(1500000000000000000n); // "1.5"
174
- formatAse('1000000000000000000'); // "1"
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
- ## How it works
312
+ ## Upgrading from 0.1.x
178
313
 
179
- The SDK wraps the node's HTTP RPC endpoints:
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
- | SDK method | RPC endpoint |
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 built via SSZ schemas from `@asentum/types`, hashed
191
- with BLAKE3, signed with Dilithium3 from `@asentum/crypto`, and
192
- submitted as hex-encoded wire bytes. The entire signing pipeline runs
193
- locally: private keys never leave your process.
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
- ## Requirements
329
+ ## Development
196
330
 
197
- - Node.js 22+ or any modern browser with `fetch` and `crypto.getRandomValues`
198
- - An Asentum RPC endpoint (the public testnet is at `https://testnet.asentum.com`)
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