@subly_fi/pay 0.4.1 → 0.6.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,27 @@
1
+ // src/setup-status.ts
2
+ function fail(message) {
3
+ process.stderr.write(`${message}
4
+ `);
5
+ process.exit(1);
6
+ }
7
+ var arg = process.argv[2];
8
+ if (arg === void 0) {
9
+ fail("Usage: pay setup-status <st_sessionId | setupUrl>");
10
+ }
11
+ var match = arg.match(/st_[0-9a-f]+/i);
12
+ if (match === null) {
13
+ fail(`no session id (st_...) found in: ${arg}`);
14
+ }
15
+ var sessionId = match[0];
16
+ var relayerBaseUrl = process.env.SUBLY_RELAYER_URL ?? process.env.SUBLY_FACILITATOR_URL ?? "https://api.demo.sublyfi.com";
17
+ var response = await fetch(
18
+ `${relayerBaseUrl}/v1/setup-sessions/${encodeURIComponent(sessionId)}`
19
+ );
20
+ var text = await response.text();
21
+ if (response.status !== 200) {
22
+ process.stdout.write(`${text}
23
+ `);
24
+ process.exit(1);
25
+ }
26
+ process.stdout.write(`${text}
27
+ `);
package/dist/withdraw.js CHANGED
@@ -1021,14 +1021,18 @@ async function walletAuthHeaders(params) {
1021
1021
 
1022
1022
  // ../../src/client/vault-flows.ts
1023
1023
  var VaultFlowClientError = class extends Error {
1024
- constructor(step, message, detail = null) {
1024
+ constructor(step, message, detail = null, code = null, errorDetails = null) {
1025
1025
  super(message);
1026
1026
  this.step = step;
1027
1027
  this.detail = detail;
1028
+ this.code = code;
1029
+ this.errorDetails = errorDetails;
1028
1030
  this.name = "VaultFlowClientError";
1029
1031
  }
1030
1032
  step;
1031
1033
  detail;
1034
+ code;
1035
+ errorDetails;
1032
1036
  };
1033
1037
  var VaultFlowClient = class {
1034
1038
  baseUrl;
@@ -1045,12 +1049,37 @@ var VaultFlowClient = class {
1045
1049
  this.pollTimeoutMs = config.pollTimeoutMs ?? 9e4;
1046
1050
  this.pollIntervalMs = config.pollIntervalMs ?? 2500;
1047
1051
  }
1048
- /** Moves USDC from the agent wallet into the vault (fee sponsored). */
1052
+ /**
1053
+ * Moves USDC from the agent wallet into the vault (fee sponsored). Under
1054
+ * depositPolicy "owner_approval_required" the relayer refuses to prepare
1055
+ * without an owner approval; when the caller passes none, an already
1056
+ * APPROVED deposit approval for this exact amount (e.g. the mandate's
1057
+ * initialDeposit — "one Face ID covers mandate + first deposit") is looked
1058
+ * up and used automatically before surfacing deposit_approval_required.
1059
+ */
1049
1060
  async deposit(input) {
1050
- const prepared = await this.postJson("prepare", "/v1/deposits/prepare", {
1051
- wallet: this.signer.walletAddress,
1052
- amountRawUsdc: input.amountRawUsdc.toString()
1053
- });
1061
+ let approvalId2 = input.approvalId;
1062
+ let prepared;
1063
+ try {
1064
+ prepared = await this.postJson("prepare", "/v1/deposits/prepare", {
1065
+ wallet: this.signer.walletAddress,
1066
+ amountRawUsdc: input.amountRawUsdc.toString(),
1067
+ ...approvalId2 === void 0 ? {} : { approvalId: approvalId2 }
1068
+ });
1069
+ } catch (error) {
1070
+ if (!(error instanceof VaultFlowClientError) || error.code !== "deposit_approval_required" || approvalId2 !== void 0) {
1071
+ throw error;
1072
+ }
1073
+ approvalId2 = await this.findApprovedDepositApproval(input.amountRawUsdc);
1074
+ if (approvalId2 === void 0) {
1075
+ throw error;
1076
+ }
1077
+ prepared = await this.postJson("prepare", "/v1/deposits/prepare", {
1078
+ wallet: this.signer.walletAddress,
1079
+ amountRawUsdc: input.amountRawUsdc.toString(),
1080
+ approvalId: approvalId2
1081
+ });
1082
+ }
1054
1083
  const signed = await this.signer.signDeposit({
1055
1084
  intent: prepared.signingIntent,
1056
1085
  serializedTransaction: prepared.serializedTransaction,
@@ -1089,7 +1118,9 @@ var VaultFlowClient = class {
1089
1118
  {
1090
1119
  wallet: this.signer.walletAddress,
1091
1120
  amountRawUsdc: input.amountRawUsdc.toString(),
1092
- ...input.purpose === void 0 ? {} : { purpose: input.purpose }
1121
+ ...input.purpose === void 0 ? {} : { purpose: input.purpose },
1122
+ ...input.payment === void 0 ? {} : { payment: input.payment },
1123
+ ...input.approvalId === void 0 ? {} : { approvalId: input.approvalId }
1093
1124
  }
1094
1125
  );
1095
1126
  const signed = await this.signer.signWithdrawal({
@@ -1168,6 +1199,70 @@ var VaultFlowClient = class {
1168
1199
  spendableYieldRawUsdc: body.budget?.spendableYieldRawUsdc ?? "0"
1169
1200
  };
1170
1201
  }
1202
+ /** Best-effort audit link: reports the x402 payment tx a realize funded. */
1203
+ async reportPayment(input) {
1204
+ await this.postJson("submit", "/v1/payments/report", {
1205
+ wallet: this.signer.walletAddress,
1206
+ withdrawalId: input.withdrawalId,
1207
+ paymentTxSignature: input.paymentTxSignature
1208
+ });
1209
+ }
1210
+ /** Wallet's approvals as the relayer sees them (optionally by status). */
1211
+ async listApprovals(status) {
1212
+ const body = await this.getJson(
1213
+ `/v1/wallets/${this.signer.walletAddress}/approvals${status === void 0 ? "" : `?status=${encodeURIComponent(status)}`}`
1214
+ );
1215
+ return body.approvals ?? [];
1216
+ }
1217
+ /**
1218
+ * Creates the owner-onboarding setup link (wallet-auth pins the agreed
1219
+ * policy + initial deposit). Paste `setupUrl` into the chat verbatim.
1220
+ */
1221
+ async createSetupSession(input) {
1222
+ return await this.postJson(
1223
+ "prepare",
1224
+ `/v1/wallets/${this.signer.walletAddress}/setup-sessions`,
1225
+ {
1226
+ ...input.policy === void 0 ? {} : { policy: input.policy },
1227
+ ...input.enforcementMode === void 0 ? {} : { enforcementMode: input.enforcementMode },
1228
+ ...input.mandateTtlDays === void 0 ? {} : { mandateTtlDays: input.mandateTtlDays },
1229
+ ...input.initialDepositRawUsdc === void 0 ? {} : { initialDepositRawUsdc: input.initialDepositRawUsdc }
1230
+ }
1231
+ );
1232
+ }
1233
+ /** Polls a setup session (public capability URL — no auth needed). */
1234
+ async getSetupSession(sessionId) {
1235
+ const url = `${this.baseUrl}/v1/setup-sessions/${encodeURIComponent(sessionId)}`;
1236
+ const response = await this.fetchImpl(url);
1237
+ const text = await response.text();
1238
+ if (response.status !== 200) {
1239
+ const parsed = parseRelayerError(text);
1240
+ throw new VaultFlowClientError(
1241
+ "read",
1242
+ parsed.message ?? `setup session read failed with ${response.status}`,
1243
+ text,
1244
+ parsed.code,
1245
+ parsed.details
1246
+ );
1247
+ }
1248
+ return JSON.parse(text);
1249
+ }
1250
+ /**
1251
+ * Finds an APPROVED, unconsumed deposit approval bound to exactly this
1252
+ * amount — the shape the mandate's initialDeposit approval has.
1253
+ */
1254
+ async findApprovedDepositApproval(amountRawUsdc2) {
1255
+ try {
1256
+ const approvals = await this.listApprovals("approved");
1257
+ const match = approvals.find((approval) => {
1258
+ const binding = approval.binding;
1259
+ return binding?.kind === "deposit" && binding.amountRawUsdc === amountRawUsdc2.toString();
1260
+ });
1261
+ return match?.approvalId;
1262
+ } catch {
1263
+ return void 0;
1264
+ }
1265
+ }
1171
1266
  /**
1172
1267
  * Polls the reconciling GET endpoint until the intent leaves "submitted"
1173
1268
  * (each read looks the tx up on-chain) or the timeout elapses.
@@ -1217,10 +1312,13 @@ var VaultFlowClient = class {
1217
1312
  });
1218
1313
  const text = await response.text();
1219
1314
  if (response.status !== 200) {
1315
+ const parsed = parseRelayerError(text);
1220
1316
  throw new VaultFlowClientError(
1221
1317
  step,
1222
- `${path} failed with ${response.status}: ${text}`,
1223
- text
1318
+ parsed.message === null ? `${path} failed with ${response.status}: ${text}` : `${path} failed (${parsed.code ?? response.status}): ${parsed.message}`,
1319
+ text,
1320
+ parsed.code,
1321
+ parsed.details
1224
1322
  );
1225
1323
  }
1226
1324
  try {
@@ -1233,7 +1331,49 @@ var VaultFlowClient = class {
1233
1331
  );
1234
1332
  }
1235
1333
  }
1334
+ async getJson(path) {
1335
+ const url = `${this.baseUrl}${path}`;
1336
+ const response = await this.fetchImpl(url, {
1337
+ headers: await walletAuthHeaders({
1338
+ signer: this.signer,
1339
+ method: "GET",
1340
+ url
1341
+ })
1342
+ });
1343
+ const text = await response.text();
1344
+ if (response.status !== 200) {
1345
+ const parsed = parseRelayerError(text);
1346
+ throw new VaultFlowClientError(
1347
+ "read",
1348
+ parsed.message === null ? `${path} failed with ${response.status}: ${text}` : `${path} failed (${parsed.code ?? response.status}): ${parsed.message}`,
1349
+ text,
1350
+ parsed.code,
1351
+ parsed.details
1352
+ );
1353
+ }
1354
+ try {
1355
+ return JSON.parse(text);
1356
+ } catch {
1357
+ throw new VaultFlowClientError(
1358
+ "read",
1359
+ `${path} returned 200 with a non-JSON body`,
1360
+ text
1361
+ );
1362
+ }
1363
+ }
1236
1364
  };
1365
+ function parseRelayerError(text) {
1366
+ try {
1367
+ const parsed = JSON.parse(text);
1368
+ return {
1369
+ code: typeof parsed.error?.code === "string" ? parsed.error.code : null,
1370
+ message: typeof parsed.error?.message === "string" ? parsed.error.message : null,
1371
+ details: parsed.error?.details ?? null
1372
+ };
1373
+ } catch {
1374
+ return { code: null, message: null, details: null };
1375
+ }
1376
+ }
1237
1377
 
1238
1378
  // ../../src/solana/keys.ts
1239
1379
  import { readFileSync } from "node:fs";
@@ -1283,9 +1423,13 @@ var relayerBaseUrl = process.env.SUBLY_RELAYER_URL ?? process.env.SUBLY_FACILITA
1283
1423
  var amountRawUsdc = process.argv[2];
1284
1424
  if (amountRawUsdc === void 0 || !/^[1-9]\d*$/.test(amountRawUsdc)) {
1285
1425
  fail(
1286
- "Usage: npm run demo:withdraw -- <amountRawUsdc> (positive integer, e.g. 1000000 = 1 USDC)"
1426
+ "Usage: pay withdraw <amountRawUsdc> [apr_<approvalId>] (positive integer, e.g. 1000000 = 1 USDC)"
1287
1427
  );
1288
1428
  }
1429
+ var approvalId = process.argv[3];
1430
+ if (approvalId !== void 0 && !/^apr_[0-9a-f]+$/i.test(approvalId)) {
1431
+ fail(`unrecognized argument: ${approvalId} (expected apr_<approvalId>)`);
1432
+ }
1289
1433
  var keyPairSigner = await loadKeyPairSigner({
1290
1434
  base58Secret: process.env.SUBLY_DEMO_AGENT_KEYPAIR,
1291
1435
  jsonFilePath: process.env.SUBLY_DEMO_AGENT_KEYPAIR_PATH,
@@ -1309,10 +1453,30 @@ console.log(
1309
1453
  var submitted;
1310
1454
  try {
1311
1455
  submitted = await vaultFlows.withdraw({
1312
- amountRawUsdc: BigInt(amountRawUsdc)
1456
+ amountRawUsdc: BigInt(amountRawUsdc),
1457
+ ...approvalId === void 0 ? {} : { approvalId }
1313
1458
  });
1314
1459
  } catch (error) {
1315
1460
  if (error instanceof VaultFlowClientError) {
1461
+ if (error.code === "withdrawal_approval_required") {
1462
+ const details = error.errorDetails ?? {};
1463
+ process.stdout.write(
1464
+ `${JSON.stringify(
1465
+ {
1466
+ ok: false,
1467
+ approvalRequired: true,
1468
+ approvalId: details.approvalId ?? null,
1469
+ approveUrl: details.approveUrl ?? null,
1470
+ expiresAtMs: details.expiresAtMs ?? null,
1471
+ message: `This withdrawal needs the owner's approval. Paste approveUrl to the user; once they approve, retry: pay withdraw ${amountRawUsdc} ${details.approvalId ?? "<approvalId>"}`
1472
+ },
1473
+ null,
1474
+ 2
1475
+ )}
1476
+ `
1477
+ );
1478
+ process.exit(1);
1479
+ }
1316
1480
  fail(`[withdraw] ${error.step} failed: ${error.message}`);
1317
1481
  }
1318
1482
  throw error;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@subly_fi/pay",
3
- "version": "0.4.1",
4
- "description": "Subly client: pay for ANY standard x402 (HTTP 402) paid API from Kamino vault yield — the seller needs no Subly integration. Ships an MCP server and a one-shot pay/deposit CLI; non-custodial (signs locally with your own key).",
3
+ "version": "0.6.0",
4
+ "description": "Subly client: pay compatible standard x402 Solana USDC exact APIs from Kamino vault yield — the seller needs no Subly integration. Ships an MCP server and a one-shot pay/deposit CLI; non-custodial (signs locally with your own key).",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "engines": {