@blindmarket/sdk 0.6.4 → 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
+ }
@@ -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>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blindmarket/sdk",
3
- "version": "0.6.4",
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",