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:
parent
7c64a252e5
commit
0939e6192d
10
README.md
10
README.md
@ -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
|
account-abstraction and post-quantum contract surface, the seedless hybrid PQC
|
||||||
wallet, and AERE's native NIST post-quantum precompiles.
|
wallet, and AERE's native NIST post-quantum precompiles.
|
||||||
|
|
||||||
**SDK version:** 0.16.13
|
**SDK version:** 0.16.14
|
||||||
**Last updated:** 2026-10-15 (network facts, state window, client selectors; the rest is as of 2026-07-16)
|
**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
|
**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
|
clients (RaaS, Sink, Agent and x402 settlement, state channels, compliance, lending, the compliant pool) did not match
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@aere/sdk",
|
"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.",
|
"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",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|||||||
@ -242,19 +242,39 @@ export class AereClient {
|
|||||||
return () => { ws.removeAllListeners('block'); ws.destroy(); };
|
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> {
|
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 ws = new WebSocketProvider(wsUrl ?? AERE_MAINNET.ws);
|
||||||
const onBlock = async (blockNumber: number) => {
|
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;
|
if (!block) return;
|
||||||
for (const tx of block.prefetchedTransactions ?? []) {
|
for (const tx of transfersToFromRawBlock(block, addr)) handler({ ...tx, blockNumber });
|
||||||
if (tx.to?.toLowerCase() === addr.toLowerCase() && tx.value > 0n) {
|
|
||||||
handler({ hash: tx.hash, value: tx.value, from: tx.from, blockNumber });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
ws.on('block', onBlock);
|
ws.on('block', onBlock);
|
||||||
return () => { ws.off('block', onBlock); ws.destroy(); };
|
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;
|
||||||
|
}
|
||||||
|
|||||||
44
src/test/transfers-raw-block.test.ts
Normal file
44
src/test/transfers-raw-block.test.ts
Normal 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), []);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user