@aqb-foundation/aqb-agent 1.0.0 → 1.0.2
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/LICENSE +4 -16
- package/README.md +68 -20
- package/WHITEPAPER.md +2 -1
- package/client/aqb.mjs +232 -148
- package/compile.mjs +44 -0
- package/contracts/AgentQuantumCredit.sol +234 -0
- package/contracts/AgentRegistry.sol +85 -0
- package/contracts/GenesisSale.sol +89 -0
- package/contracts/MLDSAVerifier.sol +65 -0
- package/contracts/PQSmartAccount.sol +105 -0
- package/contracts/interfaces/IPQVerifier.sol +19 -0
- package/contracts/mocks/MockUSDC.sol +30 -0
- package/contracts/mocks/MockVerifier.sol +13 -0
- package/docs/CARD.md +1 -1
- package/docs/SPEC.md +5 -4
- package/docs/articles/01-payment-rail-agents.md +55 -0
- package/docs/articles/01.body +56 -0
- package/docs/articles/02-gated-transfers.md +36 -0
- package/docs/articles/03-peg-without-oracle.md +33 -0
- package/docs/articles/04-discovery-over-landing.md +42 -0
- package/docs/articles/04-discovery-over-landing.md.body +40 -0
- package/docs/articles/04-discovery-over-landing.md.title +1 -0
- package/docs/articles/05-identity-survives-key.md +27 -0
- package/docs/articles/05-identity-survives-key.md.body +25 -0
- package/docs/articles/05-identity-survives-key.md.title +1 -0
- package/docs/articles/06-what-an-agent-needs.md +30 -0
- package/docs/articles/06-what-an-agent-needs.md.body +28 -0
- package/docs/articles/06-what-an-agent-needs.md.title +1 -0
- package/docs/articles/body.json +1 -0
- package/docs/articles/body.txt +50 -0
- package/docs/articles/title.txt +1 -0
- package/docs/marketing-plan.md +55 -0
- package/docs/pq-note.md +1 -0
- package/index.js +241 -0
- package/mcp/server.mjs +118 -25
- package/package.json +40 -10
- package/registry/agent-coin.json +20 -15
- package/registry/banner.png +0 -0
- package/registry/logo.png +0 -0
- package/test/e2e.mjs +100 -0
- package/test/evm.mjs +69 -0
- package/test/local.mjs +86 -0
- package/test/pq.mjs +37 -0
- package/test/smoke.mjs +27 -0
package/LICENSE
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
3
|
+
Copyright (c) 2026 AgentQubit
|
|
4
4
|
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
11
6
|
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
14
8
|
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
1
|
# AgentQubit (AQB)
|
|
2
2
|
|
|
3
|
-
USD-pegged, agent-only credit token. Post-quantum
|
|
3
|
+
USD-pegged, agent-only credit token. Post-quantum identity. Base L2. 900B hard cap. 1 AQB = 1 real USDC-backed dollar for circulating supply.
|
|
4
|
+
|
|
5
|
+
Telegram: https://t.me/agentqubit | X: https://x.com/yss6060
|
|
4
6
|
|
|
5
7
|
## Why agents
|
|
6
|
-
Card rails need humans. ECDSA wallets die to Shor. AQB gives: ML-DSA identity, sub-cent settlement, self-discovery, stealth payers.
|
|
8
|
+
Card rails need humans. ECDSA wallets die to Shor. AQB gives: ML-DSA identity, sub-cent settlement, self-discovery, stealth payers, and a vault that redeems 1:1 in USDC.
|
|
7
9
|
|
|
8
10
|
## Discovery (canonical)
|
|
9
11
|
|
|
10
|
-
- https://
|
|
11
|
-
- https://
|
|
12
|
-
- https://
|
|
12
|
+
- https://agentqubit.pages.dev/.well-known/agent-coin.json
|
|
13
|
+
- https://agentqubit.pages.dev/.well-known/agent-card.json (cardHash committed on chain)
|
|
14
|
+
- https://agentqubit.pages.dev/llms.txt
|
|
15
|
+
- https://agentqubit.pages.dev/agents.json
|
|
16
|
+
|
|
17
|
+
Install: `npm i @aqb-foundation/aqb-agent`
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
## CLI
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx @aqb-foundation/aqb-agent --help
|
|
23
|
+
npx @aqb-foundation/aqb-agent balance 0xAGENT
|
|
24
|
+
npx @aqb-foundation/aqb-agent quote
|
|
25
|
+
npx @aqb-foundation/aqb-agent pay --to 0xAGENT --amount 0.25
|
|
26
|
+
npx @aqb-foundation/aqb-agent redeem --amount 10
|
|
27
|
+
npx @aqb-foundation/aqb-agent deploy-account
|
|
28
|
+
npx @aqb-foundation/aqb-agent register --account 0xACCOUNT --name ocr --skill "OCR batch" --endpoint https://agent.example/a2a
|
|
29
|
+
```
|
|
15
30
|
|
|
16
31
|
## Verification
|
|
17
32
|
|
|
18
|
-
All deployed contracts are source-verified on BaseScan (MIT
|
|
33
|
+
All deployed contracts are source-verified on BaseScan (MIT): token, registry, verifier, sale, account — see `registry/agent-coin.json`.
|
|
19
34
|
|
|
20
35
|
## Read first
|
|
21
36
|
- Spec (normative): [docs/SPEC.md](docs/SPEC.md)
|
|
@@ -27,21 +42,21 @@ All deployed contracts are source-verified on BaseScan (MIT license): token, reg
|
|
|
27
42
|
|
|
28
43
|
## Live on Base mainnet (chainId 8453)
|
|
29
44
|
|
|
30
|
-
- Token **$AQB**: https://basescan.org/token/0x2C54F202B565086B6d8D2226Edb67AC3E14C39d1
|
|
45
|
+
- Token / vault **$AQB**: https://basescan.org/token/0x2C54F202B565086B6d8D2226Edb67AC3E14C39d1
|
|
31
46
|
- Genesis sale (74B @ $1): https://basescan.org/address/0xeDBD6B860c37C411aeeA513cbc0656e3135f59c1
|
|
32
47
|
- Registry: https://basescan.org/address/0xAa964f6ea767f0D709b268abBFdd8B6C2Beef3eB
|
|
33
|
-
- Founder
|
|
48
|
+
- Founder account: 0x9eA52810f31768b1886cba9eAf917F4152f53042
|
|
34
49
|
- Trading pair (AQB/USDC): https://basescan.org/address/0xF61Cb078f87425933741Dd7d02F5779c030dB1A6
|
|
35
50
|
- Deployed: 2026-11-11T18:45:48.901Z
|
|
36
51
|
|
|
37
52
|
## Contracts
|
|
38
53
|
- `AgentQuantumCredit` — ERC-20, 18dp, cap 900e9, mint/redeem 1:1 USDC, agent-gated transfers
|
|
39
54
|
- `AgentRegistry` — PQ-key registration + stealth announcer
|
|
40
|
-
- `MLDSAVerifier` — FIPS 204 verify (precompile path +
|
|
41
|
-
- `PQSmartAccount` — account whose
|
|
55
|
+
- `MLDSAVerifier` — FIPS 204 verify shell (precompile path + pluggable library fallback)
|
|
56
|
+
- `PQSmartAccount` — account whose identity is a PQ key; ECDSA only fallback for execution
|
|
42
57
|
|
|
43
58
|
## Build and test
|
|
44
|
-
```
|
|
59
|
+
```bash
|
|
45
60
|
npm i
|
|
46
61
|
node compile.mjs
|
|
47
62
|
node test/pq.mjs # real ML-DSA-65 + ML-KEM-768
|
|
@@ -50,29 +65,62 @@ node test/e2e.mjs # peg, gates, cap, transfer rules
|
|
|
50
65
|
|
|
51
66
|
## Use as an agent (SDK)
|
|
52
67
|
```js
|
|
53
|
-
import { AqbAgent, discover } from "aqb-agent/client";
|
|
54
|
-
const d = await discover("
|
|
55
|
-
const me = new AqbAgent({ chain: "base", privateKey: process.env.
|
|
68
|
+
import { AqbAgent, discover } from "@aqb-foundation/aqb-agent/client";
|
|
69
|
+
const d = await discover("agentqubit.pages.dev");
|
|
70
|
+
const me = new AqbAgent({ chain: "base", privateKey: process.env.AQB_KEY, token: d.token, registry: d.registry });
|
|
56
71
|
if (!(await me.isAgent(target))) throw new Error("not an agent");
|
|
57
72
|
await me.pay(target, 25n * 10n ** 18n);
|
|
58
73
|
```
|
|
59
74
|
Buying is only ever with real dollars: `await me.fund(10_000_000n)` pulls 10 USDC and mints 10 AQB.
|
|
60
75
|
|
|
76
|
+
From a PQ account:
|
|
77
|
+
```js
|
|
78
|
+
await me.payFromAccount(me.account("0xACCOUNT"), target, 25n * 10n ** 18n);
|
|
79
|
+
await me.registerFromAccount(me.account("0xACCOUNT"), card);
|
|
80
|
+
```
|
|
81
|
+
|
|
61
82
|
## Use as an MCP server
|
|
62
83
|
```json
|
|
63
|
-
{ "mcpServers": { "aqb": { "command": "
|
|
64
|
-
"env": { "
|
|
84
|
+
{ "mcpServers": { "aqb": { "command": "npx", "args": ["-y", "@aqb-foundation/aqb-agent", "mcp"],
|
|
85
|
+
"env": { "AQB_KEY": "0x…" } } } }
|
|
65
86
|
```
|
|
66
|
-
Tools: `aqb_discover`, `aqb_is_agent`, `
|
|
87
|
+
Tools: `aqb_discover`, `aqb_balance`, `aqb_is_agent`, `aqb_quote`, `aqb_register`, `aqb_update_card`, `aqb_fund`, `aqb_pay`, `aqb_redeem`, `aqb_cash_out`, `aqb_announce_stealth`, `aqb_wellknown`.
|
|
67
88
|
|
|
68
89
|
## Post-quantum keys
|
|
69
|
-
Real ML-DSA-65 + ML-KEM-768 live in `pq/pq.mjs` (noble primitives). The account, not an EOA, is the
|
|
90
|
+
Real ML-DSA-65 + ML-KEM-768 live in `pq/pq.mjs` (noble primitives). The account, not an EOA, is the identity: `registerFromAccount`, `fundFromAccount`, `payFromAccount` accept the account path. On-chain execution currently uses the ECDSA fallback (`0x01`) because Base has no ML-DSA precompile and no fallback library is deployed; `0x02` becomes usable when an ML-DSA verifier backend is live. The PQ key still signs off-chain and is committed in the registry.
|
|
91
|
+
|
|
92
|
+
## Peg accounting (important)
|
|
93
|
+
`contracts/AgentQuantumCredit.sol` reports `backedSupply() = totalSupply - genesisUnsold`, but `genesisUnsold` only shrinks on `GenesisSale.buy`. A direct transfer from the founder wallet to a pool or agent creates circulating coins that are still counted as "unsold". The operational invariant is therefore:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
vaultUSDC * 1e12 == totalSupply - founderBalance - saleBalance
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Check it with `node --env-file=.env scripts/state-full.mjs`. Genesis coins must leave the founder wallet only through `GenesisSale`, which deposits one real USDC per coin into the vault. The founder wallet must not redeem its own allotment; that would withdraw backing without reducing circulating supply.
|
|
100
|
+
|
|
101
|
+
`scripts/repair-peg.mjs` repairs a gap by having the founder buy B USDC from the sale (self-transfer) and seed agents return R AQB, with `B + R = circulating - vault`.
|
|
102
|
+
|
|
103
|
+
## Demo
|
|
104
|
+
`node --env-file=.env scripts/demo-agent-pay.mjs` shows a registered agent paying another and the payee redeeming for USDC. Pass `--send` to broadcast.
|
|
70
105
|
|
|
71
106
|
## Deploy
|
|
72
107
|
`node deploy.mjs` — needs `PRIVATE_KEY` and gas. Deployer wallet: `0x1464BA077e5752F0F8a1264391a19E00c1392958`.
|
|
73
108
|
|
|
74
109
|
## Invariants
|
|
75
|
-
1. `
|
|
110
|
+
1. `backedSupply <= usdc.balanceOf(token) * 1e12` (with the circulating definition above)
|
|
76
111
|
2. `totalSupply <= MAX_SUPPLY`
|
|
77
112
|
3. no mint without a USDC pull; no redeem without a burn
|
|
78
113
|
4. transfers only to agents or governor-whitelisted pools
|
|
114
|
+
5. `genesisUnsold == founderBalance` when every circulating coin is backed
|
|
115
|
+
|
|
116
|
+
## Listings & PRs (open)
|
|
117
|
+
- CoinGecko request: CL1111260050 (reapply after 2026-11-26)
|
|
118
|
+
- CoinMarketCap new-asset request: submitted (Zendesk)
|
|
119
|
+
- Uniswap default token list: https://github.com/Uniswap/default-token-list/pull/2648
|
|
120
|
+
- awesome-x402: https://github.com/xpaysh/awesome-x402/pull/2205
|
|
121
|
+
- awesome-agentic-commerce: https://github.com/xpaysh/awesome-agentic-commerce/pull/41
|
|
122
|
+
- awesome-mcp-servers: https://github.com/punkpeye/awesome-mcp-servers/pull/19336
|
|
123
|
+
- Superchain token list: https://github.com/ethereum-optimism/ethereum-optimism.github.io/pull/1394
|
|
124
|
+
- Verification post: https://github.com/yss787976-ai/agent-coin/issues/1
|
|
125
|
+
- X post: https://x.com/yss6060/status/2120901091282014578
|
|
126
|
+
- Announcement release: https://github.com/yss787976-ai/agent-coin/releases/tag/v1.0.0
|
package/WHITEPAPER.md
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
- `collateralRatio()` يفضح النسبة علنًا. المعروض المتداول = الدولارات المودعة دائمًا، ومستحيل أن يتجاوزه.
|
|
25
25
|
- النتيجة: لا انحراف، لا depeg، لا بنك مركزي، لا وعد — حساب.
|
|
26
26
|
- السقف 900 مليار وحدة: سقف أصلي ثابت في الشيفرة، يكفي كل وكيل في العالم أن يعمل قرنًا. لا إصدار إضافي بعد بلوغ السقف، والإصدار الوحيد الممكن هو مقابل دولار محجوز.
|
|
27
|
+
- حصة المبتكر خارج التداول. لا تُحوَّل عملات منها مباشرة إلى pool أو وكيل إلا عبر `GenesisSale` (دولار واحد مقابل عملة واحدة)؛ التحويل المباشر يخلق معروضًا متداولًا بلا ضمان ويكسر `collateralRatio()`. الفحص التشغيلي: `vaultUSDC == totalSupply − founderBalance − saleBalance`.
|
|
27
28
|
|
|
28
29
|
## 3) سرعة ورخص
|
|
29
30
|
|
|
@@ -32,7 +33,7 @@ Base L2: كتلة ~2 ثانية، رسوم تُقاس بالسنتات، ورا
|
|
|
32
33
|
## 4) ما بعد الكم
|
|
33
34
|
|
|
34
35
|
- سلطة الحساب = تعهد مفتاح ML-DSA-65. لا يوجد ECDSA وحده كسلطة.
|
|
35
|
-
- توقيعات ML-DSA-65
|
|
36
|
+
- توقيعات ML-DSA-65 حقيقية ومُختبرة (FIPS 204). التحقق على السلسلة جاهز في `MLDSAVerifier` بمسار precompile ومكتب بديل قابل للتوصيل، لكن Base لا يوفّر precompile لـML-DSA بعد ولم يُنشر مكتب fallback؛ لذلك التنفيذ الحي للحساب يستخدم ECDSA fallback (`0x01`) بينما يبقى مفتاح PQ هو هوية الحساب ويوقّع خارج السلسلة. مسار `0x02` يعمل فور نشر backend تحقق.
|
|
36
37
|
- التنفيذ المرجعي للعملاء في `pq/pq.mjs`: توليد مفاتيح ML-DSA-65 (1952 بايت)، تواقيع (3309 بايت)، وML-KEM-768 لعناوين التخفي — مع 10 اختبارات كريبتو و20 اختبار عقد تمر كلها.
|
|
37
38
|
- دوران المفاتيح مدعوم (`rotatePQKey`) عبر الحساب نفسه فقط.
|
|
38
39
|
- ML-KEM يُستخدم لتغليف المفتاح في عنوان التخفي، والتشفير من وكيل لوكيل مستقل عن العقد.
|
package/client/aqb.mjs
CHANGED
|
@@ -1,149 +1,233 @@
|
|
|
1
|
-
// AQB agent SDK — the only client an agent needs.
|
|
2
|
-
// docs/SPEC.md is normative; this file is a thin, boring implementation of it.
|
|
3
|
-
import { Contract, JsonRpcProvider, Wallet, keccak256, toUtf8Bytes, solidityPacked } from "ethers";
|
|
4
|
-
import { newIdentity, signDigest, stealthFor, stealthCheck } from "../pq/pq.mjs";
|
|
5
|
-
export { newIdentity, stealthFor, stealthCheck };
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"function
|
|
20
|
-
"function
|
|
21
|
-
"function
|
|
22
|
-
"function
|
|
23
|
-
"function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"function
|
|
28
|
-
"function
|
|
29
|
-
"function
|
|
30
|
-
"function
|
|
31
|
-
|
|
32
|
-
];
|
|
1
|
+
// AQB agent SDK — the only client an agent needs.
|
|
2
|
+
// docs/SPEC.md is normative; this file is a thin, boring implementation of it.
|
|
3
|
+
import { Contract, JsonRpcProvider, Wallet, keccak256, toUtf8Bytes, solidityPacked } from "ethers";
|
|
4
|
+
import { newIdentity, signDigest, stealthFor, stealthCheck } from "../pq/pq.mjs";
|
|
5
|
+
export { newIdentity, stealthFor, stealthCheck };
|
|
6
|
+
|
|
7
|
+
export const CHAINS = { base: 8453, "base-sepolia": 84532 };
|
|
8
|
+
export const USDC = {
|
|
9
|
+
8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
10
|
+
84532: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
11
|
+
};
|
|
12
|
+
export const DEFAULT_RPC = {
|
|
13
|
+
8453: "https://mainnet.base.org",
|
|
14
|
+
84532: "https://sepolia.base.org",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const ABI_TOKEN = [
|
|
18
|
+
"function mint(uint256 usd6) returns (uint256)",
|
|
19
|
+
"function redeem(uint256 aqb18) returns (uint256)",
|
|
20
|
+
"function balanceOf(address) view returns (uint256)",
|
|
21
|
+
"function collateralRatio() view returns (uint256)",
|
|
22
|
+
"function transfer(address,uint256) returns (bool)",
|
|
23
|
+
"function totalSupply() view returns (uint256)",
|
|
24
|
+
];
|
|
25
|
+
const ABI_REG = [
|
|
26
|
+
"function register(bytes32 pqKeyHash, bytes32 cardHash)",
|
|
27
|
+
"function isAgent(address) view returns (bool)",
|
|
28
|
+
"function updateCard(bytes32 cardHash)",
|
|
29
|
+
"function announce(address stealth, bytes ephPubKey, bytes1 viewTag, bytes32 meta)",
|
|
30
|
+
"function agentCount() view returns (uint256)",
|
|
31
|
+
];
|
|
33
32
|
const ABI_USDC = ["function approve(address,uint256) returns (bool)", "function balanceOf(address) view returns (uint256)"];
|
|
34
|
-
const ABI_ACCOUNT = [
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ----
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
33
|
+
const ABI_ACCOUNT = [
|
|
34
|
+
"function digest(address,uint256,bytes,uint256) view returns (bytes32)",
|
|
35
|
+
"function nonce() view returns (uint256)",
|
|
36
|
+
"function execute(address,uint256,bytes,bytes)",
|
|
37
|
+
"function pqKeyHash() view returns (bytes32)",
|
|
38
|
+
];
|
|
39
|
+
const ABI_PAIR = [
|
|
40
|
+
"function getReserves() view returns (uint112,uint112,uint32)",
|
|
41
|
+
"function token0() view returns (address)",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/// ML-DSA-65 keypair stand-in: `pq` may be any FIPS-204 implementation.
|
|
45
|
+
/// pubKey must be 1952 bytes, sig 3309 bytes, scheme tag 0x02.
|
|
46
|
+
export function keyHash(pubKey) {
|
|
47
|
+
return keccak256(pubKey);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Recursively sort object keys so a card has one deterministic JSON form.
|
|
51
|
+
function canonicalize(value) {
|
|
52
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
53
|
+
if (value && typeof value === "object") {
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const key of Object.keys(value).sort()) out[key] = canonicalize(value[key]);
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Deterministic 32-byte agent card commitment (SPEC §1.4).
|
|
62
|
+
/// Nested objects are sorted too; a shallow JSON.stringify replacer is not enough.
|
|
63
|
+
export function cardHash(card) {
|
|
64
|
+
return keccak256(toUtf8Bytes(JSON.stringify(canonicalize(card))));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Stealth address + one-byte view tag (SPEC §5.2).
|
|
68
|
+
/// Matches pq.stealthFor(): h = keccak256(sharedSecret || ephPubKey).
|
|
69
|
+
export function stealth(ephPubKey, sharedSecret) {
|
|
70
|
+
const h = keccak256(solidityPacked(["bytes32", "bytes"], [sharedSecret, ephPubKey]));
|
|
71
|
+
return {
|
|
72
|
+
addr: ("0x" + h.slice(26)).toLowerCase(),
|
|
73
|
+
viewTag: "0x" + h.slice(2, 4),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class AqbAgent {
|
|
78
|
+
constructor({ rpc, chain = "base", privateKey, token, registry }) {
|
|
79
|
+
this.chainId = typeof chain === "number" ? chain : CHAINS[chain];
|
|
80
|
+
this.provider = new JsonRpcProvider(rpc || DEFAULT_RPC[this.chainId], this.chainId, { staticNetwork: true });
|
|
81
|
+
this.signer = privateKey ? new Wallet(privateKey, this.provider) : null;
|
|
82
|
+
this.tokenAddr = token;
|
|
83
|
+
this.registryAddr = registry;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
requireSigner() {
|
|
87
|
+
if (!this.signer) throw new Error("read-only client: pass privateKey");
|
|
88
|
+
return this.signer;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
token(ro = false) {
|
|
92
|
+
return new Contract(this.tokenAddr, ABI_TOKEN, ro ? this.provider : this.requireSigner());
|
|
93
|
+
}
|
|
94
|
+
registry(ro = false) {
|
|
95
|
+
return new Contract(this.registryAddr, ABI_REG, ro ? this.provider : this.requireSigner());
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---- read
|
|
99
|
+
usdBalance(a = this.signer?.address) {
|
|
100
|
+
if (!a) throw new Error("address required");
|
|
101
|
+
return new Contract(USDC[this.chainId], ABI_USDC, this.provider).balanceOf(a);
|
|
102
|
+
}
|
|
103
|
+
isAgent(a = this.signer?.address) {
|
|
104
|
+
if (!a) throw new Error("address required");
|
|
105
|
+
return this.registry(true).isAgent(a);
|
|
106
|
+
}
|
|
107
|
+
balance(a = this.signer?.address) {
|
|
108
|
+
if (!a) throw new Error("address required");
|
|
109
|
+
return this.token(true).balanceOf(a);
|
|
110
|
+
}
|
|
111
|
+
ratio() { return this.token(true).collateralRatio(); }
|
|
112
|
+
|
|
113
|
+
/// Uniswap V2 spot quote: how much USDC out for `amount` AQB (18dp).
|
|
114
|
+
async poolQuote(pair, amount = 10n ** 18n) {
|
|
115
|
+
const c = new Contract(pair, ABI_PAIR, this.provider);
|
|
116
|
+
const [r0, r1] = await c.getReserves();
|
|
117
|
+
const token0 = (await c.token0()).toLowerCase();
|
|
118
|
+
const [rAqb, rUsd] = token0 === this.tokenAddr.toLowerCase() ? [r0, r1] : [r1, r0];
|
|
119
|
+
if (rAqb === 0n || rUsd === 0n) return { amount, usdc: 0n, reserves: { aqb: 0n, usdc: 0n } };
|
|
120
|
+
const inWithFee = amount * 997n;
|
|
121
|
+
const out = (BigInt(rUsd) * inWithFee) / (BigInt(rAqb) * 1000n + inWithFee);
|
|
122
|
+
return { amount, usdc: out, reserves: { aqb: rAqb, usdc: rUsd } };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---- write (EOA; the EOA must already be an agent to mint/redeem)
|
|
126
|
+
async register() {
|
|
127
|
+
throw new Error(
|
|
128
|
+
"AgentRegistry.register must be called by a PQSmartAccount; use registerFromAccount(account, card) or `aqb-agent register --account 0x...`"
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async fund(usd) {
|
|
133
|
+
const usdc = new Contract(USDC[this.chainId], ABI_USDC, this.requireSigner());
|
|
134
|
+
await (await usdc.approve(this.tokenAddr, usd)).wait();
|
|
135
|
+
return (await (await this.token().mint(usd)).wait()).hash;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async cashOut(aqb) {
|
|
139
|
+
return (await (await this.token().redeem(aqb)).wait()).hash;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async pay(to, aqb) {
|
|
143
|
+
return (await (await this.token().transfer(to, aqb)).wait()).hash;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ---- PQ smart-account path
|
|
147
|
+
account(addr, identity = null) {
|
|
148
|
+
const c = new Contract(addr, ABI_ACCOUNT, this.requireSigner());
|
|
149
|
+
return { c, identity, addr };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_account(acc) {
|
|
153
|
+
return typeof acc === "string" ? this.account(acc) : acc;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/// Execute through a PQSmartAccount. `scheme` is "ecdsa" (0x01, current
|
|
157
|
+
/// on-chain path) or "pq" (0x02, requires a live ML-DSA verifier).
|
|
158
|
+
async exec(acc, target, data, scheme = "ecdsa", value = 0n) {
|
|
159
|
+
const a = this._account(acc);
|
|
160
|
+
const nonce = await a.c.nonce();
|
|
161
|
+
const digest = await a.c.digest(target, value, data, nonce);
|
|
162
|
+
let sig;
|
|
163
|
+
if (scheme === "pq") {
|
|
164
|
+
if (!a.identity) throw new Error("pq scheme needs an identity");
|
|
165
|
+
sig = signDigest(a.identity, digest);
|
|
166
|
+
} else {
|
|
167
|
+
const s = this.requireSigner().signingKey.sign(digest);
|
|
168
|
+
sig = "0x01" + s.r.slice(2) + s.s.slice(2) + s.v.toString(16).padStart(2, "0");
|
|
169
|
+
}
|
|
170
|
+
const tx = await a.c.execute(target, value, data, sig);
|
|
171
|
+
return (await tx.wait()).hash;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async execPQ(acc, target, data, value = 0n) {
|
|
175
|
+
return this.exec(acc, target, data, "pq", value);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async registerFromAccount(acc, card, scheme = "ecdsa") {
|
|
179
|
+
const a = this._account(acc);
|
|
180
|
+
const pqKeyHash = await a.c.pqKeyHash();
|
|
181
|
+
const data = this.registry(true).interface.encodeFunctionData("register", [pqKeyHash, cardHash(card)]);
|
|
182
|
+
return this.exec(a, this.registryAddr, data, scheme);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async registerViaAccount(accountAddr, card, scheme = "ecdsa") {
|
|
186
|
+
return this.registerFromAccount(accountAddr, card, scheme);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async updateCardFromAccount(acc, card, scheme = "ecdsa") {
|
|
190
|
+
const data = this.registry(true).interface.encodeFunctionData("updateCard", [cardHash(card)]);
|
|
191
|
+
return this.exec(acc, this.registryAddr, data, scheme);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async fundFromAccount(acc, usd, scheme = "ecdsa") {
|
|
195
|
+
const usdc = new Contract(USDC[this.chainId], ABI_USDC, this.provider);
|
|
196
|
+
await this.exec(acc, USDC[this.chainId], usdc.interface.encodeFunctionData("approve", [this.tokenAddr, usd]), scheme);
|
|
197
|
+
return this.exec(acc, this.tokenAddr, this.token().interface.encodeFunctionData("mint", [usd]), scheme);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async payFromAccount(acc, to, aqb, scheme = "ecdsa") {
|
|
201
|
+
return this.exec(acc, this.tokenAddr, this.token().interface.encodeFunctionData("transfer", [to, aqb]), scheme);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async redeemFromAccount(acc, aqb, scheme = "ecdsa") {
|
|
205
|
+
return this.exec(acc, this.tokenAddr, this.token().interface.encodeFunctionData("redeem", [aqb]), scheme);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async cashOutFromAccount(acc, aqb, scheme = "ecdsa") {
|
|
209
|
+
return this.redeemFromAccount(acc, aqb, scheme);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async announceFromAccount(acc, ephPubKey, sharedSecret, metaHash = "0x" + "00".repeat(32), scheme = "ecdsa") {
|
|
213
|
+
const { addr, viewTag } = stealth(ephPubKey, sharedSecret);
|
|
214
|
+
const data = this.registry(true).interface.encodeFunctionData("announce", [addr, ephPubKey, viewTag, metaHash]);
|
|
215
|
+
return this.exec(acc, this.registryAddr, data, scheme);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// Discovery: read /.well-known/agent-coin.json from a host, or emit your own.
|
|
220
|
+
export async function discover(host) {
|
|
221
|
+
const base = host.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
222
|
+
const r = await fetch(`https://${base}/.well-known/agent-coin.json`);
|
|
223
|
+
if (!r.ok) throw new Error(`no AQB discovery at ${host}`);
|
|
224
|
+
return r.json();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function wellKnown({ token, registry, verifier, chainId = 8453, vault, pool, sale }) {
|
|
228
|
+
return {
|
|
229
|
+
aqb: 1, chainId, token, registry, verifier,
|
|
230
|
+
vault: vault || token, pool, sale,
|
|
231
|
+
pq: "ML-DSA-65", kem: "ML-KEM-768",
|
|
232
|
+
};
|
|
233
|
+
}
|
package/compile.mjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Compile check: solc standard-JSON, optimized, no framework.
|
|
2
|
+
import solc from "solc";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
const root = "contracts";
|
|
7
|
+
const sources = {};
|
|
8
|
+
(function walk(dir) {
|
|
9
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
10
|
+
const p = path.join(dir, e.name);
|
|
11
|
+
if (e.isDirectory()) walk(p);
|
|
12
|
+
else if (p.endsWith(".sol"))
|
|
13
|
+
sources[p.replace(/\\/g, "/")] = { content: fs.readFileSync(p, "utf8") };
|
|
14
|
+
}
|
|
15
|
+
})(root);
|
|
16
|
+
|
|
17
|
+
const input = {
|
|
18
|
+
language: "Solidity",
|
|
19
|
+
sources,
|
|
20
|
+
settings: {
|
|
21
|
+
optimizer: { enabled: true, runs: 200 },
|
|
22
|
+
evmVersion: "cancun",
|
|
23
|
+
outputSelection: { "*": { "*": ["abi", "evm.bytecode.object", "evm.deployedBytecode.object"] } },
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const out = JSON.parse(solc.compile(JSON.stringify(input)));
|
|
28
|
+
const errs = (out.errors || []).filter((e) => e.severity === "error");
|
|
29
|
+
for (const e of out.errors || []) {
|
|
30
|
+
if (e.severity !== "error") continue;
|
|
31
|
+
console.log("ERR", e.formattedMessage);
|
|
32
|
+
}
|
|
33
|
+
if (errs.length) process.exit(1);
|
|
34
|
+
|
|
35
|
+
fs.mkdirSync("artifacts", { recursive: true });
|
|
36
|
+
for (const [file, ctrs] of Object.entries(out.contracts)) {
|
|
37
|
+
for (const [name, c] of Object.entries(ctrs)) {
|
|
38
|
+
fs.writeFileSync(`artifacts/${name}.json`, JSON.stringify({ file, abi: c.abi, bytecode: "0x" + c.evm.bytecode.object }, null, 1));
|
|
39
|
+
const size = c.evm.deployedBytecode.object.length / 2;
|
|
40
|
+
if (size > 24576) { console.log(`SIZE TOO BIG ${name} ${size}`); process.exitCode = 1; }
|
|
41
|
+
else console.log(`ok ${name} ${size}B`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
console.log("OK");
|