@remitmd/sdk 0.1.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 +250 -0
- package/dist/a2a.d.ts +137 -0
- package/dist/a2a.d.ts.map +1 -0
- package/dist/a2a.js +121 -0
- package/dist/a2a.js.map +1 -0
- package/dist/client.d.ts +41 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +81 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +108 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +218 -0
- package/dist/errors.js.map +1 -0
- package/dist/http.d.ts +23 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +150 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/vercel-ai.d.ts +44 -0
- package/dist/integrations/vercel-ai.d.ts.map +1 -0
- package/dist/integrations/vercel-ai.js +175 -0
- package/dist/integrations/vercel-ai.js.map +1 -0
- package/dist/models/bounty.d.ts +22 -0
- package/dist/models/bounty.d.ts.map +1 -0
- package/dist/models/bounty.js +2 -0
- package/dist/models/bounty.js.map +1 -0
- package/dist/models/common.d.ts +78 -0
- package/dist/models/common.d.ts.map +1 -0
- package/dist/models/common.js +3 -0
- package/dist/models/common.js.map +1 -0
- package/dist/models/deposit.d.ts +13 -0
- package/dist/models/deposit.d.ts.map +1 -0
- package/dist/models/deposit.js +2 -0
- package/dist/models/deposit.js.map +1 -0
- package/dist/models/escrow.d.ts +16 -0
- package/dist/models/escrow.d.ts.map +1 -0
- package/dist/models/escrow.js +2 -0
- package/dist/models/escrow.js.map +1 -0
- package/dist/models/index.d.ts +9 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +9 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/invoice.d.ts +30 -0
- package/dist/models/invoice.d.ts.map +1 -0
- package/dist/models/invoice.js +2 -0
- package/dist/models/invoice.js.map +1 -0
- package/dist/models/reputation.d.ts +7 -0
- package/dist/models/reputation.d.ts.map +1 -0
- package/dist/models/reputation.js +2 -0
- package/dist/models/reputation.js.map +1 -0
- package/dist/models/stream.d.ts +15 -0
- package/dist/models/stream.d.ts.map +1 -0
- package/dist/models/stream.js +2 -0
- package/dist/models/stream.js.map +1 -0
- package/dist/models/tab.d.ts +21 -0
- package/dist/models/tab.d.ts.map +1 -0
- package/dist/models/tab.js +2 -0
- package/dist/models/tab.js.map +1 -0
- package/dist/provider.d.ts +135 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +218 -0
- package/dist/provider.js.map +1 -0
- package/dist/signer.d.ts +31 -0
- package/dist/signer.d.ts.map +1 -0
- package/dist/signer.js +35 -0
- package/dist/signer.js.map +1 -0
- package/dist/testing/local.d.ts +31 -0
- package/dist/testing/local.d.ts.map +1 -0
- package/dist/testing/local.js +100 -0
- package/dist/testing/local.js.map +1 -0
- package/dist/testing/mock.d.ts +95 -0
- package/dist/testing/mock.d.ts.map +1 -0
- package/dist/testing/mock.js +407 -0
- package/dist/testing/mock.js.map +1 -0
- package/dist/wallet.d.ts +162 -0
- package/dist/wallet.d.ts.map +1 -0
- package/dist/wallet.js +365 -0
- package/dist/wallet.js.map +1 -0
- package/dist/x402.d.ts +78 -0
- package/dist/x402.d.ts.map +1 -0
- package/dist/x402.js +151 -0
- package/dist/x402.js.map +1 -0
- package/eslint.config.js +27 -0
- package/package.json +39 -0
- package/src/a2a.ts +241 -0
- package/src/client.ts +104 -0
- package/src/errors.ts +261 -0
- package/src/http.ts +190 -0
- package/src/index.ts +94 -0
- package/src/integrations/vercel-ai.ts +213 -0
- package/src/models/bounty.ts +23 -0
- package/src/models/common.ts +106 -0
- package/src/models/deposit.ts +13 -0
- package/src/models/escrow.ts +16 -0
- package/src/models/index.ts +8 -0
- package/src/models/invoice.ts +32 -0
- package/src/models/reputation.ts +7 -0
- package/src/models/stream.ts +15 -0
- package/src/models/tab.ts +22 -0
- package/src/provider.ts +281 -0
- package/src/signer.ts +70 -0
- package/src/testing/local.ts +118 -0
- package/src/testing/mock.ts +507 -0
- package/src/wallet.ts +546 -0
- package/src/x402.ts +202 -0
- package/tests/acceptance/bounty.test.ts +82 -0
- package/tests/acceptance/deposit.test.ts +70 -0
- package/tests/acceptance/direct.test.ts +53 -0
- package/tests/acceptance/escrow.test.ts +67 -0
- package/tests/acceptance/setup.ts +113 -0
- package/tests/acceptance/stream.test.ts +98 -0
- package/tests/acceptance/tab.test.ts +108 -0
- package/tests/acceptance/x402.test.ts +140 -0
- package/tests/compliance/auth.ts +69 -0
- package/tests/compliance/escrows.ts +96 -0
- package/tests/compliance/helpers.ts +90 -0
- package/tests/compliance/payments.ts +69 -0
- package/tests/compliance/tabs.ts +52 -0
- package/tests/test_a2a.ts +151 -0
- package/tests/test_errors.ts +80 -0
- package/tests/test_golden_vectors.ts +162 -0
- package/tests/test_integrations.ts +115 -0
- package/tests/test_mock.ts +217 -0
- package/tests/test_permit.ts +216 -0
- package/tests/test_provider.ts +304 -0
- package/tests/test_wallet.ts +108 -0
- package/tests/test_x402.ts +302 -0
- package/tsconfig.json +19 -0
package/src/wallet.ts
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet — read + write operations, requires a private key (or custom Signer).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { generatePrivateKey } from "viem/accounts";
|
|
7
|
+
import { RemitClient, type RemitClientOptions } from "./client.js";
|
|
8
|
+
import { AuthenticatedClient } from "./http.js";
|
|
9
|
+
import { PrivateKeySigner, type Signer } from "./signer.js";
|
|
10
|
+
import { X402Client, type PaymentRequired } from "./x402.js";
|
|
11
|
+
import type {
|
|
12
|
+
Transaction,
|
|
13
|
+
WalletStatus,
|
|
14
|
+
Webhook,
|
|
15
|
+
LinkResponse,
|
|
16
|
+
} from "./models/index.js";
|
|
17
|
+
import type { Invoice } from "./models/invoice.js";
|
|
18
|
+
import type { Escrow } from "./models/escrow.js";
|
|
19
|
+
import type { Tab, TabCharge } from "./models/tab.js";
|
|
20
|
+
import type { Stream } from "./models/stream.js";
|
|
21
|
+
import type { Bounty } from "./models/bounty.js";
|
|
22
|
+
import type { Deposit } from "./models/deposit.js";
|
|
23
|
+
/** Default public RPC URLs per chain. */
|
|
24
|
+
const DEFAULT_RPC_URLS: Record<string, string> = {
|
|
25
|
+
"base-sepolia": "https://sepolia.base.org",
|
|
26
|
+
base: "https://mainnet.base.org",
|
|
27
|
+
localhost: "http://127.0.0.1:8545",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export interface WalletOptions extends RemitClientOptions {
|
|
31
|
+
privateKey?: string;
|
|
32
|
+
signer?: Signer;
|
|
33
|
+
/** Router contract address for EIP-712 domain — must match server's ROUTER_ADDRESS. */
|
|
34
|
+
routerAddress?: string;
|
|
35
|
+
/** JSON-RPC URL for on-chain reads (nonce fetching). Falls back to REMITMD_RPC_URL env var, then public defaults. */
|
|
36
|
+
rpcUrl?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface OpenTabOptions {
|
|
40
|
+
to: string;
|
|
41
|
+
limit: number;
|
|
42
|
+
perUnit: number;
|
|
43
|
+
expires?: number; // seconds
|
|
44
|
+
/** Optional EIP-2612 permit for gasless USDC approval. */
|
|
45
|
+
permit?: PermitSignature;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface CloseTabOptions {
|
|
49
|
+
/** Final charged amount in USDC. Defaults to 0 (full refund). */
|
|
50
|
+
finalAmount?: number;
|
|
51
|
+
/** Provider's EIP-712 TabCharge signature covering the final state. */
|
|
52
|
+
providerSig?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ChargeTabOptions {
|
|
56
|
+
amount: number;
|
|
57
|
+
cumulative: number;
|
|
58
|
+
callCount: number;
|
|
59
|
+
/** Provider's EIP-712 TabCharge signature. */
|
|
60
|
+
providerSig: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface OpenStreamOptions {
|
|
64
|
+
to: string;
|
|
65
|
+
rate: number; // per second
|
|
66
|
+
maxDuration?: number; // seconds
|
|
67
|
+
maxTotal?: number;
|
|
68
|
+
/** Optional EIP-2612 permit for gasless USDC approval. */
|
|
69
|
+
permit?: PermitSignature;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** EIP-2612 permit signature for gasless USDC approval. */
|
|
73
|
+
export interface PermitSignature {
|
|
74
|
+
value: number;
|
|
75
|
+
deadline: number;
|
|
76
|
+
v: number;
|
|
77
|
+
r: string;
|
|
78
|
+
s: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Options for signing an EIP-2612 USDC permit. */
|
|
82
|
+
export interface SignPermitOptions {
|
|
83
|
+
/** Contract address that will be approved as spender. */
|
|
84
|
+
spender: string;
|
|
85
|
+
/** Amount in USDC base units (6 decimals). */
|
|
86
|
+
value: bigint;
|
|
87
|
+
/** Permit deadline (Unix timestamp). */
|
|
88
|
+
deadline: number;
|
|
89
|
+
/** Current permit nonce for this wallet (default: 0). */
|
|
90
|
+
nonce?: number;
|
|
91
|
+
/** USDC contract address (defaults to Base Sepolia MockUSDC). */
|
|
92
|
+
usdcAddress?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface PostBountyOptions {
|
|
96
|
+
amount: number;
|
|
97
|
+
task: string;
|
|
98
|
+
deadline: number; // unix timestamp
|
|
99
|
+
validation?: "poster" | "oracle" | "multisig";
|
|
100
|
+
maxAttempts?: number;
|
|
101
|
+
/** Optional EIP-2612 permit for gasless USDC approval. */
|
|
102
|
+
permit?: PermitSignature;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface PlaceDepositOptions {
|
|
106
|
+
to: string;
|
|
107
|
+
amount: number;
|
|
108
|
+
expires: number; // seconds
|
|
109
|
+
/** Optional EIP-2612 permit for gasless USDC approval. */
|
|
110
|
+
permit?: PermitSignature;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Convert a UUID string to bytes32 matching the server's id_to_bytes32().
|
|
115
|
+
* Encodes the UUID's UTF-8 bytes, left-aligned, zero-padded to 32.
|
|
116
|
+
*/
|
|
117
|
+
function uuidToBytes32(uuid: string): `0x${string}` {
|
|
118
|
+
const padded = new Uint8Array(32);
|
|
119
|
+
for (let i = 0; i < Math.min(uuid.length, 32); i++) {
|
|
120
|
+
padded[i] = uuid.charCodeAt(i);
|
|
121
|
+
}
|
|
122
|
+
return ("0x" + Array.from(padded, (b) => b.toString(16).padStart(2, "0")).join("")) as `0x${string}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export class Wallet extends RemitClient {
|
|
126
|
+
readonly #signer: Signer;
|
|
127
|
+
readonly #auth: AuthenticatedClient;
|
|
128
|
+
readonly #rpcUrl: string;
|
|
129
|
+
|
|
130
|
+
constructor(options: WalletOptions = {}) {
|
|
131
|
+
const { privateKey: explicitKey, signer, routerAddress, rpcUrl, ...clientOptions } = options;
|
|
132
|
+
|
|
133
|
+
const privateKey =
|
|
134
|
+
explicitKey ?? (!signer ? process.env["REMITMD_KEY"] : undefined);
|
|
135
|
+
|
|
136
|
+
if (!privateKey && !signer) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
"Wallet requires privateKey, signer, or the REMITMD_KEY environment variable."
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
super(clientOptions);
|
|
143
|
+
|
|
144
|
+
this.#rpcUrl = rpcUrl ?? process.env["REMITMD_RPC_URL"] ?? DEFAULT_RPC_URLS[this._chain] ?? DEFAULT_RPC_URLS["base-sepolia"]!;
|
|
145
|
+
|
|
146
|
+
// Router contract address for EIP-712 domain — falls back to env var.
|
|
147
|
+
const verifyingContract =
|
|
148
|
+
routerAddress ?? process.env["REMITMD_ROUTER_ADDRESS"] ?? "";
|
|
149
|
+
|
|
150
|
+
this.#signer = signer ?? new PrivateKeySigner(privateKey!);
|
|
151
|
+
this.#auth = new AuthenticatedClient({
|
|
152
|
+
signer: this.#signer,
|
|
153
|
+
baseUrl: this._apiUrl,
|
|
154
|
+
chainId: this._chainId,
|
|
155
|
+
verifyingContract,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Generate a new random wallet (for testing / onboarding). */
|
|
160
|
+
static create(options?: RemitClientOptions): Wallet {
|
|
161
|
+
const key = generatePrivateKey();
|
|
162
|
+
return new Wallet({ ...options, privateKey: key });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Load from REMITMD_KEY and REMITMD_CHAIN environment variables. */
|
|
166
|
+
static fromEnv(overrides?: RemitClientOptions): Wallet {
|
|
167
|
+
const key = process.env["REMITMD_KEY"];
|
|
168
|
+
const chain = process.env["REMITMD_CHAIN"] ?? "base";
|
|
169
|
+
if (!key) throw new Error("REMITMD_KEY environment variable is not set.");
|
|
170
|
+
return new Wallet({ chain, ...overrides, privateKey: key });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Checksummed public address. */
|
|
174
|
+
get address(): string {
|
|
175
|
+
return this.#signer.getAddress();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Prevent private key leakage. */
|
|
179
|
+
toJSON(): Record<string, string> {
|
|
180
|
+
return { address: this.address, chain: this._chain };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
[Symbol.for("nodejs.util.inspect.custom")](): string {
|
|
184
|
+
return `Wallet { address: '${this.address}', chain: '${this._chain}' }`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ─── EIP-2612 Permit ─────────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
/** Default USDC addresses per chain. */
|
|
190
|
+
static readonly USDC_ADDRESSES: Record<string, string> = {
|
|
191
|
+
"base-sepolia": "0x142aD61B8d2edD6b3807D9266866D97C35Ee0317",
|
|
192
|
+
base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
193
|
+
localhost: "0x5FbDB2315678afecb367f032d93F642f64180aa3",
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Sign an EIP-2612 permit for USDC approval.
|
|
198
|
+
* Returns a PermitSignature object that can be passed to postBounty() or placeDeposit().
|
|
199
|
+
*/
|
|
200
|
+
async signUsdcPermit(options: SignPermitOptions): Promise<PermitSignature> {
|
|
201
|
+
const usdcAddress = options.usdcAddress ?? Wallet.USDC_ADDRESSES[this._chain] ?? "";
|
|
202
|
+
|
|
203
|
+
const domain = {
|
|
204
|
+
name: "USD Coin",
|
|
205
|
+
version: "2",
|
|
206
|
+
chainId: this._chainId,
|
|
207
|
+
verifyingContract: usdcAddress,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const types = {
|
|
211
|
+
Permit: [
|
|
212
|
+
{ name: "owner", type: "address" },
|
|
213
|
+
{ name: "spender", type: "address" },
|
|
214
|
+
{ name: "value", type: "uint256" },
|
|
215
|
+
{ name: "nonce", type: "uint256" },
|
|
216
|
+
{ name: "deadline", type: "uint256" },
|
|
217
|
+
],
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const value = {
|
|
221
|
+
owner: this.address,
|
|
222
|
+
spender: options.spender,
|
|
223
|
+
value: options.value.toString(),
|
|
224
|
+
nonce: options.nonce ?? 0,
|
|
225
|
+
deadline: options.deadline,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const sig = await this.#signer.signTypedData(domain, types, value);
|
|
229
|
+
|
|
230
|
+
// Split signature into v, r, s
|
|
231
|
+
const sigBytes = sig.startsWith("0x") ? sig.slice(2) : sig;
|
|
232
|
+
const r = `0x${sigBytes.slice(0, 64)}`;
|
|
233
|
+
const s = `0x${sigBytes.slice(64, 128)}`;
|
|
234
|
+
const v = parseInt(sigBytes.slice(128, 130), 16);
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
value: Number(options.value),
|
|
238
|
+
deadline: options.deadline,
|
|
239
|
+
v,
|
|
240
|
+
r,
|
|
241
|
+
s,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Convenience: sign an EIP-2612 permit for USDC approval.
|
|
247
|
+
* Auto-fetches the on-chain nonce and sets a default deadline (1 hour).
|
|
248
|
+
* @param spender Contract address that will call transferFrom (e.g. Router, Escrow).
|
|
249
|
+
* @param amount Amount in USDC (e.g. 1.50 for $1.50).
|
|
250
|
+
* @param deadline Optional Unix timestamp. Defaults to 1 hour from now.
|
|
251
|
+
*/
|
|
252
|
+
async signPermit(spender: string, amount: number, deadline?: number): Promise<PermitSignature> {
|
|
253
|
+
const usdcAddress = Wallet.USDC_ADDRESSES[this._chain] ?? "";
|
|
254
|
+
const nonce = await this.#fetchUsdcNonce(usdcAddress);
|
|
255
|
+
const dl = deadline ?? Math.floor(Date.now() / 1000) + 3600;
|
|
256
|
+
const rawAmount = BigInt(Math.round(amount * 1e6));
|
|
257
|
+
return this.signUsdcPermit({
|
|
258
|
+
spender,
|
|
259
|
+
value: rawAmount,
|
|
260
|
+
deadline: dl,
|
|
261
|
+
nonce,
|
|
262
|
+
usdcAddress,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Fetch the current EIP-2612 nonce for this wallet from the USDC contract via JSON-RPC. */
|
|
267
|
+
async #fetchUsdcNonce(usdcAddress: string): Promise<number> {
|
|
268
|
+
// nonces(address) selector = 0x7ecebe00 + address padded to 32 bytes
|
|
269
|
+
const paddedAddress = this.address.toLowerCase().replace("0x", "").padStart(64, "0");
|
|
270
|
+
const data = `0x7ecebe00${paddedAddress}`;
|
|
271
|
+
|
|
272
|
+
const response = await fetch(this.#rpcUrl, {
|
|
273
|
+
method: "POST",
|
|
274
|
+
headers: { "Content-Type": "application/json" },
|
|
275
|
+
body: JSON.stringify({
|
|
276
|
+
jsonrpc: "2.0",
|
|
277
|
+
id: 1,
|
|
278
|
+
method: "eth_call",
|
|
279
|
+
params: [{ to: usdcAddress, data }, "latest"],
|
|
280
|
+
}),
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const json = await response.json() as { result?: string; error?: { message: string } };
|
|
284
|
+
if (json.error) throw new Error(`RPC error fetching nonce: ${json.error.message}`);
|
|
285
|
+
return parseInt(json.result ?? "0x0", 16);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ─── Direct Payment ─────────────────────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
payDirect(to: string, amount: number, memo = "", options?: { permit?: PermitSignature }): Promise<Transaction> {
|
|
291
|
+
return this.#auth.post<Transaction>("/payments/direct", {
|
|
292
|
+
to,
|
|
293
|
+
amount,
|
|
294
|
+
task: memo,
|
|
295
|
+
chain: this._chain,
|
|
296
|
+
nonce: randomBytes(16).toString("hex"),
|
|
297
|
+
signature: "0x",
|
|
298
|
+
...(options?.permit ? { permit: options.permit } : {}),
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ─── Escrow ─────────────────────────────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
async pay(invoice: Invoice, options?: { permit?: PermitSignature }): Promise<Escrow> {
|
|
305
|
+
// Step 1: create invoice on server.
|
|
306
|
+
const invoiceId = invoice.id || randomBytes(16).toString("hex");
|
|
307
|
+
await this.#auth.post<unknown>("/invoices", {
|
|
308
|
+
id: invoiceId,
|
|
309
|
+
chain: invoice.chain || this._chain,
|
|
310
|
+
from_agent: this.address.toLowerCase(),
|
|
311
|
+
to_agent: invoice.to.toLowerCase(),
|
|
312
|
+
amount: invoice.amount,
|
|
313
|
+
type: invoice.paymentType ?? "escrow",
|
|
314
|
+
task: invoice.memo ?? "",
|
|
315
|
+
nonce: randomBytes(16).toString("hex"),
|
|
316
|
+
signature: "0x",
|
|
317
|
+
...(invoice.timeout ? { escrow_timeout: invoice.timeout } : {}),
|
|
318
|
+
});
|
|
319
|
+
// Step 2: fund the escrow and return it.
|
|
320
|
+
return this.#auth.post<Escrow>("/escrows", {
|
|
321
|
+
invoice_id: invoiceId,
|
|
322
|
+
...(options?.permit ? { permit: options.permit } : {}),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
claimStart(invoiceId: string): Promise<Transaction> {
|
|
327
|
+
return this.#auth.post<Transaction>(`/escrows/${invoiceId}/claim-start`, {});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
submitEvidence(invoiceId: string, evidenceUri: string, milestoneIndex = 0): Promise<Transaction> {
|
|
331
|
+
return this.#auth.post<Transaction>(`/escrows/${invoiceId}/evidence`, {
|
|
332
|
+
evidenceUri,
|
|
333
|
+
milestoneIndex,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
releaseEscrow(invoiceId: string): Promise<Transaction> {
|
|
338
|
+
return this.#auth.post<Transaction>(`/escrows/${invoiceId}/release`, {});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
releaseMilestone(invoiceId: string, milestoneIndex: number): Promise<Transaction> {
|
|
342
|
+
return this.#auth.post<Transaction>(`/escrows/${invoiceId}/milestones/${milestoneIndex}/release`, {});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
cancelEscrow(invoiceId: string): Promise<Transaction> {
|
|
346
|
+
return this.#auth.post<Transaction>(`/escrows/${invoiceId}/cancel`, {});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ─── Metered Tabs ───────────────────────────────────────────────────────────
|
|
350
|
+
|
|
351
|
+
openTab(options: OpenTabOptions): Promise<Tab> {
|
|
352
|
+
return this.#auth.post<Tab>("/tabs", {
|
|
353
|
+
chain: this._chain,
|
|
354
|
+
provider: options.to,
|
|
355
|
+
limit_amount: options.limit,
|
|
356
|
+
per_unit: options.perUnit,
|
|
357
|
+
expiry: Math.floor(Date.now() / 1000) + (options.expires ?? 86400),
|
|
358
|
+
...(options.permit ? { permit: options.permit } : {}),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Sign a TabCharge EIP-712 message (provider-side, for charging or closing a tab). */
|
|
363
|
+
async signTabCharge(
|
|
364
|
+
tabContract: string,
|
|
365
|
+
tabId: string,
|
|
366
|
+
totalCharged: bigint,
|
|
367
|
+
callCount: number,
|
|
368
|
+
): Promise<string> {
|
|
369
|
+
return this.#signer.signTypedData(
|
|
370
|
+
{
|
|
371
|
+
name: "RemitTab",
|
|
372
|
+
version: "1",
|
|
373
|
+
chainId: this._chainId,
|
|
374
|
+
verifyingContract: tabContract,
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
TabCharge: [
|
|
378
|
+
{ name: "tabId", type: "bytes32" },
|
|
379
|
+
{ name: "totalCharged", type: "uint96" },
|
|
380
|
+
{ name: "callCount", type: "uint32" },
|
|
381
|
+
],
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
tabId: uuidToBytes32(tabId),
|
|
385
|
+
totalCharged,
|
|
386
|
+
callCount,
|
|
387
|
+
},
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** Charge a tab (provider-side). Requires a TabCharge EIP-712 signature. */
|
|
392
|
+
chargeTab(tabId: string, options: ChargeTabOptions): Promise<TabCharge> {
|
|
393
|
+
return this.#auth.post<TabCharge>(`/tabs/${tabId}/charge`, {
|
|
394
|
+
amount: options.amount,
|
|
395
|
+
cumulative: options.cumulative,
|
|
396
|
+
call_count: options.callCount,
|
|
397
|
+
provider_sig: options.providerSig,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
closeTab(tabId: string, options?: CloseTabOptions): Promise<Transaction> {
|
|
402
|
+
return this.#auth.post<Transaction>(`/tabs/${tabId}/close`, {
|
|
403
|
+
final_amount: options?.finalAmount ?? 0,
|
|
404
|
+
provider_sig: options?.providerSig ?? "0x",
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// ─── Streaming ──────────────────────────────────────────────────────────────
|
|
409
|
+
|
|
410
|
+
openStream(options: OpenStreamOptions): Promise<Stream> {
|
|
411
|
+
return this.#auth.post<Stream>("/streams", {
|
|
412
|
+
chain: this._chain,
|
|
413
|
+
payee: options.to,
|
|
414
|
+
rate_per_second: options.rate,
|
|
415
|
+
max_total: options.maxTotal,
|
|
416
|
+
...(options.permit ? { permit: options.permit } : {}),
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
closeStream(streamId: string): Promise<Transaction> {
|
|
421
|
+
return this.#auth.post<Transaction>(`/streams/${streamId}/close`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ─── Bounties ───────────────────────────────────────────────────────────────
|
|
425
|
+
|
|
426
|
+
postBounty(options: PostBountyOptions): Promise<Bounty> {
|
|
427
|
+
return this.#auth.post<Bounty>("/bounties", {
|
|
428
|
+
chain: this._chain,
|
|
429
|
+
amount: options.amount,
|
|
430
|
+
task_description: options.task,
|
|
431
|
+
deadline: options.deadline,
|
|
432
|
+
max_attempts: options.maxAttempts ?? 10,
|
|
433
|
+
...(options.permit ? { permit: options.permit } : {}),
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
submitBounty(bountyId: string, evidenceHash: string, evidenceUri?: string): Promise<Transaction> {
|
|
438
|
+
return this.#auth.post<Transaction>(`/bounties/${bountyId}/submit`, {
|
|
439
|
+
evidence_hash: evidenceHash,
|
|
440
|
+
...(evidenceUri ? { evidence_uri: evidenceUri } : {}),
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
awardBounty(bountyId: string, submissionId: number): Promise<Transaction> {
|
|
445
|
+
return this.#auth.post<Transaction>(`/bounties/${bountyId}/award`, { submission_id: submissionId });
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ─── Deposits ───────────────────────────────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
placeDeposit(options: PlaceDepositOptions): Promise<Deposit> {
|
|
451
|
+
return this.#auth.post<Deposit>("/deposits", {
|
|
452
|
+
chain: this._chain,
|
|
453
|
+
provider: options.to,
|
|
454
|
+
amount: options.amount,
|
|
455
|
+
expiry: Math.floor(Date.now() / 1000) + options.expires,
|
|
456
|
+
...(options.permit ? { permit: options.permit } : {}),
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
returnDeposit(depositId: string): Promise<Transaction> {
|
|
461
|
+
return this.#auth.post<Transaction>(`/deposits/${depositId}/return`, {});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ─── Authenticated reads (override unauthenticated base class versions) ──────
|
|
465
|
+
|
|
466
|
+
/** GET /escrows/{id} — authenticated; server requires auth to access escrow details. */
|
|
467
|
+
override getEscrow(invoiceId: string): Promise<Escrow> {
|
|
468
|
+
return this.#auth.get<Escrow>(`/escrows/${invoiceId}`);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** GET /tabs/{id} — authenticated; server requires auth to access tab details. */
|
|
472
|
+
override getTab(tabId: string): Promise<Tab> {
|
|
473
|
+
return this.#auth.get<Tab>(`/tabs/${tabId}`);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ─── Status ─────────────────────────────────────────────────────────────────
|
|
477
|
+
|
|
478
|
+
status(): Promise<WalletStatus> {
|
|
479
|
+
return this.#auth.get<WalletStatus>(`/status/${this.address}`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async balance(): Promise<number> {
|
|
483
|
+
const s = await this.status();
|
|
484
|
+
return parseFloat(s.balance);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ─── Webhooks ───────────────────────────────────────────────────────────────
|
|
488
|
+
|
|
489
|
+
registerWebhook(
|
|
490
|
+
url: string,
|
|
491
|
+
events: string[],
|
|
492
|
+
chains?: string[],
|
|
493
|
+
): Promise<Webhook> {
|
|
494
|
+
return this.#auth.post<Webhook>("/webhooks", {
|
|
495
|
+
url,
|
|
496
|
+
events,
|
|
497
|
+
chains: chains ?? [this._chain],
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ─── One-time operator links ─────────────────────────────────────────────────
|
|
502
|
+
|
|
503
|
+
/** Generate a one-time URL for the operator to fund this wallet. */
|
|
504
|
+
createFundLink(): Promise<LinkResponse> {
|
|
505
|
+
return this.#auth.post<LinkResponse>("/links/fund", {});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** Generate a one-time URL for the operator to withdraw funds. */
|
|
509
|
+
createWithdrawLink(): Promise<LinkResponse> {
|
|
510
|
+
return this.#auth.post<LinkResponse>("/links/withdraw", {});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ─── Testnet ────────────────────────────────────────────────────────────────
|
|
514
|
+
|
|
515
|
+
/** @deprecated Use mint() instead. Faucet endpoint returns 410 Gone. */
|
|
516
|
+
requestTestnetFunds(): Promise<Transaction> {
|
|
517
|
+
return this.#auth.post<Transaction>("/faucet", { wallet: this.address });
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** Mint testnet USDC via POST /mint. Max $2,500 per call, once per hour per wallet. */
|
|
521
|
+
async mint(amount: number): Promise<{ tx_hash: string; balance: number }> {
|
|
522
|
+
const res = await fetch(`${this._apiUrl}/mint`, {
|
|
523
|
+
method: "POST",
|
|
524
|
+
headers: { "Content-Type": "application/json" },
|
|
525
|
+
body: JSON.stringify({ wallet: this.address, amount }),
|
|
526
|
+
});
|
|
527
|
+
if (!res.ok) {
|
|
528
|
+
const body = await res.json().catch(() => ({})) as Record<string, unknown>;
|
|
529
|
+
throw new Error(`mint failed (${res.status}): ${(body as Record<string, string>).message ?? res.statusText}`);
|
|
530
|
+
}
|
|
531
|
+
return res.json() as Promise<{ tx_hash: string; balance: number }>;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// ─── x402 ───────────────────────────────────────────────────────────────────
|
|
535
|
+
|
|
536
|
+
/** Make a fetch request, auto-paying any x402 402 responses within maxAutoPayUsdc. */
|
|
537
|
+
async x402Fetch(
|
|
538
|
+
url: string,
|
|
539
|
+
maxAutoPayUsdc = 0.1,
|
|
540
|
+
init?: RequestInit,
|
|
541
|
+
): Promise<{ response: Response; lastPayment: PaymentRequired | null }> {
|
|
542
|
+
const client = new X402Client({ signer: this.#signer, address: this.address, maxAutoPayUsdc });
|
|
543
|
+
const response = await client.fetch(url, init);
|
|
544
|
+
return { response, lastPayment: client.lastPayment };
|
|
545
|
+
}
|
|
546
|
+
}
|