@sovrx402/agent-sdk 1.0.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/dist/BaseAgent.d.ts +25 -0
- package/dist/BaseAgent.js +164 -0
- package/dist/BscAgent.d.ts +36 -0
- package/dist/BscAgent.js +358 -0
- package/dist/errors.d.ts +90 -0
- package/dist/errors.js +140 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +23 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.js +5 -0
- package/package.json +28 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SOVRx402 BaseAgent — Base 主网 Agent(x402 协议)
|
|
3
|
+
*
|
|
4
|
+
* call() 流程:
|
|
5
|
+
* GET /resource → 200 → 返回
|
|
6
|
+
* GET /resource → 402 → 读 PAYMENT-REQUIRED → 签 EIP-3009 → 带 X-PAYMENT 重试一次
|
|
7
|
+
* 重试仍 402 → PaymentRejectedError
|
|
8
|
+
*/
|
|
9
|
+
import { ethers } from 'ethers';
|
|
10
|
+
import type { BaseAgentConfig, SetupResult, CallResult, AgentStatus } from './types';
|
|
11
|
+
export declare class BaseAgent {
|
|
12
|
+
protected readonly config: BaseAgentConfig;
|
|
13
|
+
protected readonly wallet: ethers.Wallet;
|
|
14
|
+
protected readonly provider: ethers.JsonRpcProvider;
|
|
15
|
+
protected readonly usdc: ethers.Contract;
|
|
16
|
+
protected usdcName: string;
|
|
17
|
+
protected usdcVersion: string;
|
|
18
|
+
protected chainId: number;
|
|
19
|
+
protected initialized: boolean;
|
|
20
|
+
constructor(config: BaseAgentConfig);
|
|
21
|
+
setup(): Promise<SetupResult>;
|
|
22
|
+
call(resource: string): Promise<CallResult>;
|
|
23
|
+
getStatus(): Promise<AgentStatus>;
|
|
24
|
+
protected signEip3009(payTo: string, value: bigint): Promise<Record<string, unknown>>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SOVRx402 BaseAgent — Base 主网 Agent(x402 协议)
|
|
4
|
+
*
|
|
5
|
+
* call() 流程:
|
|
6
|
+
* GET /resource → 200 → 返回
|
|
7
|
+
* GET /resource → 402 → 读 PAYMENT-REQUIRED → 签 EIP-3009 → 带 X-PAYMENT 重试一次
|
|
8
|
+
* 重试仍 402 → PaymentRejectedError
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.BaseAgent = void 0;
|
|
12
|
+
const ethers_1 = require("ethers");
|
|
13
|
+
const errors_1 = require("./errors");
|
|
14
|
+
const USDC_ABI = [
|
|
15
|
+
'function name() view returns (string)',
|
|
16
|
+
'function version() view returns (string)',
|
|
17
|
+
'function decimals() view returns (uint8)',
|
|
18
|
+
'function balanceOf(address) view returns (uint256)',
|
|
19
|
+
];
|
|
20
|
+
const EIP3009_TYPES = {
|
|
21
|
+
TransferWithAuthorization: [
|
|
22
|
+
{ name: 'from', type: 'address' },
|
|
23
|
+
{ name: 'to', type: 'address' },
|
|
24
|
+
{ name: 'value', type: 'uint256' },
|
|
25
|
+
{ name: 'validAfter', type: 'uint256' },
|
|
26
|
+
{ name: 'validBefore', type: 'uint256' },
|
|
27
|
+
{ name: 'nonce', type: 'bytes32' },
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
class BaseAgent {
|
|
31
|
+
constructor(config) {
|
|
32
|
+
this.config = config;
|
|
33
|
+
// EIP-3009 domain info — 在 setup() 中读取,测试可通过子类覆盖
|
|
34
|
+
this.usdcName = '';
|
|
35
|
+
this.usdcVersion = '2';
|
|
36
|
+
this.chainId = 8453;
|
|
37
|
+
this.initialized = false;
|
|
38
|
+
const rpc = config.rpcUrl ?? 'https://mainnet.base.org';
|
|
39
|
+
this.provider = new ethers_1.ethers.JsonRpcProvider(rpc);
|
|
40
|
+
this.wallet = new ethers_1.ethers.Wallet(config.privateKey, this.provider);
|
|
41
|
+
this.usdc = new ethers_1.ethers.Contract(config.usdcAddress, USDC_ABI, this.provider);
|
|
42
|
+
}
|
|
43
|
+
async setup() {
|
|
44
|
+
if (!ethers_1.ethers.isAddress(this.config.usdcAddress)) {
|
|
45
|
+
throw new errors_1.InvalidAddressError(this.config.usdcAddress);
|
|
46
|
+
}
|
|
47
|
+
const network = await this.provider.getNetwork();
|
|
48
|
+
this.chainId = Number(network.chainId);
|
|
49
|
+
const [name, version, nativeBal, usdcBal] = await Promise.all([
|
|
50
|
+
this.usdc.name(),
|
|
51
|
+
this.usdc.version(),
|
|
52
|
+
this.provider.getBalance(this.wallet.address),
|
|
53
|
+
this.usdc.balanceOf(this.wallet.address),
|
|
54
|
+
]);
|
|
55
|
+
this.usdcName = name;
|
|
56
|
+
this.usdcVersion = version;
|
|
57
|
+
this.initialized = true;
|
|
58
|
+
return {
|
|
59
|
+
address: this.wallet.address,
|
|
60
|
+
chainId: this.chainId,
|
|
61
|
+
nativeBalance: nativeBal,
|
|
62
|
+
usdcBalance: usdcBal,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async call(resource) {
|
|
66
|
+
const path = resource.startsWith('/') ? resource : '/' + resource;
|
|
67
|
+
const url = this.config.gatewayUrl.replace(/\/$/, '') + path;
|
|
68
|
+
// ── 第一次请求 ────────────────────────────────────────────────────────
|
|
69
|
+
let resp;
|
|
70
|
+
try {
|
|
71
|
+
resp = await fetch(url);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
throw new errors_1.NetworkError(url, undefined, e);
|
|
75
|
+
}
|
|
76
|
+
if (resp.ok) {
|
|
77
|
+
return { success: true, data: await resp.json() };
|
|
78
|
+
}
|
|
79
|
+
if (resp.status !== 402) {
|
|
80
|
+
throw new errors_1.GatewayError(resp.status, await resp.json().catch(() => null));
|
|
81
|
+
}
|
|
82
|
+
// ── 402:读取 PAYMENT-REQUIRED ────────────────────────────────────────
|
|
83
|
+
const payHeader = resp.headers.get('PAYMENT-REQUIRED') ??
|
|
84
|
+
resp.headers.get('X-PAYMENT-REQUIRED');
|
|
85
|
+
if (!payHeader) {
|
|
86
|
+
throw new errors_1.PaymentRejectedError('NO_PAYMENT_HEADER', 'PAYMENT-REQUIRED header missing');
|
|
87
|
+
}
|
|
88
|
+
let payReq;
|
|
89
|
+
try {
|
|
90
|
+
payReq = JSON.parse(payHeader);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw new errors_1.PaymentRejectedError('INVALID_PAYMENT_HEADER', 'Cannot parse PAYMENT-REQUIRED');
|
|
94
|
+
}
|
|
95
|
+
const payTo = payReq.payTo ?? '';
|
|
96
|
+
const amount = BigInt(payReq.maxAmountRequired ?? '0');
|
|
97
|
+
// ── 签 EIP-3009 ───────────────────────────────────────────────────────
|
|
98
|
+
const paymentPayload = await this.signEip3009(payTo, amount);
|
|
99
|
+
// ── 带 X-PAYMENT 重试 ──────────────────────────────────────────────────
|
|
100
|
+
try {
|
|
101
|
+
resp = await fetch(url, {
|
|
102
|
+
headers: { 'X-PAYMENT': JSON.stringify(paymentPayload) },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
throw new errors_1.NetworkError(url, undefined, e);
|
|
107
|
+
}
|
|
108
|
+
if (resp.ok) {
|
|
109
|
+
return { success: true, data: await resp.json() };
|
|
110
|
+
}
|
|
111
|
+
if (resp.status === 402) {
|
|
112
|
+
throw new errors_1.PaymentRejectedError('PAYMENT_REJECTED', 'Gateway rejected payment on retry');
|
|
113
|
+
}
|
|
114
|
+
throw new errors_1.GatewayError(resp.status, await resp.json().catch(() => null));
|
|
115
|
+
}
|
|
116
|
+
async getStatus() {
|
|
117
|
+
const [nativeBal, usdcBal] = await Promise.all([
|
|
118
|
+
this.provider.getBalance(this.wallet.address),
|
|
119
|
+
this.usdc.balanceOf(this.wallet.address),
|
|
120
|
+
]);
|
|
121
|
+
return {
|
|
122
|
+
address: this.wallet.address,
|
|
123
|
+
chainId: this.chainId,
|
|
124
|
+
nativeBalance: nativeBal,
|
|
125
|
+
usdcBalance: usdcBal,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// ── 内部:EIP-3009 签名 ───────────────────────────────────────────────────
|
|
129
|
+
async signEip3009(payTo, value) {
|
|
130
|
+
const nonce = ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(32));
|
|
131
|
+
const validBefore = BigInt(Math.floor(Date.now() / 1000) + 300);
|
|
132
|
+
const domain = {
|
|
133
|
+
name: this.usdcName,
|
|
134
|
+
version: this.usdcVersion,
|
|
135
|
+
chainId: this.chainId,
|
|
136
|
+
verifyingContract: this.config.usdcAddress,
|
|
137
|
+
};
|
|
138
|
+
const message = {
|
|
139
|
+
from: this.wallet.address,
|
|
140
|
+
to: payTo,
|
|
141
|
+
value,
|
|
142
|
+
validAfter: 0n,
|
|
143
|
+
validBefore,
|
|
144
|
+
nonce,
|
|
145
|
+
};
|
|
146
|
+
const signature = await this.wallet.signTypedData(domain, EIP3009_TYPES, message);
|
|
147
|
+
return {
|
|
148
|
+
scheme: 'exact',
|
|
149
|
+
networkId: `eip155:${this.chainId}`,
|
|
150
|
+
payload: {
|
|
151
|
+
authorization: {
|
|
152
|
+
from: this.wallet.address,
|
|
153
|
+
to: payTo,
|
|
154
|
+
value: value.toString(),
|
|
155
|
+
validAfter: '0',
|
|
156
|
+
validBefore: validBefore.toString(),
|
|
157
|
+
nonce,
|
|
158
|
+
},
|
|
159
|
+
signature,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
exports.BaseAgent = BaseAgent;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SOVRx402 BscAgent — BSC 主网 Agent(EIP-712 Auth)
|
|
3
|
+
*
|
|
4
|
+
* call() 流程:
|
|
5
|
+
* 生成 EIP-712 PaymentAuth → X-SOVRx402-Signature → 200 → 返回
|
|
6
|
+
* 401 AUTH_EXPIRED / NONCE_REUSED / PRICE_MISMATCH → 重签 → 重试一次
|
|
7
|
+
* 401 SIGNER_MISMATCH / PATH_MISMATCH / CHAIN_MISMATCH → 直接抛错
|
|
8
|
+
* 402 → ApprovalRequiredError
|
|
9
|
+
* 403 → NotRegisteredError
|
|
10
|
+
*/
|
|
11
|
+
import { ethers } from 'ethers';
|
|
12
|
+
import type { BscAgentConfig, SetupResult, RegisterResult, ApproveResult, CallResult, SettleResult, AgentStatus } from './types';
|
|
13
|
+
export declare class BscAgent {
|
|
14
|
+
protected readonly config: BscAgentConfig;
|
|
15
|
+
protected readonly wallet: ethers.Wallet;
|
|
16
|
+
protected readonly provider: ethers.JsonRpcProvider;
|
|
17
|
+
protected readonly registry: ethers.Contract;
|
|
18
|
+
protected readonly ledger: ethers.Contract;
|
|
19
|
+
protected readonly usdc: ethers.Contract;
|
|
20
|
+
protected operatorAddress: string;
|
|
21
|
+
protected depositAmount: bigint;
|
|
22
|
+
protected pricePerCall: bigint;
|
|
23
|
+
protected initialized: boolean;
|
|
24
|
+
constructor(config: BscAgentConfig);
|
|
25
|
+
setup(): Promise<SetupResult>;
|
|
26
|
+
register(): Promise<RegisterResult>;
|
|
27
|
+
approveRegistry(amount: bigint): Promise<ApproveResult>;
|
|
28
|
+
approveOperator(amount: bigint): Promise<ApproveResult>;
|
|
29
|
+
approveLedger(amount: bigint): Promise<ApproveResult>;
|
|
30
|
+
private _approve;
|
|
31
|
+
call(resource: string): Promise<CallResult>;
|
|
32
|
+
settle(): Promise<SettleResult>;
|
|
33
|
+
getStatus(): Promise<AgentStatus>;
|
|
34
|
+
private signPaymentAuth;
|
|
35
|
+
protected fetchPriceFromGateway(): Promise<bigint>;
|
|
36
|
+
}
|
package/dist/BscAgent.js
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SOVRx402 BscAgent — BSC 主网 Agent(EIP-712 Auth)
|
|
4
|
+
*
|
|
5
|
+
* call() 流程:
|
|
6
|
+
* 生成 EIP-712 PaymentAuth → X-SOVRx402-Signature → 200 → 返回
|
|
7
|
+
* 401 AUTH_EXPIRED / NONCE_REUSED / PRICE_MISMATCH → 重签 → 重试一次
|
|
8
|
+
* 401 SIGNER_MISMATCH / PATH_MISMATCH / CHAIN_MISMATCH → 直接抛错
|
|
9
|
+
* 402 → ApprovalRequiredError
|
|
10
|
+
* 403 → NotRegisteredError
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.BscAgent = void 0;
|
|
14
|
+
const ethers_1 = require("ethers");
|
|
15
|
+
const errors_1 = require("./errors");
|
|
16
|
+
const REGISTRY_ABI = [
|
|
17
|
+
'function agents(address) view returns (bool registered, uint256 deposit, int256 score, bool slashed)',
|
|
18
|
+
'function getScore(address) view returns (int256)',
|
|
19
|
+
'function getCreditLimit(address) view returns (uint256)',
|
|
20
|
+
'function TOKEN_DECIMALS() view returns (uint8)',
|
|
21
|
+
'function DEPOSIT() view returns (uint256)',
|
|
22
|
+
'function operator() view returns (address)',
|
|
23
|
+
'function ledger() view returns (address)',
|
|
24
|
+
'function register()',
|
|
25
|
+
'function recordPrePayment(address)',
|
|
26
|
+
];
|
|
27
|
+
const LEDGER_ABI = [
|
|
28
|
+
'function getDebt(address) view returns (uint256)',
|
|
29
|
+
'function getDebtDueTime(address) view returns (uint256)',
|
|
30
|
+
'function settle()',
|
|
31
|
+
'function operator() view returns (address)',
|
|
32
|
+
'function registry() view returns (address)',
|
|
33
|
+
];
|
|
34
|
+
const ERC20_ABI = [
|
|
35
|
+
'function balanceOf(address) view returns (uint256)',
|
|
36
|
+
'function allowance(address,address) view returns (uint256)',
|
|
37
|
+
'function approve(address,uint256) returns (bool)',
|
|
38
|
+
];
|
|
39
|
+
const PAYMENT_AUTH_TYPES = {
|
|
40
|
+
PaymentAuth: [
|
|
41
|
+
{ name: 'agent', type: 'address' },
|
|
42
|
+
{ name: 'method', type: 'string' },
|
|
43
|
+
{ name: 'path', type: 'string' },
|
|
44
|
+
{ name: 'price', type: 'uint256' },
|
|
45
|
+
{ name: 'chainId', type: 'uint256' },
|
|
46
|
+
{ name: 'timestamp', type: 'uint256' },
|
|
47
|
+
{ name: 'nonce', type: 'bytes32' },
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
class BscAgent {
|
|
51
|
+
constructor(config) {
|
|
52
|
+
this.config = config;
|
|
53
|
+
// 由 setup() 填充,测试可通过子类覆盖
|
|
54
|
+
this.operatorAddress = '';
|
|
55
|
+
this.depositAmount = 0n;
|
|
56
|
+
this.pricePerCall = 0n;
|
|
57
|
+
this.initialized = false;
|
|
58
|
+
const rpc = config.rpcUrl ?? 'https://bsc-dataseed.binance.org';
|
|
59
|
+
this.provider = new ethers_1.ethers.JsonRpcProvider(rpc);
|
|
60
|
+
this.wallet = new ethers_1.ethers.Wallet(config.privateKey, this.provider);
|
|
61
|
+
this.registry = new ethers_1.ethers.Contract(config.registryAddress, REGISTRY_ABI, this.wallet);
|
|
62
|
+
this.ledger = new ethers_1.ethers.Contract(config.ledgerAddress, LEDGER_ABI, this.wallet);
|
|
63
|
+
this.usdc = new ethers_1.ethers.Contract(config.usdcAddress, ERC20_ABI, this.wallet);
|
|
64
|
+
}
|
|
65
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
66
|
+
// setup()
|
|
67
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
68
|
+
async setup() {
|
|
69
|
+
const expectedChainId = this.config.chainId ?? 56;
|
|
70
|
+
const network = await this.provider.getNetwork();
|
|
71
|
+
if (Number(network.chainId) !== expectedChainId) {
|
|
72
|
+
throw new errors_1.InvalidAddressError(`chainId mismatch: expected ${expectedChainId}, got ${network.chainId}`);
|
|
73
|
+
}
|
|
74
|
+
const [operator, deposit, onChainLedger] = await Promise.all([
|
|
75
|
+
this.registry.operator(),
|
|
76
|
+
this.registry.DEPOSIT(),
|
|
77
|
+
this.registry.ledger(),
|
|
78
|
+
]);
|
|
79
|
+
if (onChainLedger.toLowerCase() !== this.config.ledgerAddress.toLowerCase()) {
|
|
80
|
+
throw new errors_1.ContractRevertError('registry.ledger()', `Mismatch: ${onChainLedger} vs ${this.config.ledgerAddress}`);
|
|
81
|
+
}
|
|
82
|
+
this.operatorAddress = operator;
|
|
83
|
+
this.depositAmount = deposit;
|
|
84
|
+
if (this.config.pricePerCall !== undefined) {
|
|
85
|
+
this.pricePerCall = this.config.pricePerCall;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this.pricePerCall = await this.fetchPriceFromGateway();
|
|
89
|
+
}
|
|
90
|
+
const [agentData, debt, creditLimit, nativeBal, usdcBal] = await Promise.all([
|
|
91
|
+
this.registry.agents(this.wallet.address),
|
|
92
|
+
this.ledger.getDebt(this.wallet.address),
|
|
93
|
+
this.registry.getCreditLimit(this.wallet.address),
|
|
94
|
+
this.provider.getBalance(this.wallet.address),
|
|
95
|
+
this.usdc.balanceOf(this.wallet.address),
|
|
96
|
+
]);
|
|
97
|
+
this.initialized = true;
|
|
98
|
+
return {
|
|
99
|
+
address: this.wallet.address,
|
|
100
|
+
chainId: expectedChainId,
|
|
101
|
+
nativeBalance: nativeBal,
|
|
102
|
+
usdcBalance: usdcBal,
|
|
103
|
+
registered: agentData.registered,
|
|
104
|
+
debt,
|
|
105
|
+
creditLimit,
|
|
106
|
+
score: agentData.score,
|
|
107
|
+
pricePerCall: this.pricePerCall,
|
|
108
|
+
operatorAddress: this.operatorAddress,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
112
|
+
// register()
|
|
113
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
114
|
+
async register() {
|
|
115
|
+
const agentData = await this.registry.agents(this.wallet.address);
|
|
116
|
+
if (agentData.registered) {
|
|
117
|
+
return { didSendTx: false, alreadyRegistered: true };
|
|
118
|
+
}
|
|
119
|
+
const [balance, allowance] = await Promise.all([
|
|
120
|
+
this.usdc.balanceOf(this.wallet.address),
|
|
121
|
+
this.usdc.allowance(this.wallet.address, this.config.registryAddress),
|
|
122
|
+
]);
|
|
123
|
+
if (balance < this.depositAmount) {
|
|
124
|
+
throw new errors_1.InsufficientBalanceError(this.depositAmount, balance, 'USDC');
|
|
125
|
+
}
|
|
126
|
+
if (allowance < this.depositAmount) {
|
|
127
|
+
throw new errors_1.ApprovalRequiredError(this.depositAmount, allowance, this.config.registryAddress, this.config.usdcAddress);
|
|
128
|
+
}
|
|
129
|
+
const scoreBefore = agentData.score;
|
|
130
|
+
let tx;
|
|
131
|
+
try {
|
|
132
|
+
tx = await this.registry.register();
|
|
133
|
+
await tx.wait();
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
137
|
+
throw new errors_1.ContractRevertError('registry.register()', msg);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
didSendTx: true,
|
|
141
|
+
alreadyRegistered: false,
|
|
142
|
+
tx: tx.hash,
|
|
143
|
+
agent: this.wallet.address,
|
|
144
|
+
depositAmount: this.depositAmount,
|
|
145
|
+
scoreBefore,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
149
|
+
// approve 三件套
|
|
150
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
151
|
+
async approveRegistry(amount) {
|
|
152
|
+
return this._approve(this.config.registryAddress, amount);
|
|
153
|
+
}
|
|
154
|
+
async approveOperator(amount) {
|
|
155
|
+
if (!this.operatorAddress)
|
|
156
|
+
throw new errors_1.AuthRequiredError();
|
|
157
|
+
return this._approve(this.operatorAddress, amount);
|
|
158
|
+
}
|
|
159
|
+
async approveLedger(amount) {
|
|
160
|
+
return this._approve(this.config.ledgerAddress, amount);
|
|
161
|
+
}
|
|
162
|
+
async _approve(spender, amount) {
|
|
163
|
+
let tx;
|
|
164
|
+
try {
|
|
165
|
+
tx = await this.usdc.approve(spender, amount);
|
|
166
|
+
await tx.wait();
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
170
|
+
throw new errors_1.ContractRevertError(`usdc.approve(${spender})`, msg);
|
|
171
|
+
}
|
|
172
|
+
return { tx: tx.hash, spender, amount };
|
|
173
|
+
}
|
|
174
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
175
|
+
// call()
|
|
176
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
177
|
+
async call(resource) {
|
|
178
|
+
if (!this.initialized)
|
|
179
|
+
throw new errors_1.AuthRequiredError();
|
|
180
|
+
const canonicalPath = '/' + resource.replace(/^\/+/, '').split('?')[0];
|
|
181
|
+
const url = this.config.gatewayUrl.replace(/\/$/, '') + canonicalPath;
|
|
182
|
+
const attempt = async (price) => {
|
|
183
|
+
const payload = await this.signPaymentAuth(canonicalPath, price);
|
|
184
|
+
try {
|
|
185
|
+
return await fetch(url, {
|
|
186
|
+
headers: { 'X-SOVRx402-Signature': JSON.stringify(payload) },
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
throw new errors_1.NetworkError(url, undefined, e);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
let resp = await attempt(this.pricePerCall);
|
|
194
|
+
if (resp.ok) {
|
|
195
|
+
return { success: true, data: await resp.json() };
|
|
196
|
+
}
|
|
197
|
+
// ── 非 200 处理 ──────────────────────────────────────────────────────────
|
|
198
|
+
if (resp.status === 402) {
|
|
199
|
+
throw new errors_1.ApprovalRequiredError(this.pricePerCall, 0n, this.operatorAddress, this.config.usdcAddress);
|
|
200
|
+
}
|
|
201
|
+
if (resp.status === 403) {
|
|
202
|
+
throw new errors_1.NotRegisteredError(this.wallet.address, this.config.registryAddress);
|
|
203
|
+
}
|
|
204
|
+
if (resp.status !== 401) {
|
|
205
|
+
throw new errors_1.GatewayError(resp.status, await resp.json().catch(() => null));
|
|
206
|
+
}
|
|
207
|
+
// ── 401:解析错误,决定是否重试 ──────────────────────────────────────────
|
|
208
|
+
const body = await resp.json();
|
|
209
|
+
const { code, reason } = body;
|
|
210
|
+
// 不重试的错误
|
|
211
|
+
if (reason === 'SIGNER_MISMATCH')
|
|
212
|
+
throw new errors_1.AuthInvalidError('SIGNER_MISMATCH');
|
|
213
|
+
if (reason === 'PATH_MISMATCH')
|
|
214
|
+
throw new errors_1.AuthInvalidError('PATH_MISMATCH');
|
|
215
|
+
if (reason === 'CHAIN_MISMATCH')
|
|
216
|
+
throw new errors_1.AuthInvalidError('CHAIN_MISMATCH');
|
|
217
|
+
if (reason === 'SIGNATURE_MALFORMED')
|
|
218
|
+
throw new errors_1.AuthInvalidError('SIGNATURE_MALFORMED');
|
|
219
|
+
if (reason === 'HEADER_MISSING')
|
|
220
|
+
throw new errors_1.AuthRequiredError();
|
|
221
|
+
// PRICE_MISMATCH:刷新 pricePerCall
|
|
222
|
+
if (reason === 'PRICE_MISMATCH') {
|
|
223
|
+
try {
|
|
224
|
+
const freshPrice = await this.fetchPriceFromGateway();
|
|
225
|
+
this.pricePerCall = freshPrice;
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// 拉取失败则继续用当前 price 重试
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// 可重试:PRICE_MISMATCH / AUTH_EXPIRED / NONCE_REUSED
|
|
232
|
+
const retryResp = await attempt(this.pricePerCall);
|
|
233
|
+
if (retryResp.ok) {
|
|
234
|
+
return { success: true, data: await retryResp.json() };
|
|
235
|
+
}
|
|
236
|
+
// 重试仍失败 → 抛对应错误
|
|
237
|
+
if (code === 'AUTH_EXPIRED')
|
|
238
|
+
throw new errors_1.AuthExpiredError(reason);
|
|
239
|
+
if (code === 'NONCE_REUSED')
|
|
240
|
+
throw new errors_1.NonceReusedError();
|
|
241
|
+
if (code === 'AUTH_INVALID')
|
|
242
|
+
throw new errors_1.AuthInvalidError(reason);
|
|
243
|
+
throw new errors_1.GatewayError(retryResp.status, await retryResp.json().catch(() => null));
|
|
244
|
+
}
|
|
245
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
246
|
+
// settle()
|
|
247
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
248
|
+
async settle() {
|
|
249
|
+
const debt = await this.ledger.getDebt(this.wallet.address);
|
|
250
|
+
if (debt === 0n) {
|
|
251
|
+
const score = await this.registry.getScore(this.wallet.address);
|
|
252
|
+
return { didSendTx: false, amountPaid: 0n, scoreAfter: score };
|
|
253
|
+
}
|
|
254
|
+
const [balance, allowance] = await Promise.all([
|
|
255
|
+
this.usdc.balanceOf(this.wallet.address),
|
|
256
|
+
this.usdc.allowance(this.wallet.address, this.config.ledgerAddress),
|
|
257
|
+
]);
|
|
258
|
+
if (balance < debt) {
|
|
259
|
+
throw new errors_1.InsufficientBalanceError(debt, balance, 'USDC');
|
|
260
|
+
}
|
|
261
|
+
if (allowance < debt) {
|
|
262
|
+
throw new errors_1.ApprovalRequiredError(debt, allowance, this.config.ledgerAddress, this.config.usdcAddress);
|
|
263
|
+
}
|
|
264
|
+
let tx;
|
|
265
|
+
try {
|
|
266
|
+
tx = await this.ledger.settle();
|
|
267
|
+
await tx.wait();
|
|
268
|
+
}
|
|
269
|
+
catch (e) {
|
|
270
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
271
|
+
throw new errors_1.ContractRevertError('ledger.settle()', msg);
|
|
272
|
+
}
|
|
273
|
+
const scoreAfter = await this.registry.getScore(this.wallet.address);
|
|
274
|
+
return { didSendTx: true, tx: tx.hash, amountPaid: debt, scoreAfter };
|
|
275
|
+
}
|
|
276
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
277
|
+
// getStatus()
|
|
278
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
279
|
+
async getStatus() {
|
|
280
|
+
const [agentData, debt, debtDueTime, creditLimit, nativeBal, usdcBal, allowance] = await Promise.all([
|
|
281
|
+
this.registry.agents(this.wallet.address),
|
|
282
|
+
this.ledger.getDebt(this.wallet.address),
|
|
283
|
+
this.ledger.getDebtDueTime(this.wallet.address),
|
|
284
|
+
this.registry.getCreditLimit(this.wallet.address),
|
|
285
|
+
this.provider.getBalance(this.wallet.address),
|
|
286
|
+
this.usdc.balanceOf(this.wallet.address),
|
|
287
|
+
this.usdc.allowance(this.wallet.address, this.operatorAddress || ethers_1.ethers.ZeroAddress),
|
|
288
|
+
]);
|
|
289
|
+
const canCall = (allowance >= this.pricePerCall) ||
|
|
290
|
+
(debt + this.pricePerCall <= creditLimit);
|
|
291
|
+
return {
|
|
292
|
+
address: this.wallet.address,
|
|
293
|
+
chainId: this.config.chainId ?? 56,
|
|
294
|
+
nativeBalance: nativeBal,
|
|
295
|
+
usdcBalance: usdcBal,
|
|
296
|
+
registered: agentData.registered,
|
|
297
|
+
slashed: agentData.slashed,
|
|
298
|
+
debt,
|
|
299
|
+
debtDueTime: Number(debtDueTime),
|
|
300
|
+
creditLimit,
|
|
301
|
+
score: agentData.score,
|
|
302
|
+
allowance,
|
|
303
|
+
pricePerCall: this.pricePerCall,
|
|
304
|
+
canCall,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
308
|
+
// 内部工具
|
|
309
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
310
|
+
async signPaymentAuth(canonicalPath, price) {
|
|
311
|
+
const chainId = this.config.chainId ?? 56;
|
|
312
|
+
const nonce = ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(32));
|
|
313
|
+
const timestamp = Math.floor(Date.now() / 1000) - 1; // 避免 FUTURE_TIMESTAMP
|
|
314
|
+
const domain = {
|
|
315
|
+
name: 'SOVRx402BSCGateway',
|
|
316
|
+
version: '1',
|
|
317
|
+
chainId,
|
|
318
|
+
verifyingContract: this.config.registryAddress,
|
|
319
|
+
};
|
|
320
|
+
const value = {
|
|
321
|
+
agent: this.wallet.address,
|
|
322
|
+
method: 'GET',
|
|
323
|
+
path: canonicalPath,
|
|
324
|
+
price,
|
|
325
|
+
chainId: BigInt(chainId),
|
|
326
|
+
timestamp: BigInt(timestamp),
|
|
327
|
+
nonce,
|
|
328
|
+
};
|
|
329
|
+
const signature = await this.wallet.signTypedData(domain, PAYMENT_AUTH_TYPES, value);
|
|
330
|
+
return {
|
|
331
|
+
agent: this.wallet.address,
|
|
332
|
+
method: 'GET',
|
|
333
|
+
path: canonicalPath,
|
|
334
|
+
price: price.toString(),
|
|
335
|
+
chainId,
|
|
336
|
+
timestamp,
|
|
337
|
+
nonce,
|
|
338
|
+
signature,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
async fetchPriceFromGateway() {
|
|
342
|
+
const gatewayBase = this.config.gatewayUrl.replace(/\/$/, '');
|
|
343
|
+
let resp;
|
|
344
|
+
try {
|
|
345
|
+
resp = await fetch(`${gatewayBase}/status?agent=${this.wallet.address}`);
|
|
346
|
+
}
|
|
347
|
+
catch (e) {
|
|
348
|
+
throw new errors_1.NetworkError(gatewayBase + '/status', undefined, e);
|
|
349
|
+
}
|
|
350
|
+
if (!resp.ok)
|
|
351
|
+
throw new errors_1.GatewayError(resp.status, await resp.json().catch(() => null));
|
|
352
|
+
const data = await resp.json();
|
|
353
|
+
if (!data.pricePerCall)
|
|
354
|
+
throw new errors_1.GatewayError(200, { detail: 'pricePerCall missing' });
|
|
355
|
+
return BigInt(data.pricePerCall);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
exports.BscAgent = BscAgent;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SOVRx402 Agent SDK — Machine-readable error types
|
|
3
|
+
* 禁止使用字符串 message 作为主要信息载体
|
|
4
|
+
*/
|
|
5
|
+
export declare abstract class SOVRx402BaseError extends Error {
|
|
6
|
+
abstract readonly type: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class NetworkError extends SOVRx402BaseError {
|
|
9
|
+
readonly url: string;
|
|
10
|
+
readonly statusCode?: number | undefined;
|
|
11
|
+
readonly type: "NETWORK_ERROR";
|
|
12
|
+
constructor(url: string, statusCode?: number | undefined, cause?: unknown);
|
|
13
|
+
}
|
|
14
|
+
export declare class NotRegisteredError extends SOVRx402BaseError {
|
|
15
|
+
readonly agent: string;
|
|
16
|
+
readonly registryAddress: string;
|
|
17
|
+
readonly type: "NOT_REGISTERED";
|
|
18
|
+
constructor(agent: string, registryAddress: string);
|
|
19
|
+
}
|
|
20
|
+
export declare class InvalidAddressError extends SOVRx402BaseError {
|
|
21
|
+
readonly address: string;
|
|
22
|
+
readonly type: "INVALID_ADDRESS";
|
|
23
|
+
constructor(address: string);
|
|
24
|
+
}
|
|
25
|
+
export declare class ApprovalRequiredError extends SOVRx402BaseError {
|
|
26
|
+
readonly required: bigint;
|
|
27
|
+
readonly current: bigint;
|
|
28
|
+
readonly spender: string;
|
|
29
|
+
readonly token: string;
|
|
30
|
+
readonly type: "APPROVAL_REQUIRED";
|
|
31
|
+
constructor(required: bigint, current: bigint, spender: string, token: string);
|
|
32
|
+
}
|
|
33
|
+
export declare class InsufficientBalanceError extends SOVRx402BaseError {
|
|
34
|
+
readonly required: bigint;
|
|
35
|
+
readonly current: bigint;
|
|
36
|
+
readonly token: 'USDC' | 'native';
|
|
37
|
+
readonly type: "INSUFFICIENT_BALANCE";
|
|
38
|
+
constructor(required: bigint, current: bigint, token: 'USDC' | 'native');
|
|
39
|
+
}
|
|
40
|
+
export declare class CreditExhaustedError extends SOVRx402BaseError {
|
|
41
|
+
readonly debt: bigint;
|
|
42
|
+
readonly creditLimit: bigint;
|
|
43
|
+
readonly type: "CREDIT_EXHAUSTED";
|
|
44
|
+
constructor(debt: bigint, creditLimit: bigint);
|
|
45
|
+
}
|
|
46
|
+
export declare class PaymentRejectedError extends SOVRx402BaseError {
|
|
47
|
+
readonly gatewayCode: string;
|
|
48
|
+
readonly gatewayReason: string;
|
|
49
|
+
readonly type: "PAYMENT_REJECTED";
|
|
50
|
+
constructor(gatewayCode: string, gatewayReason: string);
|
|
51
|
+
}
|
|
52
|
+
export declare class TransferFailedError extends SOVRx402BaseError {
|
|
53
|
+
readonly attempt?: string | undefined;
|
|
54
|
+
readonly txHash?: string | undefined;
|
|
55
|
+
readonly type: "TRANSFER_FAILED";
|
|
56
|
+
constructor(attempt?: string | undefined, txHash?: string | undefined);
|
|
57
|
+
}
|
|
58
|
+
export declare class GatewayError extends SOVRx402BaseError {
|
|
59
|
+
readonly statusCode: number;
|
|
60
|
+
readonly body: unknown;
|
|
61
|
+
readonly type: "GATEWAY_ERROR";
|
|
62
|
+
constructor(statusCode: number, body: unknown);
|
|
63
|
+
}
|
|
64
|
+
export declare class ContractRevertError extends SOVRx402BaseError {
|
|
65
|
+
readonly method: string;
|
|
66
|
+
readonly revertReason?: string | undefined;
|
|
67
|
+
readonly type: "CONTRACT_REVERT";
|
|
68
|
+
constructor(method: string, revertReason?: string | undefined);
|
|
69
|
+
}
|
|
70
|
+
export declare class AuthRequiredError extends SOVRx402BaseError {
|
|
71
|
+
readonly type: "AUTH_REQUIRED";
|
|
72
|
+
constructor();
|
|
73
|
+
}
|
|
74
|
+
export type AuthInvalidReason = 'CHAIN_MISMATCH' | 'SIGNATURE_MALFORMED' | 'SIGNER_MISMATCH' | 'PRICE_MISMATCH' | 'PATH_MISMATCH';
|
|
75
|
+
export declare class AuthInvalidError extends SOVRx402BaseError {
|
|
76
|
+
readonly reason: AuthInvalidReason;
|
|
77
|
+
readonly type: "AUTH_INVALID";
|
|
78
|
+
constructor(reason: AuthInvalidReason);
|
|
79
|
+
}
|
|
80
|
+
export type AuthExpiredReason = 'FUTURE_TIMESTAMP' | 'TOO_OLD';
|
|
81
|
+
export declare class AuthExpiredError extends SOVRx402BaseError {
|
|
82
|
+
readonly reason: AuthExpiredReason;
|
|
83
|
+
readonly type: "AUTH_EXPIRED";
|
|
84
|
+
constructor(reason: AuthExpiredReason);
|
|
85
|
+
}
|
|
86
|
+
export declare class NonceReusedError extends SOVRx402BaseError {
|
|
87
|
+
readonly type: "NONCE_REUSED";
|
|
88
|
+
constructor();
|
|
89
|
+
}
|
|
90
|
+
export type SOVRx402Error = NetworkError | NotRegisteredError | ApprovalRequiredError | InsufficientBalanceError | CreditExhaustedError | PaymentRejectedError | TransferFailedError | GatewayError | ContractRevertError | InvalidAddressError | AuthRequiredError | AuthInvalidError | AuthExpiredError | NonceReusedError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SOVRx402 Agent SDK — Machine-readable error types
|
|
4
|
+
* 禁止使用字符串 message 作为主要信息载体
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.NonceReusedError = exports.AuthExpiredError = exports.AuthInvalidError = exports.AuthRequiredError = exports.ContractRevertError = exports.GatewayError = exports.TransferFailedError = exports.PaymentRejectedError = exports.CreditExhaustedError = exports.InsufficientBalanceError = exports.ApprovalRequiredError = exports.InvalidAddressError = exports.NotRegisteredError = exports.NetworkError = exports.SOVRx402BaseError = void 0;
|
|
8
|
+
class SOVRx402BaseError extends Error {
|
|
9
|
+
}
|
|
10
|
+
exports.SOVRx402BaseError = SOVRx402BaseError;
|
|
11
|
+
// ── 网络层 ────────────────────────────────────────────────────────────────
|
|
12
|
+
class NetworkError extends SOVRx402BaseError {
|
|
13
|
+
constructor(url, statusCode, cause) {
|
|
14
|
+
super(`Network error: ${url}${statusCode ? ` (${statusCode})` : ''}`);
|
|
15
|
+
this.url = url;
|
|
16
|
+
this.statusCode = statusCode;
|
|
17
|
+
this.type = 'NETWORK_ERROR';
|
|
18
|
+
if (cause instanceof Error)
|
|
19
|
+
this.cause = cause;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.NetworkError = NetworkError;
|
|
23
|
+
// ── 注册 / 身份 ────────────────────────────────────────────────────────────
|
|
24
|
+
class NotRegisteredError extends SOVRx402BaseError {
|
|
25
|
+
constructor(agent, registryAddress) {
|
|
26
|
+
super(`Agent ${agent} not registered in ${registryAddress}`);
|
|
27
|
+
this.agent = agent;
|
|
28
|
+
this.registryAddress = registryAddress;
|
|
29
|
+
this.type = 'NOT_REGISTERED';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.NotRegisteredError = NotRegisteredError;
|
|
33
|
+
class InvalidAddressError extends SOVRx402BaseError {
|
|
34
|
+
constructor(address) {
|
|
35
|
+
super(`Invalid address: ${address}`);
|
|
36
|
+
this.address = address;
|
|
37
|
+
this.type = 'INVALID_ADDRESS';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.InvalidAddressError = InvalidAddressError;
|
|
41
|
+
// ── 资金 / 授权 ────────────────────────────────────────────────────────────
|
|
42
|
+
class ApprovalRequiredError extends SOVRx402BaseError {
|
|
43
|
+
constructor(required, current, spender, token) {
|
|
44
|
+
super(`Approval required: ${required} to ${spender}`);
|
|
45
|
+
this.required = required;
|
|
46
|
+
this.current = current;
|
|
47
|
+
this.spender = spender;
|
|
48
|
+
this.token = token;
|
|
49
|
+
this.type = 'APPROVAL_REQUIRED';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.ApprovalRequiredError = ApprovalRequiredError;
|
|
53
|
+
class InsufficientBalanceError extends SOVRx402BaseError {
|
|
54
|
+
constructor(required, current, token) {
|
|
55
|
+
super(`Insufficient ${token} balance: need ${required}, have ${current}`);
|
|
56
|
+
this.required = required;
|
|
57
|
+
this.current = current;
|
|
58
|
+
this.token = token;
|
|
59
|
+
this.type = 'INSUFFICIENT_BALANCE';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.InsufficientBalanceError = InsufficientBalanceError;
|
|
63
|
+
class CreditExhaustedError extends SOVRx402BaseError {
|
|
64
|
+
constructor(debt, creditLimit) {
|
|
65
|
+
super(`Credit exhausted: debt=${debt}, limit=${creditLimit}`);
|
|
66
|
+
this.debt = debt;
|
|
67
|
+
this.creditLimit = creditLimit;
|
|
68
|
+
this.type = 'CREDIT_EXHAUSTED';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.CreditExhaustedError = CreditExhaustedError;
|
|
72
|
+
// ── 支付 ──────────────────────────────────────────────────────────────────
|
|
73
|
+
class PaymentRejectedError extends SOVRx402BaseError {
|
|
74
|
+
constructor(gatewayCode, gatewayReason) {
|
|
75
|
+
super(`Payment rejected: ${gatewayCode}/${gatewayReason}`);
|
|
76
|
+
this.gatewayCode = gatewayCode;
|
|
77
|
+
this.gatewayReason = gatewayReason;
|
|
78
|
+
this.type = 'PAYMENT_REJECTED';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.PaymentRejectedError = PaymentRejectedError;
|
|
82
|
+
class TransferFailedError extends SOVRx402BaseError {
|
|
83
|
+
constructor(attempt, txHash) {
|
|
84
|
+
super(`Transfer failed${attempt ? `: ${attempt}` : ''}`);
|
|
85
|
+
this.attempt = attempt;
|
|
86
|
+
this.txHash = txHash;
|
|
87
|
+
this.type = 'TRANSFER_FAILED';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.TransferFailedError = TransferFailedError;
|
|
91
|
+
// ── 网关 / 合约 ────────────────────────────────────────────────────────────
|
|
92
|
+
class GatewayError extends SOVRx402BaseError {
|
|
93
|
+
constructor(statusCode, body) {
|
|
94
|
+
super(`Gateway error: ${statusCode}`);
|
|
95
|
+
this.statusCode = statusCode;
|
|
96
|
+
this.body = body;
|
|
97
|
+
this.type = 'GATEWAY_ERROR';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
exports.GatewayError = GatewayError;
|
|
101
|
+
class ContractRevertError extends SOVRx402BaseError {
|
|
102
|
+
constructor(method, revertReason) {
|
|
103
|
+
super(`Contract revert in ${method}: ${revertReason ?? 'unknown'}`);
|
|
104
|
+
this.method = method;
|
|
105
|
+
this.revertReason = revertReason;
|
|
106
|
+
this.type = 'CONTRACT_REVERT';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
exports.ContractRevertError = ContractRevertError;
|
|
110
|
+
// ── Auth 错误(对应 auth-bsc.ts 的服务端错误,客户端镜像)─────────────────
|
|
111
|
+
class AuthRequiredError extends SOVRx402BaseError {
|
|
112
|
+
constructor() {
|
|
113
|
+
super('Authentication required');
|
|
114
|
+
this.type = 'AUTH_REQUIRED';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exports.AuthRequiredError = AuthRequiredError;
|
|
118
|
+
class AuthInvalidError extends SOVRx402BaseError {
|
|
119
|
+
constructor(reason) {
|
|
120
|
+
super(`Auth invalid: ${reason}`);
|
|
121
|
+
this.reason = reason;
|
|
122
|
+
this.type = 'AUTH_INVALID';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.AuthInvalidError = AuthInvalidError;
|
|
126
|
+
class AuthExpiredError extends SOVRx402BaseError {
|
|
127
|
+
constructor(reason) {
|
|
128
|
+
super(`Auth expired: ${reason}`);
|
|
129
|
+
this.reason = reason;
|
|
130
|
+
this.type = 'AUTH_EXPIRED';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.AuthExpiredError = AuthExpiredError;
|
|
134
|
+
class NonceReusedError extends SOVRx402BaseError {
|
|
135
|
+
constructor() {
|
|
136
|
+
super('Nonce already used');
|
|
137
|
+
this.type = 'NONCE_REUSED';
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.NonceReusedError = NonceReusedError;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.BscAgent = exports.BaseAgent = void 0;
|
|
18
|
+
var BaseAgent_1 = require("./BaseAgent");
|
|
19
|
+
Object.defineProperty(exports, "BaseAgent", { enumerable: true, get: function () { return BaseAgent_1.BaseAgent; } });
|
|
20
|
+
var BscAgent_1 = require("./BscAgent");
|
|
21
|
+
Object.defineProperty(exports, "BscAgent", { enumerable: true, get: function () { return BscAgent_1.BscAgent; } });
|
|
22
|
+
__exportStar(require("./errors"), exports);
|
|
23
|
+
__exportStar(require("./types"), exports);
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SOVRx402 Agent SDK — 类型定义
|
|
3
|
+
*/
|
|
4
|
+
export interface BaseAgentConfig {
|
|
5
|
+
/** Agent 私钥(hex 格式,0x 前缀) */
|
|
6
|
+
privateKey: string;
|
|
7
|
+
/** 网关 URL,e.g. http://localhost:3000 */
|
|
8
|
+
gatewayUrl: string;
|
|
9
|
+
/** Base 主网 USDC 合约地址 */
|
|
10
|
+
usdcAddress: string;
|
|
11
|
+
/** RPC 端点,默认 mainnet.base.org */
|
|
12
|
+
rpcUrl?: string;
|
|
13
|
+
/** x402 facilitator URL,默认 CDP */
|
|
14
|
+
facilitatorUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface BscAgentConfig {
|
|
17
|
+
/** Agent 私钥(hex 格式,0x 前缀) */
|
|
18
|
+
privateKey: string;
|
|
19
|
+
/** 网关 URL,e.g. http://localhost:3001 */
|
|
20
|
+
gatewayUrl: string;
|
|
21
|
+
/** BSC USDC 合约地址 */
|
|
22
|
+
usdcAddress: string;
|
|
23
|
+
/** SOVRx402Registry 合约地址 */
|
|
24
|
+
registryAddress: string;
|
|
25
|
+
/** CreditLedger 合约地址 */
|
|
26
|
+
ledgerAddress: string;
|
|
27
|
+
/** RPC 端点,默认 bsc-dataseed.binance.org */
|
|
28
|
+
rpcUrl?: string;
|
|
29
|
+
/** chainId,默认 56 */
|
|
30
|
+
chainId?: number;
|
|
31
|
+
/** 每次调用价格(bigint),若省略则在 setup() 时从 /status 读取 */
|
|
32
|
+
pricePerCall?: bigint;
|
|
33
|
+
}
|
|
34
|
+
export interface SetupResult {
|
|
35
|
+
address: string;
|
|
36
|
+
chainId: number;
|
|
37
|
+
nativeBalance: bigint;
|
|
38
|
+
usdcBalance: bigint;
|
|
39
|
+
registered?: boolean;
|
|
40
|
+
debt?: bigint;
|
|
41
|
+
creditLimit?: bigint;
|
|
42
|
+
score?: bigint;
|
|
43
|
+
pricePerCall?: bigint;
|
|
44
|
+
operatorAddress?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface RegisterResult {
|
|
47
|
+
didSendTx: boolean;
|
|
48
|
+
alreadyRegistered: boolean;
|
|
49
|
+
tx?: string;
|
|
50
|
+
agent?: string;
|
|
51
|
+
depositAmount?: bigint;
|
|
52
|
+
scoreBefore?: bigint;
|
|
53
|
+
}
|
|
54
|
+
export interface ApproveResult {
|
|
55
|
+
tx: string;
|
|
56
|
+
spender: string;
|
|
57
|
+
amount: bigint;
|
|
58
|
+
}
|
|
59
|
+
export interface CallResult {
|
|
60
|
+
success: boolean;
|
|
61
|
+
data: unknown;
|
|
62
|
+
path?: 'credit' | 'prepay';
|
|
63
|
+
tx?: string;
|
|
64
|
+
}
|
|
65
|
+
export interface SettleResult {
|
|
66
|
+
didSendTx: boolean;
|
|
67
|
+
tx?: string;
|
|
68
|
+
amountPaid: bigint;
|
|
69
|
+
scoreAfter: bigint;
|
|
70
|
+
}
|
|
71
|
+
export interface AgentStatus {
|
|
72
|
+
address: string;
|
|
73
|
+
chainId: number;
|
|
74
|
+
nativeBalance: bigint;
|
|
75
|
+
usdcBalance: bigint;
|
|
76
|
+
registered?: boolean;
|
|
77
|
+
slashed?: boolean;
|
|
78
|
+
debt?: bigint;
|
|
79
|
+
debtDueTime?: number;
|
|
80
|
+
creditLimit?: bigint;
|
|
81
|
+
score?: bigint;
|
|
82
|
+
allowance?: bigint;
|
|
83
|
+
pricePerCall?: bigint;
|
|
84
|
+
canCall?: boolean;
|
|
85
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sovrx402/agent-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "On-chain credit & payment infrastructure for AI agents",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc -p tsconfig.build.json"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"web3",
|
|
15
|
+
"ai",
|
|
16
|
+
"agent",
|
|
17
|
+
"payment",
|
|
18
|
+
"x402",
|
|
19
|
+
"eip712",
|
|
20
|
+
"usdc",
|
|
21
|
+
"bsc",
|
|
22
|
+
"base"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"ethers": "^6.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|