@botanary/agent 0.1.0-alpha.1 → 0.1.0-alpha.12

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +43 -5
  3. package/dist/errors.d.ts +1 -38
  4. package/dist/exact-action.d.ts +61 -8
  5. package/dist/fingerprint.d.ts +0 -15
  6. package/dist/generated/routes.d.ts +0 -1
  7. package/dist/generated/schema.d.ts +8 -1092
  8. package/dist/http-path.d.ts +0 -81
  9. package/dist/index.d.ts +1 -2
  10. package/dist/index.js +2 -7
  11. package/dist/pairing-code.d.ts +0 -23
  12. package/dist/registration.d.ts +0 -3
  13. package/dist/runtime.d.ts +3 -128
  14. package/dist/signer.d.ts +0 -7
  15. package/dist/transport.d.ts +0 -3
  16. package/dist/validation.d.ts +4 -3
  17. package/dist/views.d.ts +0 -54
  18. package/package.json +6 -2
  19. package/dist/errors.d.ts.map +0 -1
  20. package/dist/errors.js +0 -40
  21. package/dist/errors.js.map +0 -1
  22. package/dist/exact-action.d.ts.map +0 -1
  23. package/dist/exact-action.js +0 -178
  24. package/dist/exact-action.js.map +0 -1
  25. package/dist/fingerprint.d.ts.map +0 -1
  26. package/dist/fingerprint.js +0 -47
  27. package/dist/fingerprint.js.map +0 -1
  28. package/dist/generated/routes.d.ts.map +0 -1
  29. package/dist/generated/routes.js +0 -92
  30. package/dist/generated/routes.js.map +0 -1
  31. package/dist/generated/schema.d.ts.map +0 -1
  32. package/dist/generated/schema.js +0 -2
  33. package/dist/generated/schema.js.map +0 -1
  34. package/dist/http-path.d.ts.map +0 -1
  35. package/dist/http-path.js +0 -201
  36. package/dist/http-path.js.map +0 -1
  37. package/dist/index.d.ts.map +0 -1
  38. package/dist/index.js.map +0 -1
  39. package/dist/pairing-code.d.ts.map +0 -1
  40. package/dist/pairing-code.js +0 -60
  41. package/dist/pairing-code.js.map +0 -1
  42. package/dist/registration.d.ts.map +0 -1
  43. package/dist/registration.js +0 -62
  44. package/dist/registration.js.map +0 -1
  45. package/dist/runtime.d.ts.map +0 -1
  46. package/dist/runtime.js +0 -559
  47. package/dist/runtime.js.map +0 -1
  48. package/dist/signer.d.ts.map +0 -1
  49. package/dist/signer.js +0 -13
  50. package/dist/signer.js.map +0 -1
  51. package/dist/transport.d.ts.map +0 -1
  52. package/dist/transport.js +0 -135
  53. package/dist/transport.js.map +0 -1
  54. package/dist/validation.d.ts.map +0 -1
  55. package/dist/validation.js +0 -108
  56. package/dist/validation.js.map +0 -1
  57. package/dist/views.d.ts.map +0 -1
  58. package/dist/views.js +0 -2
  59. package/dist/views.js.map +0 -1
@@ -1,92 +1,11 @@
1
- /**
2
- * ONE PARSE. The string that is validated is the string that is requested.
3
- *
4
- * ------------------------------------------------------------------------------------------------
5
- * THE BUG THIS EXISTS TO CLOSE
6
- * ------------------------------------------------------------------------------------------------
7
- * `wallet_api_get` and `wallet_api_write` both take a free-form, model-supplied `path` and check it
8
- * against a generated manifest by compiling each manifest template into a regex, with a `{param}`
9
- * segment becoming `[^/]+`. The compiled string was then re-assembled and handed to `fetch`, whose
10
- * WHATWG URL parser re-parses it - and that parser does NOT agree with `[^/]+` about what a path
11
- * segment is. It normalises `\` to `/`, resolves `.` and `..` (including their percent-encoded forms
12
- * `%2e` / `%2e%2e`), and truncates everything from the first `#`. So:
13
- *
14
- * isKnownWritePath('/notifications/..\\userops#/read', 'POST') -> true
15
- * ...and the request actually issued was POST /v1/userops
16
- *
17
- * Two different strings: the one the allowlist inspected, and the one that went on the wire. Every
18
- * denylist entry in both manifests - the `/userops` relay boundary, `/approvals/{digest}/relay`,
19
- * `/solana/transactions`, `/agent-wallets/partner-provision`, and on the read side `/auth/session`,
20
- * which hands back the owner's live session token - was reachable through that gap.
21
- *
22
- * ------------------------------------------------------------------------------------------------
23
- * WHY THIS CLOSES THE CLASS RATHER THAN THE INSTANCES
24
- * ------------------------------------------------------------------------------------------------
25
- * Blocklisting the tokens (`..`, `\`, `#`) loses: there is always another encoding (`%2e%2e`, `%5c`,
26
- * `%23`, `%252e%252e`, and whatever the next parser quirk turns out to be). The root cause is not any
27
- * particular character - it is that VALIDATION AND REQUEST OPERATED ON DIFFERENT STRINGS. So this
28
- * module removes the difference instead of enumerating it:
29
- *
30
- * 1. The caller's path is canonicalised ONCE, up front, before anything looks at a manifest.
31
- * 2. Canonicalisation is a FIXED-POINT test against the very parser `fetch` uses: the path is
32
- * re-parsed with `new URL(...)`, and if `pathname + search` is not byte-identical to the input,
33
- * the path is refused. Anything the URL parser would rewrite - a traversal in any encoding, a
34
- * backslash, a fragment, a character it would percent-encode, a `//` that changes the host - is
35
- * by definition not a fixed point, and is rejected without this module ever having to know that
36
- * particular trick's name.
37
- * 3. A structural charset allowlist runs first, per segment, so the fixed-point test is a second
38
- * opinion rather than the only one: a segment may contain only unreserved/sub-delim characters
39
- * and well-formed `%XX` triplets, and must not decode - through as many layers of encoding as it
40
- * carries - to a separator, a fragment marker, or `.`/`..`.
41
- * 4. The single canonical result is what the caller then matches AND requests. There is no second
42
- * re-assembly step for the two to drift apart in.
43
- *
44
- * The `{param}` compilation is tightened to match: `PARAM_SEGMENT_PATTERN` is the same charset, so a
45
- * parameter structurally cannot hold a separator or a fragment marker even before rule 2 speaks.
46
- */
47
- /**
48
- * What a `{param}` in a manifest template compiles to. The old `[^/]+` was the bug: to a regex, `\`,
49
- * `#` and `..` are ordinary characters, and to the URL parser they are separators. This charset holds
50
- * no separator and no fragment marker in any form the URL parser recognises.
51
- */
52
1
  export declare const PARAM_SEGMENT_PATTERN = "(?:[A-Za-z0-9\\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})+";
53
- /** The canonical form of a caller-supplied path: one object, carried from validation to `fetch`. */
54
2
  export interface CanonicalHttpPath {
55
- /** The path, no query string. Byte-identical to what will be requested. */
56
3
  readonly path: string;
57
- /** The query string including its leading `?`, or `''`. Byte-identical to what will be requested. */
58
4
  readonly query: string;
59
- /** `path + query` - exactly the string that goes on the wire after the base URL. */
60
5
  readonly full: string;
61
6
  }
62
- /**
63
- * Canonicalise a caller-supplied request path, or return null if it is not canonical.
64
- *
65
- * "Canonical" means: an absolute path (never protocol-relative, which would change the HOST), every
66
- * segment drawn from the safe charset and free of any encoded separator or traversal, an optional
67
- * query drawn from its own charset, no fragment at all - and, as the final word, a string the WHATWG
68
- * URL parser reproduces unchanged. Callers must use the returned strings, never their own input.
69
- */
70
7
  export declare function canonicalizeHttpPath(raw: unknown): CanonicalHttpPath | null;
71
- /**
72
- * Strip the `/v1` prefix, if present, so `/balance` and `/v1/balance` resolve to the same manifest
73
- * entry. The OpenAPI contract declares paths WITHOUT the version prefix (`/v1` lives in
74
- * `servers[].url`), so the generated manifests are un-prefixed - this is where the two conventions
75
- * meet. Operates on an ALREADY-CANONICAL path; the old code did this to the raw input, which is how a
76
- * `/v1`-prefixed bypass got a second bite.
77
- */
78
8
  export declare function stripVersionPrefix(canonicalPath: string): string;
79
- /**
80
- * Compile one manifest template (`/agents/{id}`) into a matcher for a concrete canonical path
81
- * (`/agents/agt_1`). Shared by both allowlists so they cannot drift into two different ideas of what a
82
- * path parameter may contain - which is how one of them ends up with the other's hole.
83
- */
84
9
  export declare function compileRoutePattern(template: string): RegExp;
85
- /** Cap what a refusal echoes back, so a pathological input cannot flood a tool result. */
86
10
  export declare function describePath(raw: unknown): string;
87
- /**
88
- * The refusal text every caller shares, so the reason a path was rejected reads the same wherever it
89
- * is rejected - and names the real cause rather than the character that happened to trip it.
90
- */
91
11
  export declare function nonCanonicalPathMessage(raw: unknown, tool: string): string;
92
- //# sourceMappingURL=http-path.d.ts.map
package/dist/index.d.ts CHANGED
@@ -7,5 +7,4 @@ export { registerAgent, type AgentRegistrationClient } from './registration.js';
7
7
  export type * from './views.js';
8
8
  export { PairingCodeManager, DEFAULT_PAIRING_CODE_TTL_MS, type PairingCode } from './pairing-code.js';
9
9
  export { crockfordBase32, deriveFingerprint, generatePairingNonce, FINGERPRINT_BYTES, NONCE_BYTES } from './fingerprint.js';
10
- export type { ExactAgentAction, ExactAgentExecution, AgentActionBinding, AgentSwapBinding, SwapRouteFacts, PreparedAgentAction } from './exact-action.js';
11
- //# sourceMappingURL=index.d.ts.map
10
+ export type { ExactAgentAction, ExactAgentExecution, AgentActionBinding, AgentSwapBinding, SwapRouteFacts, PreparedAgentAction, AgentMandateProjection, MandateTransferInput, MandateSwapInput, MandateActionInput, AgentPolicyRefusal } from './exact-action.js';
package/dist/index.js CHANGED
@@ -1,7 +1,2 @@
1
- export { AgentRuntime, awaitTerminalWith, DEFAULT_API_BASE_URL } from './runtime.js';
2
- export { BotanaryApiError } from './errors.js';
3
- export { createViemAgentSigner } from './signer.js';
4
- export { registerAgent } from './registration.js';
5
- export { PairingCodeManager, DEFAULT_PAIRING_CODE_TTL_MS } from './pairing-code.js';
6
- export { crockfordBase32, deriveFingerprint, generatePairingNonce, FINGERPRINT_BYTES, NONCE_BYTES } from './fingerprint.js';
7
- //# sourceMappingURL=index.js.map
1
+ var h=class extends Error{constructor(t,n,i=null,d=null){super(t);this.status=n;this.details=i;this.retryAfterSeconds=d;this.name="BotanaryApiError"}requestId;code;operationId;userOpHash;requirementId;ambiguous=!1};import{encodeFunctionData as O,encodePacked as Oe,decodeFunctionData as j,parseAbi as D,getAddress as v,zeroAddress as Be,zeroHash as He}from"viem";import{entryPoint07Address as ae,getUserOperationHash as Te}from"viem/account-abstraction";import{publicKeyToAddress as _e}from"viem/accounts";function o(){let r=new h("Botanary returned an invalid response. Nothing was signed.",0);throw r.code="invalid_response",r}function f(r){return typeof r!="object"||r===null||Array.isArray(r)?o():r}function u(r,e){(typeof r!="string"||!/^0x(?:[0-9a-fA-F]{2})*$/.test(r)||e!==void 0&&r.length!==2+2*e)&&o()}function A(r){(typeof r!="string"||!/^[A-Za-z0-9_-]{1,128}$/.test(r)||/^(?:bsk|deo|ags|dcs|dca|dcr|dcc|dcq|din)_/.test(r))&&o()}function k(r){if(typeof r!="string")return!1;let e=/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.exec(r);if(!e)return!1;let t=Number(e[1]),n=Number(e[2]),i=Number(e[3]),s=[31,t%4===0&&(t%100!==0||t%400===0)?29:28,31,30,31,30,31,31,30,31,30,31];return t>=1&&n>=1&&n<=12&&i>=1&&i<=s[n-1]&&Number(e[4])<=23&&Number(e[5])<=59&&Number(e[6])<=59&&Number.isFinite(Date.parse(r))}function N(r){(typeof r!="string"||!/^(0|[1-9][0-9]{0,77})$/.test(r)||BigInt(r)>=2n**256n)&&o()}function U(r){let e=f(r);return u(e.address,20),u(e.publicKey,65),(!e.publicKey.startsWith("0x04")||_e(e.publicKey).toLowerCase()!==e.address.toLowerCase()||typeof e.fingerprint!="string"||!/^[A-Za-z0-9]{1,32}$/.test(e.fingerprint)||typeof e.backend!="string"||!/^[A-Za-z0-9_-]{1,64}$/.test(e.backend)||!k(e.createdAt))&&o(),Object.freeze({address:e.address,publicKey:e.publicKey,fingerprint:e.fingerprint,backend:e.backend,createdAt:e.createdAt})}function z(r){let e=f(r);return(typeof e.intentType!="string"||!e.intentType||typeof e.userOpHash!="string")&&o(),u(e.userOpHash,32),E(e.userOp),e}function Ee(r){let e=f(r);return u(e.to,20),u(e.data),N(e.value),(!Number.isSafeInteger(e.chainId)||Number(e.chainId)<1)&&o(),{to:e.to,data:e.data,value:e.value,chainId:Number(e.chainId)}}function ie(r){let e=f(r);return(!["id","agentId","accountId","declineReason","reason"].every(t=>typeof e[t]=="string"&&e[t].length>0)||!Array.isArray(e.calls)||!k(e.raisedAt)||!k(e.expiresAt)||Date.parse(e.expiresAt)<=Date.parse(e.raisedAt)||!["open","approved","expired","withdrawn"].includes(String(e.status)))&&o(),{...e,calls:e.calls.map(Ee)}}function se(r){let e=f(r);if(u(e.address,20),(!["id","name","fingerprint","accountId"].every(t=>typeof e[t]=="string"&&e[t].length>0)||!k(e.connectedAt)||!(e.accountAddress==null||typeof e.accountAddress=="string")||e.accountAddress!=null&&!/^0x[0-9a-fA-F]{40}$/.test(e.accountAddress))&&o(),e.grant!==null){let t=f(e.grant);u(t.permissionId,32),(typeof t.id!="string"||!t.id||typeof t.status!="string"||!t.status||typeof t.humanSummary!="string"||typeof t.chain!="string"||!Number.isSafeInteger(t.chainId)||!f(t.policySet)||!Array.isArray(t.spentToDate))&&o()}if(e.accountSetup!=null){let t=f(e.accountSetup);(typeof t.ready!="boolean"||!Array.isArray(t.missing)||!t.missing.every(n=>typeof n=="string")||t.ready!==(t.missing.length===0)||!Number.isSafeInteger(t.chainId)||Number(t.chainId)<1)&&o()}return e}function b(r,e=256){if(typeof r!="string"||!/^(0|[1-9][0-9]{0,77}|0x[0-9a-fA-F]{1,64})$/.test(r))return o();let t=BigInt(r);return t>=2n**BigInt(e)?o():t}function E(r){let e=f(r);for(let t of["sender","entryPoint"])u(e[t],20);for(let t of["nonce","callGasLimit","verificationGasLimit","preVerificationGas","maxFeePerGas","maxPriorityFeePerGas"])b(e[t]);for(let t of["callData","signature"])u(e[t]);for(let t of["paymasterVerificationGasLimit","paymasterPostOpGasLimit"])e[t]!=null&&b(e[t],128);(!Number.isSafeInteger(e.chainId)||Number(e.chainId)<=0)&&o();for(let t of["factory","paymaster"])e[t]!=null&&u(e[t],20);for(let t of["factoryData","paymasterData"])e[t]!=null&&u(e[t]);return r}function $(r){let e=f(r);u(e.payload,32),u(e.validator,20),(!Number.isSafeInteger(e.index)||Number(e.index)<0||Number(e.index)>=2**32)&&o();let t=f(e.authorization);return u(t.from,20),u(t.to,20),u(t.nonce,32),N(t.value),N(t.validAfter),N(t.validBefore),BigInt(t.validBefore)<=BigInt(t.validAfter)&&o(),{payload:e.payload,validator:e.validator,index:Number(e.index),authorization:{from:t.from,to:t.to,nonce:t.nonce,value:t.value,validAfter:t.validAfter,validBefore:t.validBefore}}}function F(r,e){let t=f(r);(t.requirementId!==e||typeof t.simulated!="boolean")&&o();let n={ready:["not_submitted"],pending_approval:["not_submitted"],submitted:["pending"],settled:["settled"],declined:["not_submitted","not_settled"]};return(typeof t.status!="string"||!Object.hasOwn(n,t.status)||!n[t.status].includes(String(t.paymentStatus)))&&o(),t.providerStatus!==void 0&&(!Number.isSafeInteger(t.providerStatus)||Number(t.providerStatus)<100||Number(t.providerStatus)>599)&&o(),t.txHash!==void 0&&u(t.txHash,32),t.status==="settled"&&u(t.txHash,32),t.status==="ready"?$(t.ready):t.ready!==void 0&&o(),t}var oe=D(["function execute(bytes32 mode, bytes executionCalldata) payable"]),W=D(["function executeUnderGrant(bytes32 permissionId, address target, uint256 value, bytes callData)","function executeUnderGrantWithAllowance(bytes32 permissionId, address target, uint256 value, address token, uint256 allowance, bytes callData)"]),Ce=D(["function transfer(address to, uint256 amount) returns (bool)"]),de=D(["function swapExactIn(address tokenIn, address tokenOut, uint256 amountIn, uint256 minOut, address to) returns (uint256 amountOut)"]),Ne=BigInt("0x000100000000008bdaba73cd9815d79069c247eb4bda0000");function y(r,e){return typeof r=="string"&&r.toLowerCase()===e.toLowerCase()}function I(r,e=!0){if(typeof r!="string"||!/^(0|[1-9][0-9]{0,77})$/.test(r))return o();let t=BigInt(r);return t>=2n**256n||e&&t===0n?o():t}function ce(r){let e=f(r),t=["accountId","accountAddress","chainId","action","tokenAddress","amount","recipient","tokenOutAddress","maxSlippageBps"];return Object.keys(e).some(n=>!t.includes(n))&&o(),A(e.accountId),u(e.accountAddress,20),u(e.tokenAddress,20),I(e.amount),(!Number.isSafeInteger(e.chainId)||Number(e.chainId)<=0)&&o(),e.action==="transfer"?(u(e.recipient,20),(e.tokenOutAddress!==void 0||e.maxSlippageBps!==void 0)&&o()):e.action==="swap"?(u(e.tokenOutAddress,20),(e.recipient!==void 0||e.maxSlippageBps!==void 0&&(!Number.isInteger(e.maxSlippageBps)||Number(e.maxSlippageBps)<1||Number(e.maxSlippageBps)>1e3))&&o()):o(),Object.freeze({...r,accountAddress:v(r.accountAddress),tokenAddress:v(r.tokenAddress),...r.recipient!==void 0?{recipient:v(r.recipient)}:{},...r.tokenOutAddress!==void 0?{tokenOutAddress:v(r.tokenOutAddress)}:{}})}function ue(r,e){if(A(r.delegationId),u(r.permissionId,32),u(r.executorAddress,20),I(r.maxGasCostWei),e.action==="swap"){let t=r;u(t.routerAddress,20),I(t.minAmountOut),t.verifyRoute!==void 0&&typeof t.verifyRoute!="function"&&o()}return Object.freeze({...r,executorAddress:v(r.executorAddress),...e.action==="swap"?{routerAddress:v(r.routerAddress)}:{}})}async function pe(r,e,t){try{let n=f(r),i=f(n.execution),d=E(n.userOp),s=Object.freeze({...d,sender:v(d.sender),entryPoint:v(d.entryPoint)});u(n.userOpHash,32),(n.intentType!=="delegated_action"||f(n.simulation).willSucceed!==!0||i.accountId!==e.accountId||!y(i.accountAddress,e.accountAddress)||i.chainId!==e.chainId||i.action!==e.action||i.amount!==e.amount||!y(i.tokenAddress,e.tokenAddress)||i.delegationId!==t.delegationId||!y(i.permissionId,t.permissionId)||i.gasMethod!=="native"||!y(s.sender,e.accountAddress)||s.chainId!==e.chainId||!y(s.entryPoint,ae)||s.factory!=null||s.factoryData!=null||s.paymaster!=null||s.paymasterData!=null||s.paymasterVerificationGasLimit!=null||s.paymasterPostOpGasLimit!=null)&&o();let a=b(s.nonce);a>>64n!==Ne&&o();let c=b(s.callGasLimit,128),g=b(s.verificationGasLimit,128),p=b(s.preVerificationGas),x=b(s.maxFeePerGas,128),l=b(s.maxPriorityFeePerGas,128);(l>x||(c+g+p)*x>I(t.maxGasCostWei))&&o();let m=s.callData,R;if(e.action==="transfer"){(!y(i.recipient,e.recipient)||i.tokenOutAddress!==void 0||i.maxSlippageBps!==void 0)&&o();let w=y(e.tokenAddress,Be);R=O({abi:W,functionName:"executeUnderGrant",args:[t.permissionId,w?e.recipient:e.tokenAddress,w?I(e.amount):0n,w?"0x":O({abi:Ce,functionName:"transfer",args:[e.recipient,I(e.amount)]})]})}else{let w=t;(!y(i.tokenOutAddress,e.tokenOutAddress)||i.recipient!==void 0||i.maxSlippageBps!==(e.maxSlippageBps??50))&&o();let M=j({abi:oe,data:m}).args[1],S=j({abi:W,data:`0x${M.slice(106)}`});S.functionName!=="executeUnderGrantWithAllowance"&&o();let _=S.args[5],C=Object.freeze({chainId:e.chainId,accountAddress:e.accountAddress,routerAddress:w.routerAddress,tokenAddress:e.tokenAddress,tokenOutAddress:e.tokenOutAddress,amount:e.amount,minAmountOut:w.minAmountOut,callData:_});if(w.verifyRoute)await w.verifyRoute(C)!==!0&&o();else{let re=j({abi:de,data:_}),[ke,ve,Re,Se,Pe]=re.args;(!y(ke,e.tokenAddress)||!y(ve,e.tokenOutAddress)||Re!==I(e.amount)||Se<I(w.minAmountOut)||!y(Pe,e.accountAddress)||!y(O({abi:de,functionName:"swapExactIn",args:re.args}),_))&&o()}R=O({abi:W,functionName:"executeUnderGrantWithAllowance",args:[t.permissionId,w.routerAddress,0n,e.tokenAddress,I(e.amount),_]})}let P=O({abi:oe,functionName:"execute",args:[He,Oe(["address","uint256","bytes"],[t.executorAddress,0n,R])]});y(m,P)||o();let H=Te({entryPointAddress:ae,entryPointVersion:"0.7",chainId:e.chainId,userOperation:{sender:s.sender,nonce:a,callData:s.callData,callGasLimit:c,verificationGasLimit:g,preVerificationGas:p,maxFeePerGas:x,maxPriorityFeePerGas:l,signature:"0x"}});y(H,n.userOpHash)||o();let G=Object.freeze({...e,delegationId:t.delegationId,permissionId:t.permissionId,gasMethod:"native",...e.action==="swap"?{maxSlippageBps:e.maxSlippageBps??50}:{}});return{userOp:Object.freeze({sender:s.sender,chainId:s.chainId,entryPoint:s.entryPoint,nonce:s.nonce,callData:s.callData,callGasLimit:s.callGasLimit,verificationGasLimit:s.verificationGasLimit,preVerificationGas:s.preVerificationGas,maxFeePerGas:s.maxFeePerGas,maxPriorityFeePerGas:s.maxPriorityFeePerGas,signature:"0x"}),userOpHash:H,execution:G}}catch{return o()}}function V(r,e){try{let t=f(r);if(A(t.id),!y(t.userOpHash,e)||!["pending","included","failed"].includes(String(t.status)))throw new Error;return t.txHash!=null&&u(t.txHash,32),t}catch{let t=new h("Botanary returned an invalid receipt. Reconcile using the original operation hash.",0);throw t.code="invalid_response",t.userOpHash=e,t.ambiguous=!0,t}}import{keccak256 as Ye,toHex as Je,encodePacked as xe,encodeAbiParameters as Qe}from"viem";import{randomBytes as Ue}from"node:crypto";import{hexToBytes as $e}from"viem";var le="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function K(r){let e=0,t=0,n="";for(let i of r)for(e=e<<8|i,t+=8;t>=5;)n+=le[e>>>t-5&31],t-=5;return t>0&&(n+=le[e<<5-t&31]),n}var ge=3,me=5;function Z(r){let e=$e(r);return K(e.slice(0,ge))}function X(){return K(Ue(me))}var fe=600*1e3,B=class{#r;#e;#d;#a;#i=null;constructor(e,t){if(!/^[0-9A-HJKMNP-TV-Z]{5}$/.test(e))throw new TypeError("Invalid agent fingerprint.");if(t?.ttlMs!==void 0&&(!Number.isSafeInteger(t.ttlMs)||t.ttlMs<1e3||t.ttlMs>36e5))throw new TypeError("Invalid pairing code lifetime.");this.#r=e,this.#e=t?.ttlMs??fe,this.#d=t?.now??Date.now,this.#a=t?.nonce??X}current(){let e=this.#d();return(!this.#i||this.#i.expiresAt<=e)&&this.#c(e),this.#s()}regenerate(){return this.#c(this.#d()),this.#s()}#c(e){if(!Number.isSafeInteger(e)||e<0||e>864e13)throw new TypeError("Invalid pairing clock.");let t=this.#a();if(!/^[0-9A-HJKMNP-TV-Z]{8}$/.test(t))throw new TypeError("Invalid pairing nonce.");this.#i={nonce:t,issuedAt:e,expiresAt:e+this.#e}}#s(){let e=this.#i;if(!e)throw new Error("unreachable: no pairing code minted");return{code:`${this.#r}-${e.nonce}`,fingerprint:this.#r,nonce:e.nonce,issuedAt:new Date(e.issuedAt).toISOString(),expiresAt:new Date(e.expiresAt).toISOString()}}};import je from"openapi-fetch";var he=[{method:"post",path:"/agents/pair"},{method:"post",path:"/agents/session/nonce"},{method:"post",path:"/agents/session"},{method:"get",path:"/agents/me"},{method:"get",path:"/agents/requests"},{method:"post",path:"/agents/requests"},{method:"get",path:"/balance"},{method:"get",path:"/chains"},{method:"get",path:"/gas/methods"},{method:"get",path:"/account"},{method:"post",path:"/delegations/{delegationId}/actions"},{method:"post",path:"/delegations/{delegationId}/actions/exact"},{method:"post",path:"/userops"},{method:"get",path:"/userops/{userOpId}"},{method:"get",path:"/userops/by-hash/{chainId}/{userOpHash}"},{method:"get",path:"/apis/providers"},{method:"get",path:"/apis/providers/{providerId}"},{method:"get",path:"/apis/budget"},{method:"post",path:"/apis/calls/requirements"},{method:"get",path:"/apis/calls/requirements/{id}"},{method:"post",path:"/apis/calls/relay"},{method:"get",path:"/apis/calls/relay/{id}"}];var ye="https://botanary-canonical-path.invalid",Ae="(?:[A-Za-z0-9\\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})",xt=`${Ae}+`,De=new RegExp(`^${Ae}+$`),qe=new RegExp("^(?:[A-Za-z0-9\\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*$"),Ge=/^\.{1,2}$/,Le=/[/\\#?]/,Me=/[\u0000-\u001f\u007f]/,ze=6;function Fe(r){let e=r;for(let t=0;t<ze;t+=1){if(Ge.test(e)||Le.test(e)||Me.test(e))return!0;if(!e.includes("%"))return!1;let n;try{n=decodeURIComponent(e)}catch{return!0}if(n===e)return!1;e=n}return!0}function we(r){if(typeof r!="string"||r.length===0||!r.startsWith("/")||r.startsWith("//"))return null;let e=r.indexOf("?"),t=e===-1?r:r.slice(0,e),n=e===-1?"":r.slice(e),i=t.split("/");for(let s=1;s<i.length;s+=1){let a=i[s];if(!De.test(a)||Fe(a))return null}if(n!==""&&!qe.test(n.slice(1)))return null;let d;try{d=new URL(t+n,ye)}catch{return null}return d.origin!==ye||d.hash!==""||d.pathname!==t||d.search!==n?null:{path:t,query:n,full:t+n}}var q=class{#r;constructor(e,t=fetch){let n=new URL(e);if(n.username||n.password||n.search||n.hash||n.pathname!=="/"||n.protocol!=="https:"&&!(n.protocol==="http:"&&["localhost","127.0.0.1","[::1]"].includes(n.hostname)))throw new Error("Agent API origin must be HTTPS, or HTTP on loopback.");this.#r=je({baseUrl:n.origin+"/v1",fetch:Xe(t)})}get(e,t){return this.#e("get",e,void 0,t)}post(e,t,n){return this.#e("post",e,t,n)}async#e(e,t,n,i){let d=we(t);if(!d?.path.startsWith("/v1/"))throw new Error("Invalid agent API path.");if(i!==void 0&&!/^ags_[a-f0-9]{64}$/.test(i))throw new Error("Expected an agent session credential.");let s=d.path.slice(3),a=he.find(l=>l.method===e&&new RegExp("^"+l.path.replace(/\{[^}]+\}/g,"[A-Za-z0-9_-]+")+"$").test(s));if(!a)throw new Error("This route is outside the reviewed agent API.");let c={};a.path.split("/").forEach((l,m)=>{l.startsWith("{")&&(c[l.slice(1,-1)]=s.split("/")[m])});let g=Object.fromEntries(new URLSearchParams(d.query)),p=new AbortController,x=setTimeout(()=>p.abort(),3e4);try{let l=await this.#r.request(e,a.path,{params:{path:c,query:g},...n===void 0?{}:{body:n},headers:i?{authorization:`Bearer ${i}`}:{},credentials:"omit",redirect:"error",cache:"no-store",signal:p.signal,parseAs:"text"}),m=l.response,R=l.error;if(m.ok)try{R=l.data?JSON.parse(l.data):{}}catch{throw new Error("Invalid JSON response.")}if(!m.ok){let P=Y(Y(R)?.error),H=typeof P?.message=="string"?P.message:`Agent request failed with ${m.status}.`,G=We(H).replace(/0x[a-fA-F0-9]{64,}/g,"[redacted]"),T=m.headers.get("retry-after"),w=T&&/^\d+(?:\.\d+)?$/.test(T)?Number(T):null,L=Y(P?.details),M=L&&Object.fromEntries(Object.entries(L).filter(([_,C])=>["missingModule","declineReason","chainId"].includes(_)&&(typeof C=="number"||Ze(C)))),S=new h(G.slice(0,1e3),m.status,M,w);throw S.requestId=Ve(m.headers.get("x-request-id")),S.code=Ke(P?.code)??"http_error",S.ambiguous=e==="post"&&m.status>=500,S}return R}catch(l){if(l instanceof h)throw l;let m=new h("The agent request did not complete. Check the existing operation before retrying.",0);throw m.code=p.signal.aborted?"timeout":"transport_error",m.ambiguous=e==="post",m}finally{clearTimeout(x)}}};function Y(r){return typeof r=="object"&&r!==null&&!Array.isArray(r)?r:null}var J=/(?:bsk_(?:test|live)|deo_(?:test|live)|dcs|dca|dcr|dcc|dcq|ags|din|whsec)_[A-Za-z0-9_+/=-]+/g;function Q(r){return J.lastIndex=0,!J.test(r)}function We(r){return r.replace(J,"[redacted]")}function Ve(r){return typeof r=="string"&&/^req_[A-Za-z0-9_-]{1,124}$/.test(r)&&Q(r)?r:void 0}function Ke(r){return typeof r=="string"&&/^[a-z][a-z0-9_]{0,63}$/.test(r)&&Q(r)?r:void 0}function Ze(r){return typeof r=="string"&&/^[a-zA-Z0-9_-]{1,100}$/.test(r)&&Q(r)}function Xe(r){return async(e,t)=>{let n=e instanceof Request?e:new Request(e,t),i=n.signal,d=()=>{},s=new Promise((c,g)=>{d=()=>g(new Error("Request aborted.")),i.aborted?d():i.addEventListener("abort",d,{once:!0})}),a;try{let c=await Promise.race([r(n),s]);if(!c.body)return c;a=c.body.getReader();let g=[],p=0;for(;;){let m=await Promise.race([a.read(),s]);if(m.done)break;if(p+=m.value.byteLength,p>4*1024*1024)throw new Error("Response too large.");g.push(m.value)}let x=new Uint8Array(p),l=0;for(let m of g)x.set(m,l),l+=m.byteLength;return new Response(x,{status:c.status,statusText:c.statusText,headers:c.headers})}finally{i.removeEventListener("abort",d),a&&a.cancel().catch(()=>{})}}}import{recoverAddress as et}from"viem";async function ee(r,e,t,n,i){if(!Number.isFinite(t)||t<0||t>36e5)throw new Error("Invalid wait budget.");let d=n(),s=await r(e);for(;["pending","pending_approval","submitted","queued"].includes(String(s.status))&&n()-d<t;)await i(1500),s=await r(e);return s}var tt=3e4,be=300*1e3,Ie="https://api.app.botanary.xyz",te=class{#r;#e;#d;#a;#i=null;#c=null;#s=null;#u=null;#p=null;#n=0;#g;constructor(e,t={}){if(this.#r=e,this.#d=t.baseUrl??Ie,this.#e=t.transport??new q(this.#d,t.fetch),this.#a=t.provenance??{},t.maxGasCostWei!==void 0&&(!/^[1-9][0-9]{0,77}$/.test(t.maxGasCostWei)||BigInt(t.maxGasCostWei)>=2n**256n))throw new Error("Use a positive integer agent gas ceiling in wei.");this.#g=t.maxGasCostWei??null}get apiBaseUrl(){return this.#d}get identityMaterialized(){return this.#i!==null||this.#u!==null}setProvenance(e){this.#a=e}replaceSigner(e){if(this.identityMaterialized)throw new Error("Cannot replace a materialized agent signer.");this.#r=e,this.reset()}async identity(){if(this.#i)return this.#i;if(this.#u)return this.#u;let e=this.#n,t=(async()=>{let n;try{n=await this.#r.ensure()}catch{throw this.#o()}if(e!==this.#n)throw this.#o();let i=U(n);return this.#i=i,i})();this.#u=t;try{return await t}finally{this.#u===t&&(this.#u=null)}}#o(){let e=new h("The agent signer could not complete the request.",0);return e.code="signer_error",e}async#l(e){u(e,32);let t=this.#n,n=await this.identity();if(t!==this.#n)throw this.#o();try{let i=await this.#r.signHash(e);u(i,65);let d=await et({hash:e,signature:i});if(t!==this.#n||d.toLowerCase()!==n.address.toLowerCase())throw this.#o();return i}catch{throw this.#o()}}async pairingCode(){return(await this.#f()).current()}async regeneratePairingCode(){return(await this.#f()).regenerate()}async pair(){let e=await this.pairingCode(),t=Math.floor(Date.now()/1e3),n=await this.identity(),i=`${e.code}|${t}`,d=Ye(Je(i));return await this.#e.post("/v1/agents/pair",{code:e.code,publicKey:n.publicKey,timestamp:t,signature:await this.#l(d),...this.#a.client?{client:this.#a.client}:{},...this.#a.profile?{profile:this.#a.profile}:{}}),e}async mintSession(){if(this.#p)return this.#p;let e=this.#n,t=(async()=>{let n=await this.identity(),i=f(await this.#e.post("/v1/agents/session/nonce",{address:n.address}));if(u(i.nonce,32),e!==this.#n)throw this.#o();let d=await this.#l(i.nonce),s=f(await this.#e.post("/v1/agents/session",{address:n.address,nonce:i.nonce,signature:d}));if((typeof s.token!="string"||!/^ags_[a-f0-9]{64}$/.test(s.token)||!k(s.expiresAt)||Date.parse(s.expiresAt)<=Date.now()||Date.parse(s.expiresAt)>Date.now()+366e4)&&o(),e!==this.#n)throw this.#o();return this.#s={token:s.token,safeUntil:Date.parse(s.expiresAt)-tt},s.token})();this.#p=t;try{return await t}finally{this.#p===t&&(this.#p=null)}}async ensureSession(){return this.#s&&this.#s.safeUntil>Date.now()?this.#s.token:this.mintSession()}async me(){return se(await this.#t(e=>this.#e.get("/v1/agents/me",e)))}async getBalance(){return this.#t(e=>this.#e.get("/v1/balance",e))}async listRequests(){return this.#t(e=>this.#e.get("/v1/agents/requests",e))}async listChains(){return this.#t(e=>this.#e.get("/v1/chains",e))}async getGasMethods(e){let t=new URLSearchParams;e.chainId!==void 0&&t.set("chainId",String(e.chainId)),e.accountId&&t.set("accountId",e.accountId),e.key&&t.set("key",e.key);let n=t.toString();return this.#t(i=>this.#e.get(`/v1/gas/methods${n?`?${n}`:""}`,i))}async getAccount(e){let t=new URLSearchParams;e.chainId!==void 0&&t.set("chainId",String(e.chainId));let n=t.toString();return this.#t(i=>this.#e.get(`/v1/account${n?`?${n}`:""}`,i))}async prepareExactAction(e,t){let n=ce(e),i=ue(t,n),d=this.#n,s=await this.#t(g=>this.#e.post(`/v1/delegations/${i.delegationId}/actions/exact`,n,g),!1),a=await pe(s,n,i),c;return Object.freeze({status:"prepared",userOpHash:a.userOpHash,execution:a.execution,submit:()=>(c||(c=(async()=>{if(d!==this.#n)throw this.#o();let g=await this.ensureSession();if(d!==this.#n)throw this.#o();return V(await this.spendUnderGrant(a.userOp,a.userOpHash,i.permissionId,g,"delegated_action"),a.userOpHash)})()),c),reconcile:()=>this.getUserOpByHash(n.chainId,a.userOpHash)})}async prepareAction(e){let t=e.action.type==="swap"?e.action.verifyRoute:void 0,{mandate:n,action:i}=structuredClone({mandate:e.mandate,action:{...e.action,verifyRoute:void 0}}),d=/^[1-9][0-9]{0,77}$/.test(i.amountRaw)?BigInt(i.amountRaw):0n,s=n.policy.perActionMaxRaw&&/^[1-9][0-9]{0,77}$/.test(n.policy.perActionMaxRaw)?BigInt(n.policy.perActionMaxRaw):null,a=n.chainState.remaining?.find(l=>l.token.toLowerCase()===i.tokenAddress.toLowerCase())?.remainingRaw??null,c=l=>Object.freeze({status:"refused",reason:l,transactionSubmitted:!1,requestedAmountRaw:i.amountRaw,perActionMaxRaw:n.policy.perActionMaxRaw,remainingRaw:a});if(n.chainId!==84532||!["submitted","unresolved","confirmed"].includes(n.createStatus)||n.chainState.status!=="active"||n.apiAccess!=="active")return c(n.revokeStatus==="confirmed"||n.chainState.status==="revoked"?"mandate_revoked":Date.parse(n.policy.expiresAt)<=Date.now()?"mandate_expired":"mandate_inactive");if(d===0n)return c("mandate_inactive");if(!n.policy.budgets.some(l=>l.token.toLowerCase()===i.tokenAddress.toLowerCase()))return c(i.type==="swap"?"unbudgeted_swap_leg":"mandate_inactive");if(s!==null&&d>s)return c("over_per_action_max");if(i.type==="transfer"&&n.policy.recipients.length>0&&!n.policy.recipients.some(l=>l.toLowerCase()===i.recipient.toLowerCase()))return c("wrong_recipient");if((await this.identity()).address.toLowerCase()!==n.agentAddress.toLowerCase())return c("mandate_inactive");if(!this.#g)throw new Error("Configure maxGasCostWei before preparing mandate actions.");let p={accountId:n.accountId,accountAddress:n.accountAddress,chainId:84532,tokenAddress:i.tokenAddress,amount:i.amountRaw},x={delegationId:n.id,permissionId:n.permissionId,executorAddress:n.grantExecutor,maxGasCostWei:this.#g};return i.type==="swap"?this.prepareExactAction({...p,action:"swap",tokenOutAddress:i.tokenOutAddress,...i.maxSlippageBps!=null?{maxSlippageBps:i.maxSlippageBps}:{}},{...x,routerAddress:i.routerAddress,minAmountOut:i.minAmountOutRaw,...t?{verifyRoute:t}:{}}):this.prepareExactAction({...p,action:"transfer",recipient:i.recipient},x)}async buildDelegatedAction(e){A(e.delegationId),u(e.recipient,20),(!Number.isFinite(e.amount)||e.amount<=0||e.amount>Number.MAX_SAFE_INTEGER)&&o(),e.token!==void 0&&(typeof e.token!="string"||!e.token||e.token.length>256)&&o();let t={recipient:e.recipient,amount:e.amount,...e.token?{token:e.token}:{}};return z(await this.#t(n=>this.#e.post(`/v1/delegations/${e.delegationId}/actions`,t,n),!1))}async buildDelegatedSwap(e){A(e.delegationId),(![e.tokenIn,e.tokenOut].every(n=>typeof n=="string"&&n.length>0&&n.length<=64)||!Number.isFinite(e.amountIn)||e.amountIn<=0||e.amountIn>Number.MAX_SAFE_INTEGER||e.maxSlippageBps!==void 0&&(!Number.isSafeInteger(e.maxSlippageBps)||e.maxSlippageBps<0||e.maxSlippageBps>1e4))&&o();let t={action:"swap",tokenIn:e.tokenIn,tokenOut:e.tokenOut,amountIn:e.amountIn,...e.maxSlippageBps!=null?{maxSlippageBps:e.maxSlippageBps}:{}};return z(await this.#t(n=>this.#e.post(`/v1/delegations/${e.delegationId}/actions`,t,n),!1))}async raiseRequest(e,t){return(!Array.isArray(e)||!e.length||e.length>100||typeof t!="string"||!t.trim()||t.length>2e3)&&o(),ie(await this.#t(n=>this.#e.post("/v1/agents/requests",{calls:e,reason:t},n),!1))}async spendUnderGrant(e,t,n,i,d){E(e),u(t,32),u(n,32);let s=await this.#l(t),a=xe(["bytes1","bytes32","bytes"],["0x00",n,s]);try{return await this.#e.post("/v1/userops",{userOp:{...e,signature:a},userOpHash:t,intentType:d},i)}catch(c){throw c instanceof h&&(c.userOpHash=t),c}}async getUserOp(e){return A(e),this.#t(t=>this.#e.get(`/v1/userops/${e}`,t))}async getUserOpByHash(e,t){return(!Number.isSafeInteger(e)||e<1)&&o(),u(t,32),V(await this.#t(n=>this.#e.get(`/v1/userops/by-hash/${e}/${t}`,n)),t)}async awaitTerminal(e,t){return ee(n=>this.getUserOp(n),e,t,()=>Date.now(),n=>new Promise(i=>setTimeout(i,n)))}async listApis(e){return this.#t(t=>this.#e.get(`/v1/apis/providers?chainId=${e}`,t))}async getApiProvider(e,t){let n=t!==void 0?`?chainId=${t}`:"";return this.#t(i=>this.#e.get(`/v1/apis/providers/${e}${n}`,i))}async callApi(e){let t=await this.#t(n=>this.#e.post("/v1/apis/calls/requirements",e,n),!1);return A(t?.requirementId),["ready","pending_approval"].includes(t.status)||o(),this.#m(t)}async resumeApi(e,t=be){A(e);let n=F(await this.#t(i=>this.#e.get(`/v1/apis/calls/relay/${e}`,i)),e);return n.status==="ready"?this.#m({requirementId:e,status:"ready",...$(n.ready)},t):n.status!=="pending_approval"?n:this.#m({requirementId:e,status:"pending_approval",approvalId:e,expiresAt:""},t)}async#m(e,t=be){let n;if(e.status==="pending_approval"){let a=await ee(c=>this.#t(g=>this.#e.get(`/v1/apis/calls/requirements/${c}`,g)),e.requirementId,t,()=>Date.now(),c=>new Promise(g=>setTimeout(g,c)));if(a.status==="approved"&&a.payload&&a.authorization&&a.index!==void 0&&a.validator)n={payload:a.payload,authorization:a.authorization,index:a.index,validator:a.validator};else{let c=a.status==="declined"?"owner_declined":a.status==="expired"?"approval_timed_out":"approval_still_pending",g=c==="owner_declined"?"The owner declined this call. Nothing was signed and nothing was charged.":c==="approval_timed_out"?"The owner did not respond before the approval window closed. Nothing was signed and nothing was charged.":"The owner has not answered yet. Nothing was signed or charged. Resume this requirement or ask the owner to check their pending approvals.";return["pending","declined","expired"].includes(a.status)||o(),{status:a.status==="pending"?"pending_approval":"declined",declineReason:c,message:g,requirementId:e.requirementId,approvalId:e.status==="pending_approval"?e.approvalId:void 0}}}else n=e;n=$(n);let i=await this.identity(),d=await this.#l(n.payload),s=xe(["bytes1","address","bytes"],["0x01",n.validator,Qe([{type:"address"},{type:"tuple",components:[{name:"from",type:"address"},{name:"to",type:"address"},{name:"value",type:"uint256"},{name:"validAfter",type:"uint256"},{name:"validBefore",type:"uint256"},{name:"nonce",type:"bytes32"}]},{type:"uint32"},{type:"bytes"}],[i.address,{from:n.authorization.from,to:n.authorization.to,value:BigInt(n.authorization.value),validAfter:BigInt(n.authorization.validAfter),validBefore:BigInt(n.authorization.validBefore),nonce:n.authorization.nonce},n.index,d])]);try{return F(await this.#t(a=>this.#e.post("/v1/apis/calls/relay",{requirementId:e.requirementId,signature:s},a),!1),e.requirementId)}catch(a){throw a instanceof h&&(a.requirementId=e.requirementId),a}}async getApiBudget(e){return this.#t(t=>this.#e.get(`/v1/apis/budget?chainId=${e}`,t))}reset(){this.#n+=1,this.#u=null,this.#p=null,this.#i=null,this.#c=null,this.#s=null}async#f(){if(!this.#c){let e=await this.identity();this.#c??=new B(e.fingerprint)}return this.#c}async#t(e,t=!0){let n=await this.ensureSession();try{return await e(n)}catch(i){if(t&&i instanceof h&&i.status===401){let d=this.#s&&this.#s.token!==n?await this.ensureSession():await this.mintSession();return await e(d)}throw i}}};function nt(r,e={}){let t={address:r.address,publicKey:r.publicKey,fingerprint:Z(r.address),backend:e.backend??"injected",createdAt:e.createdAt??new Date().toISOString()};return{ensure:async()=>({...t}),sign:n=>r.signMessage({message:n}),signHash:n=>r.sign({hash:n})}}import{recoverMessageAddress as rt}from"viem";async function it(r,e,t){if(typeof t!="string"||t.trim().length<1||t.trim().length>80)throw new Error("Agent name must contain 1 to 80 characters.");let n;try{n=U(await r.ensure())}catch{throw ne()}let i=await e.app.context();A(i.appId),A(i.keyId),["test","live"].includes(i.environment)||o();let d=n.publicKey.toLowerCase(),s=n.address.toLowerCase(),a=await e.agents.challenge({publicKey:d,name:t.trim()});(!a||a.appId!==i.appId||a.environment!==i.environment||a.publicKey!==d||a.address!==s||!/^dach_[A-Za-z0-9_-]{43}$/.test(a.id)||!k(a.expiresAt)||Date.parse(a.expiresAt)<=Date.now()||Date.parse(a.expiresAt)>Date.now()+36e4)&&o();let c=["Botanary agent registration","Purpose: register-agent-key","Audience: botanary:developer-platform:2026-09-07",`Application: ${i.appId}`,`Environment: ${i.environment}`,`API key: ${i.keyId}`,`Public key: ${d}`,`Address: ${s}`,`Nonce: ${a.id}`,`Expires: ${a.expiresAt}`,"This proof does not authorize an account connection or an on-chain grant."].join(`
2
+ `);a.message!==c&&o();let g;try{if(g=await r.sign(c),u(g,65),(await rt({message:c,signature:g})).toLowerCase()!==s)throw ne()}catch{throw ne()}let p=await e.agents.complete({challengeId:a.id,signature:g});return(!p||p.appId!==i.appId||p.environment!==i.environment||p.publicKey!==d||p.address!==s||p.grantStatus!=="unknown"||!["awaiting_connection","active","disabled","app_disabled","key_revoked","connection_revoked","unavailable"].includes(p.apiAccess)||p.apiAccess==="active"&&p.connectionId===null||p.apiAccess==="awaiting_connection"&&p.connectionId!==null)&&o(),p.connectionId!==null&&A(p.connectionId),p}function ne(){let r=new h("The agent signer could not complete registration.",0);return r.code="signer_error",r}export{te as AgentRuntime,h as BotanaryApiError,Ie as DEFAULT_API_BASE_URL,fe as DEFAULT_PAIRING_CODE_TTL_MS,ge as FINGERPRINT_BYTES,me as NONCE_BYTES,B as PairingCodeManager,ee as awaitTerminalWith,nt as createViemAgentSigner,K as crockfordBase32,Z as deriveFingerprint,X as generatePairingNonce,it as registerAgent};
@@ -1,41 +1,18 @@
1
- /** 10 minutes: long enough to alt-tab to the Botanary app and type ~13 characters, short enough that a
2
- * code nobody redeemed stops being worth anything on its own quickly. See README for the full
3
- * "why intercepting the code alone grants nothing" argument - the TTL is one layer of it, not the
4
- * whole thing. */
5
1
  export declare const DEFAULT_PAIRING_CODE_TTL_MS: number;
6
2
  export interface PairingCode {
7
- /** What the agent shows the user, and what they type into Connected agents: `${fingerprint}-${nonce}`. */
8
3
  code: string;
9
- /** The stable, non-secret address fingerprint half (see fingerprint.ts). */
10
4
  fingerprint: string;
11
- /** The fresh-per-code random half - this is what makes the code single-use/short-lived. */
12
5
  nonce: string;
13
6
  issuedAt: string;
14
7
  expiresAt: string;
15
8
  }
16
- /**
17
- * Owns exactly one pairing attempt at a time for this process, keyed off a fixed address fingerprint.
18
- * `current()` is idempotent while the code is still valid - Flow 7d step 1 is "the agent ... shows the
19
- * short code, and waits", which only makes sense if the code stays put while the owner is busy typing
20
- * it, rather than rotating out from under them on every tool call. It mints automatically once the
21
- * previous code expires, and `regenerate()` lets the owner (via the agent) burn a code early - e.g. if
22
- * they suspect it was seen by someone else - without waiting out the TTL.
23
- *
24
- * Deliberately in-memory only: a pairing code is not part of the identity (see store.ts) and is not
25
- * meant to survive a restart - a fresh process showing a fresh code is the correct behavior, not a bug.
26
- */
27
9
  export declare class PairingCodeManager {
28
10
  #private;
29
11
  constructor(fingerprint: string, options?: {
30
12
  ttlMs?: number;
31
- /** injectable for deterministic tests */
32
13
  now?: () => number;
33
- /** injectable for deterministic tests */
34
14
  nonce?: () => string;
35
15
  });
36
- /** The active code, minting a fresh one if there is none yet or the previous one expired. */
37
16
  current(): PairingCode;
38
- /** Always mints a fresh code, invalidating whatever was active. */
39
17
  regenerate(): PairingCode;
40
18
  }
41
- //# sourceMappingURL=pairing-code.d.ts.map
@@ -1,7 +1,6 @@
1
1
  import type { AgentSigner } from './signer.js';
2
2
  import type { components } from './generated/schema.js';
3
3
  type Schemas = components['schemas'];
4
- /** Structurally compatible with @botanary/sdk. Only the server client handles its app credential. */
5
4
  export interface AgentRegistrationClient {
6
5
  app: {
7
6
  context(): Promise<{
@@ -15,7 +14,5 @@ export interface AgentRegistrationClient {
15
14
  complete(input: Schemas['DeveloperAgentRegistrationInput']): Promise<Schemas['DeveloperAgentRegistration']>;
16
15
  };
17
16
  }
18
- /** Registration proves possession of this agent key. It creates neither owner consent nor a grant. */
19
17
  export declare function registerAgent(signer: AgentSigner, client: AgentRegistrationClient, name: string): Promise<Schemas['DeveloperAgentRegistration']>;
20
18
  export {};
21
- //# sourceMappingURL=registration.d.ts.map
package/dist/runtime.d.ts CHANGED
@@ -1,20 +1,11 @@
1
- import { type ExactAgentAction, type AgentActionBinding, type AgentSwapBinding, type PreparedAgentAction } from './exact-action.js';
1
+ import { type ExactAgentAction, type AgentActionBinding, type AgentSwapBinding, type PreparedAgentAction, type MandateActionInput, type AgentPolicyRefusal } from './exact-action.js';
2
2
  import { type Hex } from 'viem';
3
3
  import { type PairingCode } from './pairing-code.js';
4
4
  import { type AgentTransport } from './transport.js';
5
5
  import type { AgentIdentity, AgentSigner } from './signer.js';
6
6
  import type { AgentSelf, AgentRequestView, UnsignedCallView, UserOpBuildResult, UnsignedUserOp } from './views.js';
7
- /**
8
- * Poll `get` until the op reports a terminal status or `budgetMs` is spent, then return whatever the
9
- * backend last said - VERBATIM. A budget lapse returns the real `pending` answer; this helper never
10
- * invents a status, and never blocks forever.
11
- *
12
- * Pure and injectable (clock + sleep) so its behaviour is testable without timers or a network.
13
- */
14
7
  export declare function awaitTerminalWith(get: (opId: string) => Promise<Record<string, unknown>>, opId: string, budgetMs: number, now: () => number, sleep: (ms: number) => Promise<void>): Promise<Record<string, unknown>>;
15
- /** Default API origin. The shared runtime reads no environment variables; its host supplies overrides. */
16
8
  export declare const DEFAULT_API_BASE_URL = "https://api.app.botanary.xyz";
17
- /** Per-instance agent identity and sessions. The signer owns all private-key storage. */
18
9
  export declare class AgentRuntime {
19
10
  #private;
20
11
  constructor(signer: AgentSigner, options?: {
@@ -25,6 +16,7 @@ export declare class AgentRuntime {
25
16
  client?: string;
26
17
  profile?: string;
27
18
  };
19
+ maxGasCostWei?: string;
28
20
  });
29
21
  get apiBaseUrl(): string;
30
22
  protected get identityMaterialized(): boolean;
@@ -33,91 +25,32 @@ export declare class AgentRuntime {
33
25
  profile?: string;
34
26
  }): void;
35
27
  protected replaceSigner(signer: AgentSigner): void;
36
- /** The agent's public identity, creating it on first call if none exists yet. Cached for the life of
37
- * this runtime (the address/fingerprint cannot change without a forget(), which calls reset()). */
38
28
  identity(): Promise<AgentIdentity>;
39
29
  pairingCode(): Promise<PairingCode>;
40
30
  regeneratePairingCode(): Promise<PairingCode>;
41
31
  pair(): Promise<PairingCode>;
42
- /** Mint a FRESH session unconditionally (bypasses the cache - this is what re-minting after expiry or
43
- * a 401 calls). Also (re)populates the cache, so an ordinary tool call right after this one reuses it
44
- * via `ensureSession()` instead of minting again. */
45
32
  mintSession(): Promise<string>;
46
- /** The token every read/build/spend call below actually uses: the cached session if it is still safe
47
- * to reuse, otherwise a freshly minted one. This is what makes "every tool call does not re-mint" true
48
- * without any caller having to think about session lifetime. */
49
33
  ensureSession(): Promise<string>;
50
- /** `GET /agents/me` - this agent's own identity, its bound account, and its live grant if any (or an
51
- * honest `grant: null`). Re-mints the session once and retries on a 401 (a token that expired between
52
- * `ensureSession()`'s check and the request landing, or one the backend otherwise no longer honors). */
53
34
  me(): Promise<AgentSelf>;
54
- /** `GET /balance` - the account address and its token/holdings breakdown, exactly as the backend
55
- * reports it. */
56
35
  getBalance(): Promise<Record<string, unknown>>;
57
- /** `GET /agents/requests` - every approval request this agent has filed, with the owner's verdict
58
- * (`status`, `declineReason`) and deadline. The read half of `request_approval`: without it an agent
59
- * files a request it can never learn the outcome of. */
60
36
  listRequests(): Promise<unknown>;
61
- /** `GET /chains` - every chain this build serves, each with the `chainTier` that decides what it can
62
- * do (`watch` read-only, `basic` no AgentGuard, `botanary` full authority). */
63
37
  listChains(): Promise<unknown>;
64
- /** `GET /gas/methods` - only the gas methods actually AVAILABLE on the given chain (the backend
65
- * filters unavailable/sponsored ones out itself). Every parameter is optional and passed through
66
- * untouched; `key` is the Stellar/Solana key-keyed lane, `chainId` the EVM numeric one. */
67
38
  getGasMethods(params: {
68
39
  chainId?: number;
69
40
  accountId?: string;
70
41
  key?: string;
71
42
  }): Promise<unknown>;
72
- /** `GET /account` - SINGULAR: the one account this agent's session resolves to, optionally for a
73
- * specific chain. Carries `address` and `deploymentStatus`. */
74
43
  getAccount(params: {
75
44
  chainId?: number;
76
45
  }): Promise<unknown>;
77
- /** Build and verify a bound action. Submission is explicit and attempted at most once per handle. */
78
46
  prepareExactAction(input: ExactAgentAction, binding: AgentActionBinding | AgentSwapBinding): Promise<PreparedAgentAction>;
79
- /**
80
- * `POST /delegations/{delegationId}/actions` - an UNSIGNED delegated-action op, built in the
81
- * delegation's OWN Smart Sessions nonce lane with fixed native gas (the session validator cannot
82
- * gas-estimate a stub signature). This is the ONLY build lane a session-key signature can validate:
83
- * `POST /money/send/build` (this package's earlier, WRONG choice) builds in the Kernel account's ROOT
84
- * nonce lane for the OWNER's key - an op built there, then signed with this agent's session key and
85
- * wrapped in `spendUnderGrant`'s USE envelope, would route validation to the root ECDSA validator,
86
- * which cannot parse a `(bytes1, bytes32, bytes)` envelope as a 65-byte ECDSA signature. `delegationId`
87
- * is this agent's OWN grant id (`GET /agents/me`'s `grant.id` - never invented, never another agent's:
88
- * the backend refuses that with a named 403, see the workspace's delegation.controller.ts). `amount` is
89
- * DISPLAY units (e.g. `12.5` for 12.5 USDC), matching `DelegatedActionInput.amount` - never wei.
90
- * `token`, when given, disambiguates which of the delegation's budgeted tokens to move - a contract
91
- * address or CAIP-19 asset ref (Task A11), required whenever the grant budgets more than one
92
- * stablecoin. Omitted, the backend defaults to it ONLY when exactly one token is budgeted; a
93
- * multi-budget grant given neither refuses rather than silently picking the first (`propose_payment`
94
- * always resolves and passes one - see `agent-actions.ts`).
95
- */
47
+ prepareAction(input: MandateActionInput): Promise<PreparedAgentAction | AgentPolicyRefusal>;
96
48
  buildDelegatedAction(params: {
97
49
  delegationId: string;
98
50
  recipient: string;
99
51
  amount: number;
100
52
  token?: string;
101
53
  }): Promise<UserOpBuildResult>;
102
- /**
103
- * `POST /delegations/{delegationId}/actions` with `action: 'swap'` - the SAME endpoint, the same session
104
- * nonce lane and the same fixed gas as `buildDelegatedAction` above; only the body's shape differs. The
105
- * backend discriminates on `action` explicitly and NEVER infers it from the presence of `tokenIn`, so a
106
- * transfer body with a typo stays a 422 on the wrong field rather than silently becoming a swap.
107
- *
108
- * Symbols, not addresses: unlike `buildDelegatedAction`'s `token` (Task A11), this shape was NOT widened
109
- * to accept an address or asset ref - the backend re-resolves both legs from ITS OWN per-chain manifest,
110
- * which is the same resolution `buildGrant` used when it compiled the venue's `EQ` rule, so the two
111
- * cannot disagree. `propose_swap` (`agent-actions.ts`) still takes an address/asset ref from ITS OWN
112
- * caller (`assetIn`/`assetOut`) and resolves each to a verified symbol before calling this - so the
113
- * package-external argument names moved even though this wire shape did not. `amountIn` is DISPLAY
114
- * units (e.g. `25` for 25 USDC), matching `amount` on the transfer shape.
115
- *
116
- * The op this produces is ONE call into `GrantExecutor.executeUnderGrantWithAllowance` - approve, route,
117
- * zero, assert no residual - which the grant's session authorises only when the grant named a
118
- * `swapVenue`. A grant without one has no such action at all and the backend declines
119
- * `grant_swap_not_authorised` rather than handing back an op Smart Sessions would refuse in validation.
120
- */
121
54
  buildDelegatedSwap(params: {
122
55
  delegationId: string;
123
56
  tokenIn: string;
@@ -125,79 +58,21 @@ export declare class AgentRuntime {
125
58
  amountIn: number;
126
59
  maxSlippageBps?: number;
127
60
  }): Promise<UserOpBuildResult>;
128
- /** `POST /agents/requests` - the "ask" half of the design (Flow 7d step 5): raise a request naming the
129
- * calls that were declined and why, returning which bound was crossed and the deadline exactly as the
130
- * backend computed them. */
131
61
  raiseRequest(calls: UnsignedCallView[], reason: string): Promise<AgentRequestView>;
132
- /**
133
- * Sign a built op as the grant's session key and relay it. The USE envelope is EXACTLY the three lines
134
- * `botanary-fe/src/lib/wallet/signing.ts:49` already uses - `SmartSessionMode.USE` is `0x00`, and the
135
- * packing is `(bytes1, bytes32, bytes)`. Reproduced rather than re-derived: a second encoding of the same
136
- * envelope is a second thing to get byte-exact, and only one of them would be tested.
137
- *
138
- * `intentType` is the SAME value the build response itself reported (`UserOpBuildResult.intentType`,
139
- * `'delegated_action'` for `buildDelegatedAction`'s builds) - never assumed or hardcoded here, so the
140
- * relay always describes what was actually built, which is what `OrchestratorService.submit` uses to
141
- * label the audit trail entry.
142
- *
143
- * Returns the backend's OWN relay response (status/txHash/error, whatever `POST /userops` actually
144
- * said) rather than nothing - a caller (`propose_payment`) reporting success has to report what the
145
- * backend reported, never a string this package made up.
146
- */
147
62
  spendUnderGrant(userOp: UnsignedUserOp, userOpHash: Hex, permissionId: Hex, token: string, intentType: string): Promise<Record<string, unknown>>;
148
- /** `GET /userops/{opId}` - the receipt for an op THIS agent relayed. Reachable only because that route
149
- * now carries `@AgentAllowed()`; before that it 403'd an `ags_` token before the handler ran. */
150
63
  getUserOp(opId: string): Promise<Record<string, unknown>>;
151
- /** Read the original agent submission without building or signing another operation. */
152
64
  getUserOpByHash(chainId: number, hash: Hex): Promise<Record<string, unknown>>;
153
- /** Follow an op to a terminal status, bounded. See `awaitTerminalWith`. */
154
65
  awaitTerminal(opId: string, budgetMs: number): Promise<Record<string, unknown>>;
155
- /** `GET /apis/providers` - the ranked, searchable catalog of API providers on a chain, with each
156
- * provider's endpoint count, verified count, minimum and maximum price in atomic units (nullable).
157
- * Does NOT call an endpoint or spend anything. Returns
158
- * `{ providers, total, hidden, reasons, reasonsTruncated }`.
159
- *
160
- * The route is `/apis/providers`, not the `/apis/list` this file used to call: the backend's
161
- * api-catalog controller serves `providers` and `providers/:providerId` and has no `list` route at
162
- * all, so the old path 404s. botanary-mcp@0.6.1 was published with this corrected route but the
163
- * change was never committed, which is how the repository came to describe a dead endpoint while
164
- * the published package worked. */
165
66
  listApis(chainId: number): Promise<unknown>;
166
- /** `GET /apis/providers/{providerId}` - one provider with a page of its endpoints, each with its
167
- * live price as measured or declared (nullable), and whether it is payable on this chain. Returns
168
- * the `ApiProviderDetail` shape, with an `endpoints` array. */
169
67
  getApiProvider(providerId: string, chainId?: number): Promise<unknown>;
170
- /**
171
- * Call a paid third-party API endpoint and pay for it from the owner's wallet, within the budget the
172
- * owner committed on chain. Build -> sign -> relay, exactly like every other spending lane here.
173
- *
174
- * This used to `POST /v1/apis/calls`, a route that has never existed - the contract has
175
- * `/apis/calls/requirements` and `/apis/calls/relay`, and there is no composite. There could not be
176
- * one: a single endpoint would have to sign, and the backend holds no key. So the two halves are both
177
- * real calls, with the signature produced HERE, from this agent's own key.
178
- *
179
- * `payload` is a 32-byte EIP-712 digest over an EIP-3009 `TransferWithAuthorization`. Signing it
180
- * authorises exactly one transfer, of a value and to a recipient the digest already fixes; USDC
181
- * verifies the result via ERC-1271 against the owner's account, which routes to
182
- * `X402PaymentValidator` and is refused unless this agent key is inside a live budget.
183
- */
184
68
  callApi(params: {
185
69
  chainId: number;
186
70
  providerId: string;
187
71
  endpointId: string;
188
72
  url: string;
189
- /** Raw POST body, forwarded byte for byte. The price is quoted for THIS body and the paid request
190
- * replays exactly it, so a caller cannot get a cheap quote and then send something expensive. */
191
73
  body?: string;
192
74
  }): Promise<Record<string, unknown>>;
193
- /** Read durable submission status first. A submitted payment is never signed or forwarded again. */
194
75
  resumeApi(requirementId: string, budgetMs?: number): Promise<Record<string, unknown>>;
195
- /** `GET /apis/budget` - remaining authorizations, per-call maximum, expiry and epoch for this agent
196
- * on a chain. Does NOT tell you which endpoints this agent may reach. */
197
76
  getApiBudget(chainId: number): Promise<unknown>;
198
- /** Drops cached identity/pairing/session state. Call after `store.forget()` so the next call re-derives
199
- * from storage (and, in the ordinary case, creates a brand new identity) instead of continuing to serve
200
- * an in-memory identity - or a session minted for it - whose key is now gone. */
201
77
  reset(): void;
202
78
  }
203
- //# sourceMappingURL=runtime.d.ts.map
package/dist/signer.d.ts CHANGED
@@ -1,22 +1,16 @@
1
1
  import type { Hex } from 'viem';
2
- /** Public metadata only. The runtime never accepts or exports a private key. */
3
2
  export interface AgentIdentity {
4
3
  address: Hex;
5
4
  publicKey: Hex;
6
5
  fingerprint: string;
7
6
  createdAt: string;
8
- /** A non-secret description of the injected signer, such as an HSM or desktop keychain. */
9
7
  backend: string;
10
8
  }
11
- /** A server, HSM or desktop adapter supplies signing without exposing key material. */
12
9
  export interface AgentSigner {
13
10
  ensure(): Promise<AgentIdentity>;
14
- /** EIP-191 personal message signing, used for app registration proofs. */
15
11
  sign(message: string): Promise<Hex>;
16
- /** Raw secp256k1 digest signing, used by existing agent sessions and grant execution. */
17
12
  signHash(hash: Hex): Promise<Hex>;
18
13
  }
19
- /** Adapts an already-owned viem-compatible account. Key loading remains the caller's responsibility. */
20
14
  export declare function createViemAgentSigner(account: {
21
15
  address: Hex;
22
16
  publicKey: Hex;
@@ -30,4 +24,3 @@ export declare function createViemAgentSigner(account: {
30
24
  backend?: string;
31
25
  createdAt?: string;
32
26
  }): AgentSigner;
33
- //# sourceMappingURL=signer.d.ts.map
@@ -1,13 +1,10 @@
1
- /** Injection seam for an existing host transport. It receives only public payloads and agent tokens. */
2
1
  export interface AgentTransport {
3
2
  get<T>(path: string, token?: string): Promise<T>;
4
3
  post<T>(path: string, body: unknown, token?: string): Promise<T>;
5
4
  }
6
- /** No mutation retries. A failed relay must be reconciled using its existing operation/hash. */
7
5
  export declare class AgentApiClient implements AgentTransport {
8
6
  #private;
9
7
  constructor(baseUrl: string, fetchImpl?: typeof fetch);
10
8
  get<T>(path: string, token?: string): Promise<T>;
11
9
  post<T>(path: string, body: unknown, token?: string): Promise<T>;
12
10
  }
13
- //# sourceMappingURL=transport.d.ts.map
@@ -5,11 +5,12 @@ export declare function invalidResponse(): never;
5
5
  export declare function record(value: unknown): Record<string, unknown>;
6
6
  export declare function hex(value: unknown, bytes?: number): asserts value is Hex;
7
7
  export declare function resourceId(value: unknown): asserts value is string;
8
+ export declare function rfc3339(value: unknown): value is string;
8
9
  export declare function publicIdentity(value: unknown): AgentIdentity;
9
- /** The public v0.7 contract permits decimal or hex integer quantities. Bytes remain hex-only. */
10
+ export declare function userOpBuild(value: unknown): import('./views.js').UserOpBuildResult;
11
+ export declare function agentRequest(value: unknown): import('./views.js').AgentRequestView;
12
+ export declare function agentSelf(value: unknown): import('./views.js').AgentSelf;
10
13
  export declare function userOpQuantity(value: unknown, bits?: number): bigint;
11
14
  export declare function unsignedUserOp(value: unknown): UnsignedUserOp;
12
15
  export declare function readyRequirement(value: unknown): Pick<ApiCallRequirementsReady, 'payload' | 'authorization' | 'index' | 'validator'>;
13
- /** Bind a recovery response to the requested handle; never treat provider success as payment proof. */
14
16
  export declare function apiCallStatus(value: unknown, requirementId: string): Record<string, unknown>;
15
- //# sourceMappingURL=validation.d.ts.map