Skip to content

Commit 5e82589

Browse files
author
azeth-sync[bot]
committed
v0.2.5: sync from monorepo 2026-03-07
1 parent b1a2a92 commit 5e82589

4 files changed

Lines changed: 251 additions & 7 deletions

File tree

README.md

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,26 @@ npm install @azeth/sdk
1010
pnpm add @azeth/sdk
1111
```
1212

13-
Peer dependency: `viem`.
14-
1513
## Quick Start
1614

1715
```typescript
1816
import { AzethKit } from '@azeth/sdk';
17+
import { TOKENS } from '@azeth/common';
1918

2019
const kit = await AzethKit.create({
2120
privateKey: process.env.AZETH_PRIVATE_KEY as `0x${string}`,
2221
chain: 'baseSepolia',
23-
bundlerUrl: `https://api.pimlico.io/v2/84532/rpc?apikey=${process.env.PIMLICO_API_KEY}`,
2422
});
2523

2624
try {
25+
// Deploy a smart account (gas is auto-sponsored, no ETH needed)
26+
await kit.createAccount({
27+
name: 'MyAgent',
28+
entityType: 'agent',
29+
description: 'A demo agent',
30+
capabilities: ['general'],
31+
});
32+
2733
// Check balances across all accounts with USD values
2834
const balances = await kit.getAllBalances();
2935
console.log('Total:', balances.grandTotalUSDFormatted);
@@ -32,7 +38,7 @@ try {
3238
await kit.transfer({
3339
to: '0xRecipient...' as `0x${string}`,
3440
amount: 1_000_000n, // 1 USDC
35-
token: '0xUSDC...' as `0x${string}`,
41+
token: TOKENS.baseSepolia.USDC,
3642
});
3743

3844
// Pay for an x402 service
@@ -47,6 +53,8 @@ try {
4753
}
4854
```
4955

56+
Only `privateKey` and `chain` are required. Gas sponsorship, bundler, and server URL are handled automatically via the Azeth server at `api.azeth.ai`. For production or custom infrastructure, see [Configuration](#configuration) below.
57+
5058
## Key Features
5159

5260
- **Smart Accounts** -- Deploy ERC-4337 smart accounts with guardian guardrails (spending limits, whitelists, timelocks) via `createAccount()`
@@ -130,6 +138,33 @@ try {
130138
}
131139
```
132140

141+
## Configuration
142+
143+
The minimal config is just `privateKey` + `chain`. Everything else has smart defaults:
144+
145+
```typescript
146+
const kit = await AzethKit.create({
147+
privateKey: '0x...', // Required
148+
chain: 'baseSepolia', // Required
149+
// All below are optional:
150+
serverUrl: 'https://api.azeth.ai', // Default — handles gasless relay + bundler proxy
151+
bundlerUrl: undefined, // Falls back to server bundler proxy
152+
paymasterUrl: undefined, // Falls back to server paymaster
153+
rpcUrl: undefined, // Falls back to public RPC for the chain
154+
});
155+
```
156+
157+
For production with your own infrastructure:
158+
159+
```typescript
160+
const kit = await AzethKit.create({
161+
privateKey: '0x...',
162+
chain: 'base',
163+
bundlerUrl: `https://api.pimlico.io/v2/8453/rpc?apikey=${PIMLICO_KEY}`,
164+
paymasterUrl: `https://api.pimlico.io/v2/8453/rpc?apikey=${PIMLICO_KEY}`,
165+
});
166+
```
167+
133168
## Full Documentation
134169

135170
See [docs/sdk.md](../../docs/sdk.md) for the comprehensive API reference with all method signatures, parameter types, return types, and detailed descriptions.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@azeth/sdk",
3-
"version": "0.2.4",
3+
"version": "0.2.5",
44
"type": "module",
55
"description": "TypeScript SDK for the Azeth trust infrastructure — smart accounts, x402 payments, reputation, and service discovery",
66
"license": "MIT",
@@ -38,7 +38,7 @@
3838
"test:mutation": "npx stryker run"
3939
},
4040
"dependencies": {
41-
"@azeth/common": "^0.2.4",
41+
"@azeth/common": "^0.2.5",
4242
"@x402/core": "^2.4.0",
4343
"@x402/extensions": "^2.4.0",
4444
"@xmtp/agent-sdk": "^2.2.0",

src/account/gasless.ts

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import {
2+
type PublicClient,
3+
type WalletClient,
4+
type Chain,
5+
type Transport,
6+
type Account,
7+
keccak256,
8+
concatHex,
9+
pad,
10+
toBytes,
11+
} from 'viem';
12+
import { AzethFactoryAbi } from '@azeth/common/abis';
13+
import {
14+
AzethError,
15+
AZETH_FACTORY_DOMAIN,
16+
CREATE_ACCOUNT_TYPES,
17+
type AzethContractAddresses,
18+
} from '@azeth/common';
19+
import { requireAddress } from '../utils/addresses.js';
20+
import { withRetry } from '../utils/retry.js';
21+
import type { CreateAccountParams, CreateAccountResult } from './create.js';
22+
23+
/** Result from the relay endpoint */
24+
interface RelayResponse {
25+
data: {
26+
account: `0x${string}`;
27+
tokenId: string;
28+
txHash: `0x${string}`;
29+
};
30+
error?: { code: string; message: string };
31+
}
32+
33+
/** Hash an address array the same way the contract does: keccak256(abi.encodePacked(addresses)).
34+
* Solidity's abi.encodePacked on address[] pads each element to 32 bytes (not 20). */
35+
function hashAddressArray(addresses: `0x${string}`[]): `0x${string}` {
36+
if (addresses.length === 0) return keccak256(new Uint8Array(0));
37+
return keccak256(concatHex(addresses.map((a) => pad(a, { size: 32 }))));
38+
}
39+
40+
/** Sign CreateAccount params with EIP-712 for gasless relay submission */
41+
export async function signCreateAccount(
42+
walletClient: WalletClient<Transport, Chain, Account>,
43+
publicClient: PublicClient<Transport, Chain>,
44+
addresses: AzethContractAddresses,
45+
params: CreateAccountParams,
46+
agentURI: string,
47+
salt: `0x${string}`,
48+
): Promise<{ signature: `0x${string}`; nonce: bigint }> {
49+
const factoryAddress = requireAddress(addresses, 'factory');
50+
const chainId = publicClient.chain?.id;
51+
if (!chainId) throw new AzethError('Chain ID not available', 'NETWORK_ERROR');
52+
53+
// Read current nonce from factory
54+
const nonce = await withRetry(() => publicClient.readContract({
55+
address: factoryAddress,
56+
abi: AzethFactoryAbi,
57+
functionName: 'nonces',
58+
args: [params.owner],
59+
})) as bigint;
60+
61+
// Pre-hash dynamic fields (must match contract's keccak256(abi.encodePacked(...)))
62+
const protocolsHash = hashAddressArray(params.protocols ?? []);
63+
const tokensHash = hashAddressArray(params.tokens ?? []);
64+
const agentURIHash = keccak256(toBytes(agentURI));
65+
66+
const signature = await walletClient.signTypedData({
67+
domain: {
68+
...AZETH_FACTORY_DOMAIN,
69+
chainId: BigInt(chainId),
70+
verifyingContract: factoryAddress,
71+
},
72+
types: CREATE_ACCOUNT_TYPES,
73+
primaryType: 'CreateAccount',
74+
message: {
75+
owner: params.owner,
76+
salt,
77+
guardrails: {
78+
maxTxAmountUSD: params.guardrails.maxTxAmountUSD,
79+
dailySpendLimitUSD: params.guardrails.dailySpendLimitUSD,
80+
guardianMaxTxAmountUSD: params.guardrails.guardianMaxTxAmountUSD,
81+
guardianDailySpendLimitUSD: params.guardrails.guardianDailySpendLimitUSD,
82+
guardian: params.guardrails.guardian,
83+
emergencyWithdrawTo: params.guardrails.emergencyWithdrawTo,
84+
},
85+
protocolsHash,
86+
tokensHash,
87+
agentURIHash,
88+
nonce,
89+
},
90+
});
91+
92+
return { signature, nonce };
93+
}
94+
95+
/** Submit a signed CreateAccount to the relay endpoint.
96+
* Returns null for 429 (rate-limited) or 503 (relay unavailable). */
97+
export async function submitToRelay(
98+
serverUrl: string,
99+
params: CreateAccountParams,
100+
salt: `0x${string}`,
101+
agentURI: string,
102+
signature: `0x${string}`,
103+
chain: string,
104+
): Promise<CreateAccountResult | null> {
105+
const response = await fetch(`${serverUrl}/api/v1/relay/create-account`, {
106+
method: 'POST',
107+
headers: { 'Content-Type': 'application/json' },
108+
body: JSON.stringify({
109+
owner: params.owner,
110+
salt,
111+
guardrails: {
112+
maxTxAmountUSD: params.guardrails.maxTxAmountUSD.toString(),
113+
dailySpendLimitUSD: params.guardrails.dailySpendLimitUSD.toString(),
114+
guardianMaxTxAmountUSD: params.guardrails.guardianMaxTxAmountUSD.toString(),
115+
guardianDailySpendLimitUSD: params.guardrails.guardianDailySpendLimitUSD.toString(),
116+
guardian: params.guardrails.guardian,
117+
emergencyWithdrawTo: params.guardrails.emergencyWithdrawTo,
118+
},
119+
protocols: params.protocols ?? [],
120+
tokens: params.tokens ?? [],
121+
agentURI,
122+
signature,
123+
chain,
124+
}),
125+
signal: AbortSignal.timeout(120_000),
126+
});
127+
128+
if (!response.ok) {
129+
// 429 = rate limited, 503 = relay unavailable — caller should fall back
130+
if (response.status === 429 || response.status === 503) {
131+
return null;
132+
}
133+
const body = await response.json().catch(() => null) as RelayResponse | null;
134+
throw new AzethError(
135+
body?.error?.message ?? `Relay error: HTTP ${response.status}`,
136+
'NETWORK_ERROR',
137+
);
138+
}
139+
140+
const body = await response.json() as RelayResponse;
141+
return {
142+
account: body.data.account,
143+
tokenId: BigInt(body.data.tokenId),
144+
txHash: body.data.txHash,
145+
};
146+
}
147+
148+
/** Try gasless creation via relay, return null if relay unavailable or rate-limited.
149+
* Falls back gracefully when the factory doesn't support createAccountWithSignature
150+
* (nonces() call fails), relay is down, or relay returns 429/503. */
151+
export async function createAccountGasless(
152+
publicClient: PublicClient<Transport, Chain>,
153+
walletClient: WalletClient<Transport, Chain, Account>,
154+
addresses: AzethContractAddresses,
155+
params: CreateAccountParams,
156+
serverUrl: string,
157+
chain: string,
158+
salt: `0x${string}`,
159+
agentURI: string,
160+
): Promise<CreateAccountResult | null> {
161+
try {
162+
const { signature } = await signCreateAccount(
163+
walletClient, publicClient, addresses, params, agentURI, salt,
164+
);
165+
return await submitToRelay(serverUrl, params, salt, agentURI, signature, chain);
166+
} catch {
167+
// Factory doesn't support gasless (nonces() reverts), relay down, timeout,
168+
// rate limited — fall back to direct on-chain tx
169+
return null;
170+
}
171+
}

src/client.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
bytesToHex,
77
encodeFunctionData,
88
getAddress,
9+
pad,
10+
toHex,
911
type PublicClient,
1012
type WalletClient,
1113
type Chain,
@@ -36,13 +38,14 @@ import { resolveAddresses, requireAddress } from './utils/addresses.js';
3638
import { withRetry } from './utils/retry.js';
3739
import { AzethFactoryAbi, PaymentAgreementModuleAbi, TrustRegistryModuleAbi } from '@azeth/common/abis';
3840
import { createAccount, getAccountAddress, type CreateAccountParams, type CreateAccountResult } from './account/create.js';
41+
import { createAccountGasless } from './account/gasless.js';
3942
import { setTokenWhitelist as setTokenWhitelistFn, setProtocolWhitelist as setProtocolWhitelistFn } from './account/guardian.js';
4043
import { getBalance, getAllBalances, type BalanceResult } from './account/balance.js';
4144
import type { AggregatedBalanceResult } from '@azeth/common';
4245
import { transfer, type TransferParams, type TransferResult } from './account/transfer.js';
4346
import { getHistory, type HistoryParams, type TransactionRecord } from './account/history.js';
4447
import { deposit, type DepositParams, type DepositResult } from './account/deposit.js';
45-
import { registerOnRegistry, updateMetadata, updateMetadataBatch, type RegisterParams, type RegisterResult, type MetadataUpdate } from './registry/register.js';
48+
import { registerOnRegistry, updateMetadata, updateMetadataBatch, buildAgentURI, type RegisterParams, type RegisterResult, type MetadataUpdate } from './registry/register.js';
4649
import {
4750
submitOpinion as submitOnChainOpinion,
4851
getWeightedReputation as getWeightedRep,
@@ -469,6 +472,41 @@ export class AzethKit {
469472
};
470473
}
471474

475+
// Try gasless creation via relay first (if serverUrl is configured).
476+
// The relay pays gas using createAccountWithSignature. Falls back to direct tx
477+
// if relay is unavailable (503), rate-limited (429), or any network error occurs.
478+
if (this.serverUrl) {
479+
// Pre-compute salt and agentURI (same logic as create.ts) so both paths are consistent
480+
let salt: `0x${string}`;
481+
if (fullParams.salt) {
482+
salt = fullParams.salt;
483+
} else {
484+
const existing = await withRetry(() => this.publicClient.readContract({
485+
address: requireAddress(this.addresses, 'factory'),
486+
abi: AzethFactoryAbi,
487+
functionName: 'getAccountsByOwner',
488+
args: [fullParams.owner],
489+
})) as readonly `0x${string}`[];
490+
salt = pad(toHex(existing.length), { size: 32 });
491+
}
492+
const agentURI = fullParams.registry ? buildAgentURI(fullParams.registry) : '';
493+
494+
const gaslessResult = await createAccountGasless(
495+
this.publicClient, this.walletClient, this.addresses,
496+
fullParams, this.serverUrl, this.chainName, salt, agentURI,
497+
);
498+
if (gaslessResult) {
499+
if (!this._smartAccounts) {
500+
this._smartAccounts = [gaslessResult.account];
501+
} else {
502+
this._smartAccounts.push(gaslessResult.account);
503+
}
504+
return gaslessResult;
505+
}
506+
// Relay failed — pass pre-computed salt to avoid duplicate RPC call in fallback
507+
fullParams.salt = salt;
508+
}
509+
472510
const result = await createAccount(this.publicClient, this.walletClient, this.addresses, fullParams);
473511
// Cache the newly created account
474512
if (!this._smartAccounts) {

0 commit comments

Comments
 (0)