0.16.14: watchTransfersTo reads the block as the node serves it, so a block carrying a post-quantum transaction (type 0x50, AIP-20) no longer throws and a deposit made with one is credited; transfersToFromRawBlock exported and tested offline on the real transaction of block 19,900,028

This commit is contained in:
Aere Network 2026-10-20 12:53:50 +03:00
parent 7c64a252e5
commit 0939e6192d
4 changed files with 80 additions and 10 deletions

View File

@ -5,8 +5,14 @@ live AERE L1 (chain ID 2800): the DeFi, agentic-settlement, compliance,
account-abstraction and post-quantum contract surface, the seedless hybrid PQC
wallet, and AERE's native NIST post-quantum precompiles.
**SDK version:** 0.16.13
**Last updated:** 2026-10-15 (network facts, state window, client selectors; the rest is as of 2026-07-16)
**SDK version:** 0.16.14
**Last updated:** 2026-10-20 (deposit watching on post-quantum transactions; network facts, state window, client selectors 2026-10-15; the rest is as of 2026-07-16)
**0.16.14, 2026-10-20: `watchTransfersTo` reads post-quantum transactions.** Until this release it asked ethers for the block with
full transactions; ethers does not know the post-quantum transaction type 0x50 (AIP-20), so on a block carrying one (the first on chain
2800 is block 19,900,028) the handler threw, every deposit of that block was missed, and a deposit made with a post-quantum transaction
was never credited. The block is now read as the node serves it (`eth_getBlockByNumber`), and the filter is the exported
`transfersToFromRawBlock(block, addr)`, tested offline on the real transaction of that block (`src/test/transfers-raw-block.test.ts`).
**0.16.13, 2026-10-15: client selectors repaired.** Until this release about 130 hand-written function selectors in ten
clients (RaaS, Sink, Agent and x402 settlement, state channels, compliance, lending, the compliant pool) did not match

View File

@ -1,6 +1,6 @@
{
"name": "@aere/sdk",
"version": "0.16.13",
"version": "0.16.14",
"description": "Official AERE Network SDK: typed ethers v6 clients for AERE L1 (chain ID 2800). 35+ typed clients over a 130+ contract address book, covering core DeFi, agentic x402 settlement, compliance, ERC-4337 account abstraction, t-of-n post-quantum custody, the 5 live NIST PQC precompiles (Falcon-512/1024, ML-DSA-44, SLH-DSA-128s, SHAKE256), and a seedless hybrid PQC wallet.",
"type": "module",
"main": "./dist/index.js",

View File

@ -242,19 +242,39 @@ export class AereClient {
return () => { ws.removeAllListeners('block'); ws.destroy(); };
}
/** Watch native AERE deposits to a specific address. Useful for "credit user's neobank account when they receive AERE". */
/**
* Watch native AERE deposits to a specific address. Useful for "credit user's neobank account when they receive AERE".
* The block is read as the node serves it (eth_getBlockByNumber), not decoded by ethers: ethers does not know the post-quantum
* transaction type 0x50 (AIP-20), so until 0.16.14 a block carrying one threw, every deposit in that block was missed, and a deposit
* made WITH a post-quantum transaction was never credited.
*/
async watchTransfersTo(addr: string, handler: (tx: { hash: string; value: bigint; from: string; blockNumber: number }) => void, wsUrl?: string): Promise<() => void> {
const ws = new WebSocketProvider(wsUrl ?? AERE_MAINNET.ws);
const onBlock = async (blockNumber: number) => {
const block = await ws.getBlock(blockNumber, true);
const block = await ws.send('eth_getBlockByNumber', [ethers.toQuantity(blockNumber), true]);
if (!block) return;
for (const tx of block.prefetchedTransactions ?? []) {
if (tx.to?.toLowerCase() === addr.toLowerCase() && tx.value > 0n) {
handler({ hash: tx.hash, value: tx.value, from: tx.from, blockNumber });
}
}
for (const tx of transfersToFromRawBlock(block, addr)) handler({ ...tx, blockNumber });
};
ws.on('block', onBlock);
return () => { ws.off('block', onBlock); ws.destroy(); };
}
}
/**
* The native AERE transfers to `addr` in a block as the node serves it (eth_getBlockByNumber with full transactions): every transaction
* type, the post-quantum type 0x50 (AIP-20) included, since only hash, from, to and value are read. A zero value or a contract creation
* (no `to`) is not a transfer.
*/
export function transfersToFromRawBlock(
block: { transactions?: Array<{ hash: string; from: string; to?: string | null; value?: string }> },
addr: string,
): Array<{ hash: string; value: bigint; from: string }> {
const want = addr.toLowerCase();
const out: Array<{ hash: string; value: bigint; from: string }> = [];
for (const tx of block.transactions ?? []) {
if (typeof tx !== 'object' || !tx.to || tx.to.toLowerCase() !== want) continue;
const value = BigInt(tx.value ?? '0x0');
if (value > 0n) out.push({ hash: tx.hash, value, from: ethers.getAddress(tx.from) });
}
return out;
}

View File

@ -0,0 +1,44 @@
// Offline tests for transfersToFromRawBlock (0.16.14): watchTransfersTo reads the block as the node serves it, so a block carrying the
// post-quantum transaction type 0x50 (AIP-20) no longer throws, and a deposit made WITH such a transaction is credited.
// The first fixture is the real transaction of chain 2800 block 19,900,028 (2026-09-23), trimmed to the fields the function reads; on that
// block ethers' getBlock(n, true) throws "missing r", which is what made every deposit of the block disappear before 0.16.14.
//
// Run: npm run build && node --test dist/test/transfers-raw-block.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { transfersToFromRawBlock } from '../client.js';
const REAL_0X50 = { hash: '0x7cb5bdaceb475a6fee881c6a68aadf69734e78c1fd5648865c4fdde243d602e5', from: '0xa5bd56abf32ab382678e311c12fe14b736153f0c',
to: '0xa5bd56abf32ab382678e311c12fe14b736153f0c', value: '0x0', type: '0x50', algId: '0x2', nonce: '0x0' };
const USER = '0x1111111111111111111111111111111111111111';
const OTHER = '0x2222222222222222222222222222222222222222';
test('the real 0x50 block of 2800 is read without throwing; its self-transfer of zero is not a deposit', () => {
assert.deepEqual(transfersToFromRawBlock({ transactions: [REAL_0X50] }, REAL_0X50.to), []);
});
test('a deposit made WITH a post-quantum transaction (type 0x50) is credited, with its exact value and a checksummed sender', () => {
const tx = { ...REAL_0X50, hash: '0x' + 'ab'.repeat(32), to: USER, value: '0xde0b6b3a7640000' };
const got = transfersToFromRawBlock({ transactions: [tx] }, USER.toUpperCase().replace('0X', '0x'));
assert.equal(got.length, 1);
assert.equal(got[0].value, 10n ** 18n);
assert.equal(got[0].from, '0xa5bD56abf32Ab382678e311C12FE14b736153f0c');
});
test('legacy and EIP-1559 deposits to the address are credited; other recipients, zero values and contract creations are not', () => {
const txs = [
{ hash: '0x01', from: OTHER, to: USER, value: '0x5', type: '0x0' },
{ hash: '0x02', from: OTHER, to: USER, value: '0x7', type: '0x2' },
{ hash: '0x03', from: USER, to: OTHER, value: '0x9', type: '0x2' },
{ hash: '0x04', from: OTHER, to: USER, value: '0x0', type: '0x2' },
{ hash: '0x05', from: OTHER, to: null, value: '0x9', type: '0x2' },
];
const got = transfersToFromRawBlock({ transactions: txs }, USER);
assert.deepEqual(got.map((x) => [x.hash, x.value]), [['0x01', 5n], ['0x02', 7n]]);
});
test('a block with only transaction hashes (no full transactions) or none at all gives nothing, never throws', () => {
assert.deepEqual(transfersToFromRawBlock({ transactions: ['0x01' as unknown as never] }, USER), []);
assert.deepEqual(transfersToFromRawBlock({}, USER), []);
});