@blindmarket/sdk 0.6.2 → 0.7.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.
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Sending the client's own transactions: the deploy fee, escrow funding and
3
+ * refunds. Everything here runs before, or right after, value moves, so each
4
+ * check fails before anything is sent, and a hash is handed back the moment it
5
+ * exists.
6
+ */
7
+ import { ethers } from 'ethers';
8
+ /**
9
+ * How long to wait for a receipt. Arc and Base confirm in seconds, so this
10
+ * bounds a stuck RPC, not a slow chain. The transaction may still confirm
11
+ * after it.
12
+ */
13
+ export declare const DEFAULT_CONFIRM_TIMEOUT_MS = 180000;
14
+ export interface SendOptions {
15
+ /**
16
+ * Called with the hash as soon as the transaction is broadcast, before any
17
+ * wait, and again with the replacement's hash if the wallet re-prices it.
18
+ * Persist it there: a crash from then on cannot lose it. A throwing callback
19
+ * does not stop the send, which has already happened.
20
+ */
21
+ onSent?: (hash: string) => void | Promise<void>;
22
+ timeoutMs?: number;
23
+ nonce?: number;
24
+ value?: bigint;
25
+ gasLimit?: bigint;
26
+ /** What the caller should do with the hash when the send could not be confirmed. */
27
+ unconfirmedHint?: (hash: string) => string;
28
+ }
29
+ /** A transaction that was broadcast but not seen to confirm. It may still land. */
30
+ export declare class UnconfirmedTransactionError extends Error {
31
+ hash: string;
32
+ constructor(hash: string, message: string);
33
+ }
34
+ /**
35
+ * Send a transaction and wait for it to confirm. Returns the hash that
36
+ * confirmed: a sped-up transaction's replacement when the wallet re-priced it.
37
+ * A revert or a cancel throws an Error saying nothing moved. A send that could
38
+ * not be confirmed throws UnconfirmedTransactionError naming the hash.
39
+ */
40
+ export declare function sendAndWait(signer: ethers.Signer, tx: {
41
+ to: string;
42
+ data: string;
43
+ }, opts?: SendOptions): Promise<{
44
+ hash: string;
45
+ nonce: number;
46
+ }>;
47
+ /**
48
+ * Throw, before anything is sent, unless `signer` is connected to chain
49
+ * `chainId`. A transaction signed on the wrong network can succeed there
50
+ * (a USDC address with no code accepts any call) and pay nobody.
51
+ */
52
+ export declare function assertSignerChain(signer: ethers.Signer, chainId: number | bigint, what: string): Promise<void>;
53
+ export declare function tokenBalance(signer: ethers.Signer, token: string, owner: string): Promise<bigint>;
54
+ /**
55
+ * Make sure `spender` may pull `amount` of `token` from the signer: approve
56
+ * exactly `amount` when the allowance is short. Returns the nonce the next
57
+ * transaction should use when an approve was sent (an RPC can answer the
58
+ * next nonce lookup from before the approve landed), else undefined.
59
+ */
60
+ export declare function ensureAllowance(signer: ethers.Signer, token: string, spender: string, amount: bigint, opts?: Pick<SendOptions, 'timeoutMs'>): Promise<number | undefined>;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Sending the client's own transactions: the deploy fee, escrow funding and
3
+ * refunds. Everything here runs before, or right after, value moves, so each
4
+ * check fails before anything is sent, and a hash is handed back the moment it
5
+ * exists.
6
+ */
7
+ import { ethers } from 'ethers';
8
+ import { ApiError } from './apiError.js';
9
+ /**
10
+ * How long to wait for a receipt. Arc and Base confirm in seconds, so this
11
+ * bounds a stuck RPC, not a slow chain. The transaction may still confirm
12
+ * after it.
13
+ */
14
+ export const DEFAULT_CONFIRM_TIMEOUT_MS = 180_000;
15
+ /** A transaction that was broadcast but not seen to confirm. It may still land. */
16
+ export class UnconfirmedTransactionError extends Error {
17
+ hash;
18
+ constructor(hash, message) {
19
+ super(message);
20
+ this.hash = hash;
21
+ this.name = 'UnconfirmedTransactionError';
22
+ }
23
+ }
24
+ async function notify(onSent, hash) {
25
+ if (!onSent)
26
+ return;
27
+ try {
28
+ await onSent(hash);
29
+ }
30
+ catch {
31
+ // The transaction is out whatever the callback did; the caller still gets the hash.
32
+ }
33
+ }
34
+ /**
35
+ * Send a transaction and wait for it to confirm. Returns the hash that
36
+ * confirmed: a sped-up transaction's replacement when the wallet re-priced it.
37
+ * A revert or a cancel throws an Error saying nothing moved. A send that could
38
+ * not be confirmed throws UnconfirmedTransactionError naming the hash.
39
+ */
40
+ export async function sendAndWait(signer, tx, opts = {}) {
41
+ const sent = await signer.sendTransaction({
42
+ to: tx.to,
43
+ data: tx.data,
44
+ ...(opts.value !== undefined ? { value: opts.value } : {}),
45
+ ...(opts.nonce !== undefined ? { nonce: opts.nonce } : {}),
46
+ ...(opts.gasLimit !== undefined ? { gasLimit: opts.gasLimit } : {}),
47
+ });
48
+ await notify(opts.onSent, sent.hash);
49
+ try {
50
+ const receipt = await sent.wait(1, opts.timeoutMs ?? DEFAULT_CONFIRM_TIMEOUT_MS);
51
+ if (receipt && receipt.status === 0)
52
+ throw new Error(`Transaction ${sent.hash} reverted, so nothing was paid.`);
53
+ return { hash: sent.hash, nonce: sent.nonce };
54
+ }
55
+ catch (err) {
56
+ if (ethers.isError(err, 'TRANSACTION_REPLACED')) {
57
+ if (err.cancelled)
58
+ throw new Error(`Transaction ${sent.hash} was cancelled or replaced in the wallet, so nothing was paid.`);
59
+ const replacement = err.replacement;
60
+ if (err.receipt && err.receipt.status === 0)
61
+ throw new Error(`Transaction ${replacement.hash} reverted, so nothing was paid.`);
62
+ await notify(opts.onSent, replacement.hash);
63
+ return { hash: replacement.hash, nonce: replacement.nonce };
64
+ }
65
+ if (ethers.isError(err, 'CALL_EXCEPTION'))
66
+ throw new Error(`Transaction ${sent.hash} reverted, so nothing was paid.`);
67
+ if (err instanceof Error && err.message.startsWith(`Transaction ${sent.hash}`))
68
+ throw err;
69
+ const hint = opts.unconfirmedHint ? ` ${opts.unconfirmedHint(sent.hash)}` : '';
70
+ throw new UnconfirmedTransactionError(sent.hash, `Transaction ${sent.hash} was sent but not confirmed (${err.message}).${hint}`);
71
+ }
72
+ }
73
+ /**
74
+ * Throw, before anything is sent, unless `signer` is connected to chain
75
+ * `chainId`. A transaction signed on the wrong network can succeed there
76
+ * (a USDC address with no code accepts any call) and pay nobody.
77
+ */
78
+ export async function assertSignerChain(signer, chainId, what) {
79
+ const provider = signer.provider;
80
+ if (!provider) {
81
+ throw new ApiError(400, `${what} happens on chain ${chainId}, but the signer has no provider to check its chain with. Nothing was sent.`, undefined, 'NO_RPC');
82
+ }
83
+ let actual;
84
+ try {
85
+ actual = (await provider.getNetwork()).chainId;
86
+ }
87
+ catch (err) {
88
+ throw new ApiError(503, `${what}: could not read the signer's chain (${err.message}). Nothing was sent.`, undefined, 'RPC_UNREACHABLE');
89
+ }
90
+ if (actual !== BigInt(chainId)) {
91
+ throw new ApiError(409, `${what} happens on chain ${chainId}, but the signer's RPC is on chain ${actual}. Nothing was sent. Point the signer at an RPC for chain ${chainId}.`, undefined, 'WRONG_CHAIN');
92
+ }
93
+ }
94
+ const ERC20 = new ethers.Interface([
95
+ 'function allowance(address owner, address spender) view returns (uint256)',
96
+ 'function approve(address spender, uint256 amount) returns (bool)',
97
+ 'function balanceOf(address owner) view returns (uint256)',
98
+ ]);
99
+ /** An ERC-20 read through the signer's provider. */
100
+ async function erc20Read(signer, token, fn, args) {
101
+ const provider = signer.provider;
102
+ if (!provider)
103
+ throw new ApiError(400, 'The signer has no provider to read the token with.', undefined, 'NO_RPC');
104
+ const raw = await provider.call({ to: token, data: ERC20.encodeFunctionData(fn, args) });
105
+ return ERC20.decodeFunctionResult(fn, raw)[0];
106
+ }
107
+ export function tokenBalance(signer, token, owner) {
108
+ return erc20Read(signer, token, 'balanceOf', [owner]);
109
+ }
110
+ /**
111
+ * Make sure `spender` may pull `amount` of `token` from the signer: approve
112
+ * exactly `amount` when the allowance is short. Returns the nonce the next
113
+ * transaction should use when an approve was sent (an RPC can answer the
114
+ * next nonce lookup from before the approve landed), else undefined.
115
+ */
116
+ export async function ensureAllowance(signer, token, spender, amount, opts = {}) {
117
+ const owner = await signer.getAddress();
118
+ if ((await erc20Read(signer, token, 'allowance', [owner, spender])) >= amount)
119
+ return undefined;
120
+ const { nonce } = await sendAndWait(signer, { to: token, data: ERC20.encodeFunctionData('approve', [spender, amount]) }, opts);
121
+ return nonce + 1;
122
+ }
@@ -61,7 +61,7 @@ export function createBlindMarketTools(bb) {
61
61
  preferredCapabilities: arr('Preferred subset of capabilities (optional)', str('Capability', CAP_ENUM)),
62
62
  // No enum: the backend validates the list, and a newer backend may accept
63
63
  // a chain this SDK version doesn't know.
64
- supportedChains: arr("Settlement chains you can sign submitEvidence on, e.g. ['0g', 'base']. Stored on your executor record as a declaration; the backend does not filter offers by it, so check a task's chain before accepting (optional)", str('Chain slug')),
64
+ supportedChains: arr("Settlement chains you can sign submitEvidence on, e.g. ['base', 'arc']. Stored on your executor record; newer backends also stop offering you, and refuse your accept on, tasks on other chains. Browse results are not filtered, so check a task's chain before accepting (optional)", str('Chain slug')),
65
65
  }, async (a) => {
66
66
  return bb.registerExecutor(a);
67
67
  }, ['displayName', 'capabilities', 'publicKey']),
@@ -91,17 +91,17 @@ export function createBlindMarketTools(bb) {
91
91
  }, async (a) => {
92
92
  return bb.deliverResult(a.taskId, { output: a.output });
93
93
  }, ['taskId', 'output']),
94
- tool(bb, 'deploy_agent', 'Deploy a new AI agent on BlindMarket', {
94
+ tool(bb, 'deploy_agent', "Deploy a new hosted AI agent on BlindMarket, owned by the API key's wallet. Deploying costs a fee that this tool never pays: pass feeTxHash, the transaction in which the owner paid it, or the call fails with DEPLOY_FEE_REQUIRED and says what to pay.", {
95
95
  name: str('Agent name'),
96
96
  instructions: str('System prompt / instructions'),
97
- provider: str('LLM provider', ['openai', 'anthropic', 'groq', 'gemini']),
98
- model: str('Model name (e.g. gpt-4, claude-sonnet-4-5)'),
99
- apiKey: str('Provider API key'),
100
- ownerAddress: str('Owner wallet address (0x...)'),
101
- ownerPublicKey: str('Owner public key'),
97
+ provider: str('LLM provider', ['openai', 'anthropic', 'groq', 'gemini', '0g-compute']),
98
+ model: str('Model name (e.g. gpt-4o-mini, claude-sonnet-4-5)'),
99
+ apiKey: str("Provider API key; not needed for 0g-compute, which bills the agent's own wallet"),
100
+ ownerPublicKey: str("Owner's uncompressed secp256k1 public key: 130 hex chars starting 04, no 0x. The agent's private key is encrypted to it."),
101
+ feeTxHash: str('The transaction that paid the deploy fee'),
102
102
  }, async (a) => {
103
103
  return bb.deployAgent(a);
104
- }, ['name', 'instructions', 'provider', 'model', 'apiKey', 'ownerAddress', 'ownerPublicKey']),
104
+ }, ['name', 'instructions', 'provider', 'model', 'ownerPublicKey']),
105
105
  tool(bb, 'list_agents', 'List deployed agents', {
106
106
  ownerAddress: str('Filter by owner address'),
107
107
  }, async (a) => {
package/dist/types.d.ts CHANGED
@@ -89,7 +89,10 @@ export interface OpenTask {
89
89
  export interface CreateTaskRequest {
90
90
  /** bytes32 commitment to the brief (0x + 64 hex) — sha256 of the ciphertext. */
91
91
  taskHash: Hex;
92
- /** Payment token address (USDC on Base; the zero address = native on 0G). */
92
+ /**
93
+ * The posting chain's settlement token: USDC on Arc and Base; the zero
94
+ * address = native 0G. GET /health/settlement names it.
95
+ */
93
96
  token: Address;
94
97
  /** Reward, as an integer string in the payment token's smallest unit. */
95
98
  amount: string;
@@ -123,7 +126,11 @@ export interface CreateTaskTx {
123
126
  to: Address;
124
127
  data: Hex;
125
128
  value?: string;
129
+ from?: Address;
126
130
  };
131
+ /** The chain the tx must be sent on (the backend's posting chain). Absent from older backends. */
132
+ chain?: string;
133
+ chainId?: number;
127
134
  }
128
135
  export interface TaskDetail extends OpenTask {
129
136
  metadata?: Record<string, unknown>;
@@ -168,7 +175,7 @@ export interface A2APublicTaskMeta {
168
175
  requiredCapabilities?: AgentCapability[];
169
176
  posterAddress?: string;
170
177
  /** Which escrow holds the task. Absent on rows indexed before the field existed. */
171
- chain?: 'base' | '0g';
178
+ chain?: 'base' | '0g' | 'arc';
172
179
  /** Unix seconds. */
173
180
  deadline?: number;
174
181
  privacy?: 'public';
@@ -195,9 +202,10 @@ export interface ExecutorProfile {
195
202
  minReward?: string;
196
203
  preferredCapabilities?: AgentCapability[];
197
204
  /** Settlement chains the executor declared at registration. `null` means it
198
- * never declared any. Informational: the backend stores it but does not
199
- * filter offers or /accept by it. Absent from backends that predate the
200
- * field. */
205
+ * never declared any. Older backends only store it; newer ones also filter
206
+ * offers, bids and /accept by it (see
207
+ * {@link RegisterExecutorInput.supportedChains}). Absent from backends that
208
+ * predate the field. */
201
209
  supportedChains?: string[] | null;
202
210
  registeredAt: string;
203
211
  decayedScore?: number;
@@ -219,10 +227,14 @@ export interface RegisterExecutorInput {
219
227
  minReward?: string;
220
228
  preferredCapabilities?: AgentCapability[];
221
229
  /** Settlement chains ('0g', 'base', …) this executor can sign
222
- * `submitEvidence` on. A DECLARATION ONLY: the backend stores it on the
223
- * executor record and does not filter offers or /accept by it, so the
224
- * caller must check a task's chain (`entry.meta.chain`) before accepting —
225
- * WorkerRuntime does. Backends that predate the field drop it. */
230
+ * `submitEvidence` on. Older backends store it on the executor record
231
+ * only; newer ones also leave the executor out of offers and refuse bids
232
+ * and /accept (409 CHAIN_UNSUPPORTED) for tasks on other chains — and for
233
+ * tasks indexed before chains were recorded unless it lists both '0g' and
234
+ * 'base'. No backend
235
+ * filters browse results by it, so the caller must check a task's chain
236
+ * (`entry.meta.chain`) before accepting — WorkerRuntime does. Backends
237
+ * that predate the field drop it. */
226
238
  supportedChains?: string[];
227
239
  }
228
240
  /** Params for BlindMarket.createAgent() — derives the pubkey from your key + registers the executor in one call. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blindmarket/sdk",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "description": "BlindMarket SDK — deploy agents, assign workers, verify evidence",
5
5
  "author": "BlindMarket Team",
6
6
  "license": "MIT",
@@ -53,6 +53,7 @@
53
53
  },
54
54
  "scripts": {
55
55
  "build": "tsc",
56
+ "prepublishOnly": "npm run build",
56
57
  "dev": "tsc --watch",
57
58
  "test": "vitest run"
58
59
  },