steward-arc-sdk 0.1.0 → 0.2.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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/index.cjs +31 -1
- package/dist/index.d.cts +62 -10
- package/dist/index.d.ts +62 -10
- package/dist/index.js +31 -1
- package/package.json +3 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Godswill Idolor
|
|
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:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
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.
|
package/README.md
CHANGED
|
@@ -5,6 +5,39 @@ expiry the agent can't exceed, an on-chain decision log, and an escalation path.
|
|
|
5
5
|
|
|
6
6
|
Contracts + docs: https://github.com/big14way/steward (Tameion Agents Hackathon, Canteen × Circle × Arc).
|
|
7
7
|
|
|
8
|
+
## Step 0: create a budget for your agent (the owner does this once)
|
|
9
|
+
|
|
10
|
+
The owner wallet creates an allowance on the deployed `AllowanceManager` (Arc Testnet) and funds it with USDC. Your agent can then pay
|
|
11
|
+
that one payee, within those limits, and nothing else.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createPublicClient, createWalletClient, http, parseAbi } from "viem";
|
|
15
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
16
|
+
import { arcTestnet } from "steward-arc-sdk";
|
|
17
|
+
|
|
18
|
+
const AM = "0x3AAfC635a1D1391c9FD8b5B9d8A518Fe980cb7E6"; // AllowanceManager on Arc Testnet
|
|
19
|
+
const USDC = "0x3600000000000000000000000000000000000000"; // USDC on Arc (6 decimals)
|
|
20
|
+
const abi = parseAbi([
|
|
21
|
+
"function nextId() view returns (uint256)",
|
|
22
|
+
"function create(address agent, address payee, uint128 capPerPeriod, uint128 perTxCap, uint64 period, uint64 expiry) returns (uint256)",
|
|
23
|
+
"function fund(uint256 id, uint128 amount)",
|
|
24
|
+
"function approve(address spender, uint256 amount) returns (bool)",
|
|
25
|
+
]);
|
|
26
|
+
const pub = createPublicClient({ chain: arcTestnet, transport: http() });
|
|
27
|
+
const owner = createWalletClient({ account: privateKeyToAccount(process.env.OWNER_PK as `0x${string}`), chain: arcTestnet, transport: http() });
|
|
28
|
+
const gas = { maxFeePerGas: 25_000_000_000n, maxPriorityFeePerGas: 1_000_000_000n }; // Arc's floor is 20 gwei
|
|
29
|
+
|
|
30
|
+
const id = await pub.readContract({ address: AM, abi, functionName: "nextId" });
|
|
31
|
+
// 50 USDC per payment, 200 USDC per week, no expiry
|
|
32
|
+
await pub.waitForTransactionReceipt({ hash: await owner.writeContract({ address: AM, abi, functionName: "create",
|
|
33
|
+
args: [AGENT_ADDRESS, PAYEE_ADDRESS, 200_000_000n, 50_000_000n, 604_800n, 0n], ...gas }) });
|
|
34
|
+
await pub.waitForTransactionReceipt({ hash: await owner.writeContract({ address: USDC, abi, functionName: "approve", args: [AM, 200_000_000n], ...gas }) });
|
|
35
|
+
await pub.waitForTransactionReceipt({ hash: await owner.writeContract({ address: AM, abi, functionName: "fund", args: [id, 200_000_000n], ...gas }) });
|
|
36
|
+
console.log("allowanceId", id); // pass this to steward.decide()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Prefer a UI? The hosted app does the same from *Contractors → Add contractor*.
|
|
40
|
+
|
|
8
41
|
## TypeScript
|
|
9
42
|
|
|
10
43
|
```bash
|
|
@@ -50,6 +83,40 @@ r = s.decide(allowance_id=0, amount=150_000_000, memo="logo v2",
|
|
|
50
83
|
print(r.action, r.hash, r.pay_tx or r.escalate_tx)
|
|
51
84
|
```
|
|
52
85
|
|
|
86
|
+
## Agent on a Circle wallet (no raw key)
|
|
87
|
+
|
|
88
|
+
Most Arc agents sign with a Circle Developer-Controlled wallet. Pass the Circle client you already have instead of `account`;
|
|
89
|
+
STEWARD sends `record`, `pay` and `escalate` through Circle's contract-execution API and waits for the on-chain hash.
|
|
90
|
+
The allowance's `agent` must be that wallet's address. Same rules, same hashes.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
|
|
94
|
+
import { Steward } from "steward-arc-sdk";
|
|
95
|
+
|
|
96
|
+
const client = initiateDeveloperControlledWalletsClient({ apiKey: process.env.CIRCLE_API_KEY!, entitySecret: process.env.CIRCLE_ENTITY_SECRET! });
|
|
97
|
+
const s = new Steward({
|
|
98
|
+
allowanceManager: "0x3AAfC635a1D1391c9FD8b5B9d8A518Fe980cb7E6", // Arc Testnet
|
|
99
|
+
auditLog: "0x89264D27AFbCb2Ac90b8a3802340C26Ea1326866",
|
|
100
|
+
circle: { client, walletId: process.env.AGENT_WALLET_ID! },
|
|
101
|
+
});
|
|
102
|
+
const r = await s.decide({ allowanceId: 0n, amount: 150_000_000n, memo: "logo v2", inputs: { milestone: "logo v2" } });
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
# pip install "steward-sdk[circle]"
|
|
107
|
+
from circle.web3 import utils
|
|
108
|
+
from steward_sdk import Steward
|
|
109
|
+
|
|
110
|
+
client = utils.init_developer_controlled_wallets_client(api_key=os.environ["CIRCLE_API_KEY"], entity_secret=os.environ["CIRCLE_ENTITY_SECRET"])
|
|
111
|
+
s = Steward(allowance_manager="0x3AAfC635a1D1391c9FD8b5B9d8A518Fe980cb7E6", audit_log="0x89264D27AFbCb2Ac90b8a3802340C26Ea1326866",
|
|
112
|
+
circle_client=client, circle_wallet_id=os.environ["AGENT_WALLET_ID"])
|
|
113
|
+
r = s.decide(allowance_id=0, amount=150_000_000, memo="logo v2", inputs={"milestone": "logo v2"})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Tested live on Arc Testnet with both SDKs (a `HOLD` recorded through a Circle wallet:
|
|
117
|
+
[TypeScript](https://explorer.testnet.arc.io/tx/0x1618d269880dae697842db31470db89e383e48b42450e829b0b8458f94e8a73f),
|
|
118
|
+
[Python](https://explorer.testnet.arc.io/tx/0xbb36c711456a84a2de37992f10b81409807db2436abdaecddb4d257bf9c1fda8)).
|
|
119
|
+
|
|
53
120
|
## Owner side
|
|
54
121
|
|
|
55
122
|
The owner (a Circle Developer-Controlled wallet in the reference app) calls `create(agent, payee, capPerPeriod, perTxCap, period, expiry)`,
|
package/dist/index.cjs
CHANGED
|
@@ -77,13 +77,19 @@ function applyRules(a, p) {
|
|
|
77
77
|
if (allowed < p.amount) return { action: "PARTIAL", rule: "R4_partial", pay: allowed, remainder: p.amount - allowed };
|
|
78
78
|
return { action: "PAY", rule: "R5_pay", pay: p.amount, remainder: 0n };
|
|
79
79
|
}
|
|
80
|
+
var SIGNATURES = {
|
|
81
|
+
record: "record(uint256,bytes32,uint8,uint128)",
|
|
82
|
+
pay: "pay(uint256,uint128,bytes32,string)",
|
|
83
|
+
escalate: "escalate(uint256,uint128,bytes32,string)"
|
|
84
|
+
};
|
|
80
85
|
var Steward = class {
|
|
81
86
|
constructor(cfg) {
|
|
82
87
|
this.cfg = cfg;
|
|
83
88
|
const chain = cfg.chain ?? import_chains.arcTestnet;
|
|
84
89
|
const transport = cfg.transport ?? (0, import_viem.http)(cfg.rpc ?? "https://rpc.testnet.arc.io");
|
|
85
90
|
this.pub = (0, import_viem.createPublicClient)({ chain, transport });
|
|
86
|
-
this.wallet = (0, import_viem.createWalletClient)({ chain, transport, account: cfg.account });
|
|
91
|
+
this.wallet = cfg.account ? (0, import_viem.createWalletClient)({ chain, transport, account: cfg.account }) : void 0;
|
|
92
|
+
if (!cfg.account && !cfg.circle) throw new Error("Steward needs either `account` (a viem account) or `circle` (a Circle wallet)");
|
|
87
93
|
}
|
|
88
94
|
cfg;
|
|
89
95
|
pub;
|
|
@@ -98,7 +104,31 @@ var Steward = class {
|
|
|
98
104
|
const maxFeePerGas = base + base / 4n > MIN_FEE_PER_GAS ? base + base / 4n : MIN_FEE_PER_GAS;
|
|
99
105
|
return { maxFeePerGas, maxPriorityFeePerGas: 1000000000n };
|
|
100
106
|
}
|
|
107
|
+
/** Submit through Circle and wait for the on-chain hash. The idempotency key is fixed first, so a retry can't double-submit. */
|
|
108
|
+
async writeCircle(address, functionName, args) {
|
|
109
|
+
const c = this.cfg.circle;
|
|
110
|
+
const idempotencyKey = globalThis.crypto.randomUUID();
|
|
111
|
+
const res = await c.client.createContractExecutionTransaction({
|
|
112
|
+
walletId: c.walletId,
|
|
113
|
+
contractAddress: address,
|
|
114
|
+
abiFunctionSignature: SIGNATURES[functionName],
|
|
115
|
+
abiParameters: args.map((a) => typeof a === "bigint" || typeof a === "number" ? a.toString() : a),
|
|
116
|
+
fee: { type: "level", config: { feeLevel: c.feeLevel ?? "MEDIUM" } },
|
|
117
|
+
idempotencyKey
|
|
118
|
+
});
|
|
119
|
+
const id = res.data?.id;
|
|
120
|
+
if (!id) throw new Error("Circle did not return a transaction id");
|
|
121
|
+
const deadline = Date.now() + (c.timeoutMs ?? 12e4);
|
|
122
|
+
while (Date.now() < deadline) {
|
|
123
|
+
await new Promise((r) => setTimeout(r, c.pollMs ?? 2e3));
|
|
124
|
+
const t = (await c.client.getTransaction({ id })).data?.transaction;
|
|
125
|
+
if ((t?.state === "COMPLETE" || t?.state === "CONFIRMED") && t.txHash) return t.txHash;
|
|
126
|
+
if (t?.state === "FAILED" || t?.state === "DENIED" || t?.state === "CANCELLED") throw new Error(`Circle tx ${id} ${t.state}: ${t.errorReason ?? ""}`);
|
|
127
|
+
}
|
|
128
|
+
throw new Error(`Circle tx ${id} not confirmed in time`);
|
|
129
|
+
}
|
|
101
130
|
async write(address, abi, functionName, args) {
|
|
131
|
+
if (!this.wallet) return this.writeCircle(address, functionName, args);
|
|
102
132
|
const fees = await this.fees();
|
|
103
133
|
const hash = await this.wallet.writeContract({ address, abi, functionName, args, ...fees });
|
|
104
134
|
const rcpt = await this.pub.waitForTransactionReceipt({ hash });
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Hex, Chain,
|
|
1
|
+
import { Hex, Chain, Transport, Account } from 'viem';
|
|
2
2
|
export { arcTestnet } from 'viem/chains';
|
|
3
3
|
|
|
4
4
|
declare const AM_ABI: readonly [{
|
|
@@ -190,23 +190,75 @@ declare function applyRules(a: Allowance, p: DecideParams): {
|
|
|
190
190
|
pay: bigint;
|
|
191
191
|
remainder: bigint;
|
|
192
192
|
};
|
|
193
|
+
/**
|
|
194
|
+
* The part of a Circle Developer-Controlled Wallets client STEWARD uses. Pass the client you already have:
|
|
195
|
+
* `initiateDeveloperControlledWalletsClient({ apiKey, entitySecret })` from `@circle-fin/developer-controlled-wallets`.
|
|
196
|
+
*/
|
|
197
|
+
type CircleClientLike = {
|
|
198
|
+
createContractExecutionTransaction(req: {
|
|
199
|
+
walletId?: string;
|
|
200
|
+
walletAddress?: string;
|
|
201
|
+
blockchain?: string;
|
|
202
|
+
contractAddress: string;
|
|
203
|
+
abiFunctionSignature: string;
|
|
204
|
+
abiParameters: unknown[];
|
|
205
|
+
fee: {
|
|
206
|
+
type: "level";
|
|
207
|
+
config: {
|
|
208
|
+
feeLevel: "LOW" | "MEDIUM" | "HIGH";
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
idempotencyKey?: string;
|
|
212
|
+
}): Promise<{
|
|
213
|
+
data?: {
|
|
214
|
+
id?: string;
|
|
215
|
+
};
|
|
216
|
+
}>;
|
|
217
|
+
getTransaction(req: {
|
|
218
|
+
id: string;
|
|
219
|
+
}): Promise<{
|
|
220
|
+
data?: {
|
|
221
|
+
transaction?: {
|
|
222
|
+
state?: string;
|
|
223
|
+
txHash?: string;
|
|
224
|
+
errorReason?: string;
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
}>;
|
|
228
|
+
};
|
|
229
|
+
/** Sign with a Circle developer-controlled wallet instead of a raw key. The allowance's `agent` must be this wallet's address. */
|
|
230
|
+
type CircleSigner = {
|
|
231
|
+
client: CircleClientLike;
|
|
232
|
+
walletId: string;
|
|
233
|
+
feeLevel?: "LOW" | "MEDIUM" | "HIGH";
|
|
234
|
+
pollMs?: number;
|
|
235
|
+
timeoutMs?: number;
|
|
236
|
+
};
|
|
237
|
+
type StewardConfig = {
|
|
238
|
+
rpc?: string;
|
|
239
|
+
chain?: Chain;
|
|
240
|
+
allowanceManager: Hex;
|
|
241
|
+
auditLog: Hex;
|
|
242
|
+
transport?: Transport;
|
|
243
|
+
} & ({
|
|
244
|
+
account: Account;
|
|
245
|
+
circle?: undefined;
|
|
246
|
+
} | {
|
|
247
|
+
circle: CircleSigner;
|
|
248
|
+
account?: undefined;
|
|
249
|
+
});
|
|
193
250
|
declare class Steward {
|
|
194
251
|
private cfg;
|
|
195
252
|
private pub;
|
|
196
253
|
private wallet;
|
|
197
|
-
constructor(cfg:
|
|
198
|
-
rpc?: string;
|
|
199
|
-
chain?: Chain;
|
|
200
|
-
allowanceManager: Hex;
|
|
201
|
-
auditLog: Hex;
|
|
202
|
-
account: Account;
|
|
203
|
-
transport?: Transport;
|
|
204
|
-
});
|
|
254
|
+
constructor(cfg: StewardConfig);
|
|
205
255
|
allowance(id: bigint): Promise<Allowance>;
|
|
206
256
|
private fees;
|
|
257
|
+
/** Submit through Circle and wait for the on-chain hash. The idempotency key is fixed first, so a retry can't double-submit. */
|
|
258
|
+
private writeCircle;
|
|
207
259
|
private write;
|
|
208
260
|
/** rules → canonical hash → AuditLog.record() → pay() | escalate(). Every call is recorded, including HOLD. */
|
|
209
261
|
decide(p: DecideParams): Promise<DecideResult>;
|
|
210
262
|
}
|
|
211
263
|
|
|
212
|
-
export { ACTION_CODE, AM_ABI, type Action, type Allowance, type DecideParams, type DecideResult, type DecisionRecord, LOG_ABI, MIN_FEE_PER_GAS, REMAINDER_TAG, Steward, applyRules, canonical, decisionHash, remainderHash };
|
|
264
|
+
export { ACTION_CODE, AM_ABI, type Action, type Allowance, type CircleClientLike, type CircleSigner, type DecideParams, type DecideResult, type DecisionRecord, LOG_ABI, MIN_FEE_PER_GAS, REMAINDER_TAG, Steward, applyRules, canonical, decisionHash, remainderHash };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Hex, Chain,
|
|
1
|
+
import { Hex, Chain, Transport, Account } from 'viem';
|
|
2
2
|
export { arcTestnet } from 'viem/chains';
|
|
3
3
|
|
|
4
4
|
declare const AM_ABI: readonly [{
|
|
@@ -190,23 +190,75 @@ declare function applyRules(a: Allowance, p: DecideParams): {
|
|
|
190
190
|
pay: bigint;
|
|
191
191
|
remainder: bigint;
|
|
192
192
|
};
|
|
193
|
+
/**
|
|
194
|
+
* The part of a Circle Developer-Controlled Wallets client STEWARD uses. Pass the client you already have:
|
|
195
|
+
* `initiateDeveloperControlledWalletsClient({ apiKey, entitySecret })` from `@circle-fin/developer-controlled-wallets`.
|
|
196
|
+
*/
|
|
197
|
+
type CircleClientLike = {
|
|
198
|
+
createContractExecutionTransaction(req: {
|
|
199
|
+
walletId?: string;
|
|
200
|
+
walletAddress?: string;
|
|
201
|
+
blockchain?: string;
|
|
202
|
+
contractAddress: string;
|
|
203
|
+
abiFunctionSignature: string;
|
|
204
|
+
abiParameters: unknown[];
|
|
205
|
+
fee: {
|
|
206
|
+
type: "level";
|
|
207
|
+
config: {
|
|
208
|
+
feeLevel: "LOW" | "MEDIUM" | "HIGH";
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
idempotencyKey?: string;
|
|
212
|
+
}): Promise<{
|
|
213
|
+
data?: {
|
|
214
|
+
id?: string;
|
|
215
|
+
};
|
|
216
|
+
}>;
|
|
217
|
+
getTransaction(req: {
|
|
218
|
+
id: string;
|
|
219
|
+
}): Promise<{
|
|
220
|
+
data?: {
|
|
221
|
+
transaction?: {
|
|
222
|
+
state?: string;
|
|
223
|
+
txHash?: string;
|
|
224
|
+
errorReason?: string;
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
}>;
|
|
228
|
+
};
|
|
229
|
+
/** Sign with a Circle developer-controlled wallet instead of a raw key. The allowance's `agent` must be this wallet's address. */
|
|
230
|
+
type CircleSigner = {
|
|
231
|
+
client: CircleClientLike;
|
|
232
|
+
walletId: string;
|
|
233
|
+
feeLevel?: "LOW" | "MEDIUM" | "HIGH";
|
|
234
|
+
pollMs?: number;
|
|
235
|
+
timeoutMs?: number;
|
|
236
|
+
};
|
|
237
|
+
type StewardConfig = {
|
|
238
|
+
rpc?: string;
|
|
239
|
+
chain?: Chain;
|
|
240
|
+
allowanceManager: Hex;
|
|
241
|
+
auditLog: Hex;
|
|
242
|
+
transport?: Transport;
|
|
243
|
+
} & ({
|
|
244
|
+
account: Account;
|
|
245
|
+
circle?: undefined;
|
|
246
|
+
} | {
|
|
247
|
+
circle: CircleSigner;
|
|
248
|
+
account?: undefined;
|
|
249
|
+
});
|
|
193
250
|
declare class Steward {
|
|
194
251
|
private cfg;
|
|
195
252
|
private pub;
|
|
196
253
|
private wallet;
|
|
197
|
-
constructor(cfg:
|
|
198
|
-
rpc?: string;
|
|
199
|
-
chain?: Chain;
|
|
200
|
-
allowanceManager: Hex;
|
|
201
|
-
auditLog: Hex;
|
|
202
|
-
account: Account;
|
|
203
|
-
transport?: Transport;
|
|
204
|
-
});
|
|
254
|
+
constructor(cfg: StewardConfig);
|
|
205
255
|
allowance(id: bigint): Promise<Allowance>;
|
|
206
256
|
private fees;
|
|
257
|
+
/** Submit through Circle and wait for the on-chain hash. The idempotency key is fixed first, so a retry can't double-submit. */
|
|
258
|
+
private writeCircle;
|
|
207
259
|
private write;
|
|
208
260
|
/** rules → canonical hash → AuditLog.record() → pay() | escalate(). Every call is recorded, including HOLD. */
|
|
209
261
|
decide(p: DecideParams): Promise<DecideResult>;
|
|
210
262
|
}
|
|
211
263
|
|
|
212
|
-
export { ACTION_CODE, AM_ABI, type Action, type Allowance, type DecideParams, type DecideResult, type DecisionRecord, LOG_ABI, MIN_FEE_PER_GAS, REMAINDER_TAG, Steward, applyRules, canonical, decisionHash, remainderHash };
|
|
264
|
+
export { ACTION_CODE, AM_ABI, type Action, type Allowance, type CircleClientLike, type CircleSigner, type DecideParams, type DecideResult, type DecisionRecord, LOG_ABI, MIN_FEE_PER_GAS, REMAINDER_TAG, Steward, applyRules, canonical, decisionHash, remainderHash };
|
package/dist/index.js
CHANGED
|
@@ -49,13 +49,19 @@ function applyRules(a, p) {
|
|
|
49
49
|
if (allowed < p.amount) return { action: "PARTIAL", rule: "R4_partial", pay: allowed, remainder: p.amount - allowed };
|
|
50
50
|
return { action: "PAY", rule: "R5_pay", pay: p.amount, remainder: 0n };
|
|
51
51
|
}
|
|
52
|
+
var SIGNATURES = {
|
|
53
|
+
record: "record(uint256,bytes32,uint8,uint128)",
|
|
54
|
+
pay: "pay(uint256,uint128,bytes32,string)",
|
|
55
|
+
escalate: "escalate(uint256,uint128,bytes32,string)"
|
|
56
|
+
};
|
|
52
57
|
var Steward = class {
|
|
53
58
|
constructor(cfg) {
|
|
54
59
|
this.cfg = cfg;
|
|
55
60
|
const chain = cfg.chain ?? arcTestnet;
|
|
56
61
|
const transport = cfg.transport ?? http(cfg.rpc ?? "https://rpc.testnet.arc.io");
|
|
57
62
|
this.pub = createPublicClient({ chain, transport });
|
|
58
|
-
this.wallet = createWalletClient({ chain, transport, account: cfg.account });
|
|
63
|
+
this.wallet = cfg.account ? createWalletClient({ chain, transport, account: cfg.account }) : void 0;
|
|
64
|
+
if (!cfg.account && !cfg.circle) throw new Error("Steward needs either `account` (a viem account) or `circle` (a Circle wallet)");
|
|
59
65
|
}
|
|
60
66
|
cfg;
|
|
61
67
|
pub;
|
|
@@ -70,7 +76,31 @@ var Steward = class {
|
|
|
70
76
|
const maxFeePerGas = base + base / 4n > MIN_FEE_PER_GAS ? base + base / 4n : MIN_FEE_PER_GAS;
|
|
71
77
|
return { maxFeePerGas, maxPriorityFeePerGas: 1000000000n };
|
|
72
78
|
}
|
|
79
|
+
/** Submit through Circle and wait for the on-chain hash. The idempotency key is fixed first, so a retry can't double-submit. */
|
|
80
|
+
async writeCircle(address, functionName, args) {
|
|
81
|
+
const c = this.cfg.circle;
|
|
82
|
+
const idempotencyKey = globalThis.crypto.randomUUID();
|
|
83
|
+
const res = await c.client.createContractExecutionTransaction({
|
|
84
|
+
walletId: c.walletId,
|
|
85
|
+
contractAddress: address,
|
|
86
|
+
abiFunctionSignature: SIGNATURES[functionName],
|
|
87
|
+
abiParameters: args.map((a) => typeof a === "bigint" || typeof a === "number" ? a.toString() : a),
|
|
88
|
+
fee: { type: "level", config: { feeLevel: c.feeLevel ?? "MEDIUM" } },
|
|
89
|
+
idempotencyKey
|
|
90
|
+
});
|
|
91
|
+
const id = res.data?.id;
|
|
92
|
+
if (!id) throw new Error("Circle did not return a transaction id");
|
|
93
|
+
const deadline = Date.now() + (c.timeoutMs ?? 12e4);
|
|
94
|
+
while (Date.now() < deadline) {
|
|
95
|
+
await new Promise((r) => setTimeout(r, c.pollMs ?? 2e3));
|
|
96
|
+
const t = (await c.client.getTransaction({ id })).data?.transaction;
|
|
97
|
+
if ((t?.state === "COMPLETE" || t?.state === "CONFIRMED") && t.txHash) return t.txHash;
|
|
98
|
+
if (t?.state === "FAILED" || t?.state === "DENIED" || t?.state === "CANCELLED") throw new Error(`Circle tx ${id} ${t.state}: ${t.errorReason ?? ""}`);
|
|
99
|
+
}
|
|
100
|
+
throw new Error(`Circle tx ${id} not confirmed in time`);
|
|
101
|
+
}
|
|
73
102
|
async write(address, abi, functionName, args) {
|
|
103
|
+
if (!this.wallet) return this.writeCircle(address, functionName, args);
|
|
74
104
|
const fees = await this.fees();
|
|
75
105
|
const hash = await this.wallet.writeContract({ address, abi, functionName, args, ...fees });
|
|
76
106
|
const rcpt = await this.pub.waitForTransactionReceipt({ hash });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "steward-arc-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Per-payee on-chain allowances, a replayable decision log and human escalation for AI agents that pay people in USDC on Arc.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"dist",
|
|
34
|
-
"README.md"
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
35
36
|
],
|
|
36
37
|
"scripts": {
|
|
37
38
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|