@subly_fi/pay 0.3.0 → 0.4.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/README.md +11 -4
- package/dist/deposit.js +244 -53
- package/dist/mcp-server.js +811 -210
- package/dist/pay.js +546 -128
- package/dist/withdraw.js +280 -87
- package/package.json +1 -1
package/dist/pay.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// src/pay.ts
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join as join2 } from "node:path";
|
|
4
|
+
|
|
1
5
|
// ../../src/client/agent-wallet-signer.ts
|
|
2
6
|
import { signBytes } from "@solana/kit";
|
|
3
7
|
import bs584 from "bs58";
|
|
@@ -19,6 +23,7 @@ import {
|
|
|
19
23
|
|
|
20
24
|
// ../../src/lib/hash.ts
|
|
21
25
|
import { createHash } from "node:crypto";
|
|
26
|
+
var EMPTY_BODY_HASH = "sha256-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
22
27
|
function sha256TaggedHex(data) {
|
|
23
28
|
return `sha256-${createHash("sha256").update(data).digest("hex")}`;
|
|
24
29
|
}
|
|
@@ -115,12 +120,12 @@ function deriveAssociatedTokenAddress(params) {
|
|
|
115
120
|
"associatedTokenProgramId"
|
|
116
121
|
);
|
|
117
122
|
for (let bump = 255; bump >= 0; bump -= 1) {
|
|
118
|
-
const
|
|
123
|
+
const address2 = createProgramAddress(
|
|
119
124
|
[owner, tokenProgramId, mint, Uint8Array.of(bump)],
|
|
120
125
|
associatedTokenProgramId
|
|
121
126
|
);
|
|
122
|
-
if (
|
|
123
|
-
return bs582.encode(
|
|
127
|
+
if (address2 !== null) {
|
|
128
|
+
return bs582.encode(address2);
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
131
|
throw new Error("Unable to derive associated token account address");
|
|
@@ -1036,9 +1041,6 @@ async function ensureWalletOnboarded(params) {
|
|
|
1036
1041
|
await post("sync", `/v1/wallets/${wallet}/sync`, { source: "chain" });
|
|
1037
1042
|
}
|
|
1038
1043
|
|
|
1039
|
-
// ../../src/client/relayer-yield-realizer.ts
|
|
1040
|
-
import { address as address2 } from "@solana/kit";
|
|
1041
|
-
|
|
1042
1044
|
// ../../src/client/lookup-tables.ts
|
|
1043
1045
|
import { fetchAllMaybeAddressLookupTable } from "@solana-program/address-lookup-table";
|
|
1044
1046
|
import { address, getCompiledTransactionMessageDecoder as getCompiledTransactionMessageDecoder2 } from "@solana/kit";
|
|
@@ -1079,124 +1081,165 @@ async function fetchLookupTablesForTransaction(rpc2, serializedTransaction) {
|
|
|
1079
1081
|
return result;
|
|
1080
1082
|
}
|
|
1081
1083
|
|
|
1082
|
-
// ../../src/client/
|
|
1083
|
-
var
|
|
1084
|
-
constructor(
|
|
1084
|
+
// ../../src/client/vault-flows.ts
|
|
1085
|
+
var VaultFlowClientError = class extends Error {
|
|
1086
|
+
constructor(step, message, detail = null) {
|
|
1085
1087
|
super(message);
|
|
1086
|
-
this.
|
|
1088
|
+
this.step = step;
|
|
1087
1089
|
this.detail = detail;
|
|
1088
|
-
this.name = "
|
|
1090
|
+
this.name = "VaultFlowClientError";
|
|
1089
1091
|
}
|
|
1090
|
-
|
|
1092
|
+
step;
|
|
1091
1093
|
detail;
|
|
1092
1094
|
};
|
|
1093
|
-
var
|
|
1094
|
-
|
|
1095
|
+
var VaultFlowClient = class {
|
|
1096
|
+
baseUrl;
|
|
1095
1097
|
signer;
|
|
1096
|
-
rpc;
|
|
1097
1098
|
fetchImpl;
|
|
1098
1099
|
lookupTablesFor;
|
|
1100
|
+
pollTimeoutMs;
|
|
1101
|
+
pollIntervalMs;
|
|
1099
1102
|
constructor(config) {
|
|
1100
|
-
this.
|
|
1101
|
-
facilitatorBaseUrl: config.facilitatorBaseUrl.replace(/\/$/, ""),
|
|
1102
|
-
usdcMint: config.usdcMint ?? SUBLY_VAULT.usdcMint,
|
|
1103
|
-
forceRealizeFullAmount: config.forceRealizeFullAmount ?? false
|
|
1104
|
-
};
|
|
1103
|
+
this.baseUrl = config.facilitatorBaseUrl.replace(/\/$/, "");
|
|
1105
1104
|
this.signer = config.signer;
|
|
1106
|
-
this.rpc = config.rpc;
|
|
1107
1105
|
this.fetchImpl = config.fetchImpl ?? fetch;
|
|
1108
|
-
this.lookupTablesFor = config.lookupTablesFor ?? ((serializedTransaction) => fetchLookupTablesForTransaction(
|
|
1106
|
+
this.lookupTablesFor = config.lookupTablesFor ?? ((serializedTransaction) => fetchLookupTablesForTransaction(config.rpc, serializedTransaction));
|
|
1107
|
+
this.pollTimeoutMs = config.pollTimeoutMs ?? 9e4;
|
|
1108
|
+
this.pollIntervalMs = config.pollIntervalMs ?? 2500;
|
|
1109
1109
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
mint: this.config.usdcMint
|
|
1114
|
-
});
|
|
1115
|
-
let shortfallRawUsdc;
|
|
1116
|
-
if (this.config.forceRealizeFullAmount) {
|
|
1117
|
-
shortfallRawUsdc = input.amountRawUsdc;
|
|
1118
|
-
} else {
|
|
1119
|
-
const currentBalance = await this.readTokenBalance(agentAta);
|
|
1120
|
-
if (currentBalance >= input.amountRawUsdc) {
|
|
1121
|
-
return { realizedRawUsdc: 0n, txSignature: null };
|
|
1122
|
-
}
|
|
1123
|
-
shortfallRawUsdc = input.amountRawUsdc - currentBalance;
|
|
1124
|
-
}
|
|
1125
|
-
await this.assertSpendableYield(shortfallRawUsdc);
|
|
1126
|
-
const prepared = await this.postJson("/v1/withdrawals/prepare", {
|
|
1110
|
+
/** Moves USDC from the agent wallet into the vault (fee sponsored). */
|
|
1111
|
+
async deposit(input) {
|
|
1112
|
+
const prepared = await this.postJson("prepare", "/v1/deposits/prepare", {
|
|
1127
1113
|
wallet: this.signer.walletAddress,
|
|
1128
|
-
amountRawUsdc:
|
|
1114
|
+
amountRawUsdc: input.amountRawUsdc.toString()
|
|
1115
|
+
});
|
|
1116
|
+
const signed = await this.signer.signDeposit({
|
|
1117
|
+
intent: prepared.signingIntent,
|
|
1118
|
+
serializedTransaction: prepared.serializedTransaction,
|
|
1119
|
+
lookupTables: await this.lookupTablesFor(prepared.serializedTransaction)
|
|
1129
1120
|
});
|
|
1121
|
+
let outcome = await this.postJson("submit", "/v1/deposits/submit", {
|
|
1122
|
+
depositId: prepared.depositId,
|
|
1123
|
+
serializedTransaction: signed.serializedTransaction,
|
|
1124
|
+
agentSignature: signed.agentSignature
|
|
1125
|
+
});
|
|
1126
|
+
if (outcome.status === "submitted") {
|
|
1127
|
+
outcome = await this.pollUntilTerminal(
|
|
1128
|
+
`/v1/deposits/${prepared.depositId}`,
|
|
1129
|
+
outcome
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
return {
|
|
1133
|
+
depositId: prepared.depositId,
|
|
1134
|
+
status: outcome.status,
|
|
1135
|
+
txSignature: outcome.txSignature ?? null,
|
|
1136
|
+
actualDepositRawUsdc: outcome.actualDepositRawUsdc ?? null,
|
|
1137
|
+
sharesMintedRaw: outcome.sharesMintedRaw ?? null,
|
|
1138
|
+
errorCode: outcome.errorCode ?? null
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Moves USDC from the vault back to the agent wallet's USDC ATA (fee
|
|
1143
|
+
* sponsored). A plain withdrawal is the exit path and MAY spend principal;
|
|
1144
|
+
* with purpose "yield_realize" the relayer refuses anything beyond the
|
|
1145
|
+
* spendable yield (the payment path, via RelayerYieldRealizer).
|
|
1146
|
+
*/
|
|
1147
|
+
async withdraw(input) {
|
|
1148
|
+
const prepared = await this.postJson(
|
|
1149
|
+
"prepare",
|
|
1150
|
+
"/v1/withdrawals/prepare",
|
|
1151
|
+
{
|
|
1152
|
+
wallet: this.signer.walletAddress,
|
|
1153
|
+
amountRawUsdc: input.amountRawUsdc.toString(),
|
|
1154
|
+
...input.purpose === void 0 ? {} : { purpose: input.purpose }
|
|
1155
|
+
}
|
|
1156
|
+
);
|
|
1130
1157
|
const signed = await this.signer.signWithdrawal({
|
|
1131
1158
|
intent: prepared.signingIntent,
|
|
1132
1159
|
serializedTransaction: prepared.serializedTransaction,
|
|
1133
1160
|
lookupTables: await this.lookupTablesFor(prepared.serializedTransaction)
|
|
1134
1161
|
});
|
|
1135
|
-
let
|
|
1162
|
+
let outcome = await this.postJson("submit", "/v1/withdrawals/submit", {
|
|
1136
1163
|
withdrawalId: prepared.withdrawalId,
|
|
1137
1164
|
serializedTransaction: signed.serializedTransaction,
|
|
1138
1165
|
agentSignature: signed.agentSignature
|
|
1139
1166
|
});
|
|
1140
|
-
if (
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
throw new RelayerRealizeError(
|
|
1145
|
-
"realize_not_confirmed",
|
|
1146
|
-
`yield realize withdrawal did not confirm (status=${settled.status})`,
|
|
1147
|
-
settled
|
|
1167
|
+
if (outcome.status === "submitted") {
|
|
1168
|
+
outcome = await this.pollUntilTerminal(
|
|
1169
|
+
`/v1/withdrawals/${prepared.withdrawalId}`,
|
|
1170
|
+
outcome
|
|
1148
1171
|
);
|
|
1149
1172
|
}
|
|
1150
1173
|
return {
|
|
1151
|
-
|
|
1152
|
-
|
|
1174
|
+
withdrawalId: prepared.withdrawalId,
|
|
1175
|
+
status: outcome.status,
|
|
1176
|
+
txSignature: outcome.txSignature ?? null,
|
|
1177
|
+
destinationUsdcAta: prepared.destinationUsdcAta,
|
|
1178
|
+
actualWithdrawRawUsdc: outcome.actualWithdrawRawUsdc ?? null,
|
|
1179
|
+
actualSharesBurnedRaw: outcome.actualSharesBurnedRaw ?? null,
|
|
1180
|
+
errorCode: outcome.errorCode ?? null
|
|
1153
1181
|
};
|
|
1154
1182
|
}
|
|
1155
|
-
/**
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
"could not read the spendable-yield budget",
|
|
1171
|
-
error
|
|
1172
|
-
);
|
|
1183
|
+
/**
|
|
1184
|
+
* Reads the yield budget. Syncs the relayer's ledger from chain first (so
|
|
1185
|
+
* yield accrued since the last sync shows up); the sync is best-effort and
|
|
1186
|
+
* on failure the last-synced view is returned.
|
|
1187
|
+
*/
|
|
1188
|
+
async getBudget(options = {}) {
|
|
1189
|
+
if (options.refreshFromChain !== false) {
|
|
1190
|
+
try {
|
|
1191
|
+
await this.postJson(
|
|
1192
|
+
"sync",
|
|
1193
|
+
`/v1/wallets/${this.signer.walletAddress}/sync`,
|
|
1194
|
+
{ source: "chain" }
|
|
1195
|
+
);
|
|
1196
|
+
} catch {
|
|
1197
|
+
}
|
|
1173
1198
|
}
|
|
1199
|
+
const url2 = `${this.baseUrl}/v1/wallets/${this.signer.walletAddress}/budget`;
|
|
1200
|
+
const response = await this.fetchImpl(url2, {
|
|
1201
|
+
headers: await walletAuthHeaders({
|
|
1202
|
+
signer: this.signer,
|
|
1203
|
+
method: "GET",
|
|
1204
|
+
url: url2
|
|
1205
|
+
})
|
|
1206
|
+
});
|
|
1207
|
+
const text = await response.text();
|
|
1174
1208
|
if (response.status !== 200) {
|
|
1175
|
-
throw new
|
|
1176
|
-
"
|
|
1177
|
-
`budget endpoint returned ${response.status}`
|
|
1209
|
+
throw new VaultFlowClientError(
|
|
1210
|
+
"budget",
|
|
1211
|
+
`budget endpoint returned ${response.status}: ${text}`
|
|
1178
1212
|
);
|
|
1179
1213
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1214
|
+
let parsed;
|
|
1215
|
+
try {
|
|
1216
|
+
parsed = JSON.parse(text);
|
|
1217
|
+
} catch {
|
|
1218
|
+
throw new VaultFlowClientError(
|
|
1219
|
+
"budget",
|
|
1220
|
+
"budget endpoint returned 200 with a non-JSON body",
|
|
1221
|
+
text
|
|
1187
1222
|
);
|
|
1188
1223
|
}
|
|
1224
|
+
const body2 = parsed;
|
|
1225
|
+
return {
|
|
1226
|
+
wallet: this.signer.walletAddress,
|
|
1227
|
+
principalBasisRawUsdc: body2.position?.principalBasisRawUsdc ?? "0",
|
|
1228
|
+
positionValueRawUsdc: body2.budget?.positionValueRawUsdc ?? "0",
|
|
1229
|
+
grossYieldRawUsdc: body2.budget?.grossYieldRawUsdc ?? "0",
|
|
1230
|
+
spendableYieldRawUsdc: body2.budget?.spendableYieldRawUsdc ?? "0"
|
|
1231
|
+
};
|
|
1189
1232
|
}
|
|
1190
1233
|
/**
|
|
1191
|
-
* Polls
|
|
1192
|
-
*
|
|
1234
|
+
* Polls the reconciling GET endpoint until the intent leaves "submitted"
|
|
1235
|
+
* (each read looks the tx up on-chain) or the timeout elapses.
|
|
1193
1236
|
*/
|
|
1194
|
-
async
|
|
1195
|
-
const deadline = Date.now() +
|
|
1196
|
-
let latest =
|
|
1237
|
+
async pollUntilTerminal(path, last) {
|
|
1238
|
+
const deadline = Date.now() + this.pollTimeoutMs;
|
|
1239
|
+
let latest = last;
|
|
1197
1240
|
while (Date.now() < deadline) {
|
|
1198
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1199
|
-
const url2 = `${this.
|
|
1241
|
+
await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
|
|
1242
|
+
const url2 = `${this.baseUrl}${path}`;
|
|
1200
1243
|
const response = await this.fetchImpl(url2, {
|
|
1201
1244
|
headers: await walletAuthHeaders({
|
|
1202
1245
|
signer: this.signer,
|
|
@@ -1207,19 +1250,19 @@ var RelayerYieldRealizer = class {
|
|
|
1207
1250
|
if (response.status !== 200) {
|
|
1208
1251
|
continue;
|
|
1209
1252
|
}
|
|
1210
|
-
|
|
1253
|
+
try {
|
|
1254
|
+
latest = await response.json();
|
|
1255
|
+
} catch {
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1211
1258
|
if (latest.status !== "submitted") {
|
|
1212
1259
|
return latest;
|
|
1213
1260
|
}
|
|
1214
1261
|
}
|
|
1215
|
-
return latest
|
|
1216
|
-
status: "submitted",
|
|
1217
|
-
txSignature: null,
|
|
1218
|
-
actualWithdrawRawUsdc: null
|
|
1219
|
-
};
|
|
1262
|
+
return latest;
|
|
1220
1263
|
}
|
|
1221
|
-
async postJson(path, body2) {
|
|
1222
|
-
const url2 = `${this.
|
|
1264
|
+
async postJson(step, path, body2) {
|
|
1265
|
+
const url2 = `${this.baseUrl}${path}`;
|
|
1223
1266
|
const serialized = JSON.stringify(body2);
|
|
1224
1267
|
const response = await this.fetchImpl(url2, {
|
|
1225
1268
|
method: "POST",
|
|
@@ -1236,22 +1279,133 @@ var RelayerYieldRealizer = class {
|
|
|
1236
1279
|
});
|
|
1237
1280
|
const text = await response.text();
|
|
1238
1281
|
if (response.status !== 200) {
|
|
1282
|
+
throw new VaultFlowClientError(
|
|
1283
|
+
step,
|
|
1284
|
+
`${path} failed with ${response.status}: ${text}`,
|
|
1285
|
+
text
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
try {
|
|
1289
|
+
return JSON.parse(text);
|
|
1290
|
+
} catch {
|
|
1291
|
+
throw new VaultFlowClientError(
|
|
1292
|
+
step,
|
|
1293
|
+
`${path} returned 200 with a non-JSON body`,
|
|
1294
|
+
text
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1300
|
+
// ../../src/client/relayer-yield-realizer.ts
|
|
1301
|
+
var REALIZE_OVERHEAD_RAW_USDC = 2500n;
|
|
1302
|
+
var RelayerRealizeError = class extends Error {
|
|
1303
|
+
constructor(code, message, detail = null) {
|
|
1304
|
+
super(message);
|
|
1305
|
+
this.code = code;
|
|
1306
|
+
this.detail = detail;
|
|
1307
|
+
this.name = "RelayerRealizeError";
|
|
1308
|
+
}
|
|
1309
|
+
code;
|
|
1310
|
+
detail;
|
|
1311
|
+
};
|
|
1312
|
+
var RelayerYieldRealizer = class {
|
|
1313
|
+
vaultFlows;
|
|
1314
|
+
constructor(config) {
|
|
1315
|
+
this.vaultFlows = new VaultFlowClient({
|
|
1316
|
+
facilitatorBaseUrl: config.facilitatorBaseUrl,
|
|
1317
|
+
signer: config.signer,
|
|
1318
|
+
rpc: config.rpc,
|
|
1319
|
+
...config.fetchImpl === void 0 ? {} : { fetchImpl: config.fetchImpl },
|
|
1320
|
+
...config.lookupTablesFor === void 0 ? {} : { lookupTablesFor: config.lookupTablesFor }
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
async ensureUsdcAvailable(input) {
|
|
1324
|
+
const shortfallRawUsdc = input.amountRawUsdc;
|
|
1325
|
+
await this.assertSpendableYield(shortfallRawUsdc);
|
|
1326
|
+
let outcome;
|
|
1327
|
+
try {
|
|
1328
|
+
outcome = await this.vaultFlows.withdraw({
|
|
1329
|
+
amountRawUsdc: shortfallRawUsdc,
|
|
1330
|
+
// The relayer refuses to prepare this withdrawal beyond the spendable
|
|
1331
|
+
// yield — the principal-protection guard the client cannot bypass.
|
|
1332
|
+
purpose: "yield_realize"
|
|
1333
|
+
});
|
|
1334
|
+
} catch (error) {
|
|
1335
|
+
throw this.mapWithdrawError(error);
|
|
1336
|
+
}
|
|
1337
|
+
if (outcome.status !== "confirmed" || outcome.txSignature === null) {
|
|
1239
1338
|
throw new RelayerRealizeError(
|
|
1240
|
-
|
|
1241
|
-
|
|
1339
|
+
"realize_not_confirmed",
|
|
1340
|
+
`yield realize withdrawal did not confirm (status=${outcome.status})`,
|
|
1341
|
+
outcome
|
|
1242
1342
|
);
|
|
1243
1343
|
}
|
|
1244
|
-
return
|
|
1344
|
+
return {
|
|
1345
|
+
realizedRawUsdc: BigInt(outcome.actualWithdrawRawUsdc ?? "0"),
|
|
1346
|
+
txSignature: outcome.txSignature
|
|
1347
|
+
};
|
|
1245
1348
|
}
|
|
1246
|
-
|
|
1349
|
+
/**
|
|
1350
|
+
* Refuses to realize more than the ledger's spendable yield (principal).
|
|
1351
|
+
* getBudget syncs the relayer's ledger from chain first (best-effort), so a
|
|
1352
|
+
* long-running client sees yield as it accrues instead of a frozen view.
|
|
1353
|
+
*/
|
|
1354
|
+
async assertSpendableYield(shortfallRawUsdc) {
|
|
1355
|
+
let spendable;
|
|
1247
1356
|
try {
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1250
|
-
} catch {
|
|
1251
|
-
|
|
1357
|
+
const budget = await this.vaultFlows.getBudget();
|
|
1358
|
+
spendable = BigInt(budget.spendableYieldRawUsdc);
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
throw new RelayerRealizeError(
|
|
1361
|
+
"budget_unavailable",
|
|
1362
|
+
"could not read the spendable-yield budget",
|
|
1363
|
+
error
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
const requiredRawUsdc = shortfallRawUsdc + REALIZE_OVERHEAD_RAW_USDC;
|
|
1367
|
+
if (spendable < requiredRawUsdc) {
|
|
1368
|
+
throw new RelayerRealizeError(
|
|
1369
|
+
"insufficient_yield",
|
|
1370
|
+
`spendable yield ${spendable} cannot cover ${shortfallRawUsdc} raw USDC plus the ${REALIZE_OVERHEAD_RAW_USDC} raw fee headroom; the principal is never spent \u2014 wait for more yield`,
|
|
1371
|
+
{ spendableYieldRawUsdc: spendable.toString() }
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
mapWithdrawError(error) {
|
|
1376
|
+
if (!(error instanceof VaultFlowClientError)) {
|
|
1377
|
+
return new RelayerRealizeError(
|
|
1378
|
+
"prepare_failed",
|
|
1379
|
+
`yield realize failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1380
|
+
error
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
const serverCode = errorCodeFrom(error.detail);
|
|
1384
|
+
if (serverCode === "insufficient_yield" || serverCode === "post_state_principal_invariant_failed") {
|
|
1385
|
+
return new RelayerRealizeError(
|
|
1386
|
+
"insufficient_yield",
|
|
1387
|
+
"the relayer refused to realize beyond the spendable yield; the principal is never spent \u2014 wait for more yield",
|
|
1388
|
+
error.detail
|
|
1389
|
+
);
|
|
1252
1390
|
}
|
|
1391
|
+
return new RelayerRealizeError(
|
|
1392
|
+
error.step === "submit" ? "submit_failed" : "prepare_failed",
|
|
1393
|
+
error.message,
|
|
1394
|
+
error.detail
|
|
1395
|
+
);
|
|
1253
1396
|
}
|
|
1254
1397
|
};
|
|
1398
|
+
function errorCodeFrom(detail) {
|
|
1399
|
+
if (typeof detail !== "string") {
|
|
1400
|
+
return null;
|
|
1401
|
+
}
|
|
1402
|
+
try {
|
|
1403
|
+
const parsed = JSON.parse(detail);
|
|
1404
|
+
return typeof parsed.error?.code === "string" ? parsed.error.code : null;
|
|
1405
|
+
} catch {
|
|
1406
|
+
return null;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1255
1409
|
|
|
1256
1410
|
// ../../src/x402/headers.ts
|
|
1257
1411
|
import { z } from "zod";
|
|
@@ -1319,6 +1473,14 @@ function decodeX402Header(headerValue) {
|
|
|
1319
1473
|
);
|
|
1320
1474
|
}
|
|
1321
1475
|
}
|
|
1476
|
+
function requestBodyHashFor(body2) {
|
|
1477
|
+
if (body2 === null || body2 === void 0 || body2.length === 0) {
|
|
1478
|
+
return EMPTY_BODY_HASH;
|
|
1479
|
+
}
|
|
1480
|
+
return sha256TaggedHex(
|
|
1481
|
+
typeof body2 === "string" ? Buffer.from(body2, "utf8") : Buffer.from(body2)
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1322
1484
|
|
|
1323
1485
|
// ../../src/x402/standard-requirements.ts
|
|
1324
1486
|
import { z as z2 } from "zod";
|
|
@@ -1401,6 +1563,24 @@ function selectPayableSolanaRequirement(requirements, options) {
|
|
|
1401
1563
|
feePayer: requirement.extra?.feePayer ?? null
|
|
1402
1564
|
};
|
|
1403
1565
|
}
|
|
1566
|
+
function standardRequirementMatchesSelected(candidate, selected) {
|
|
1567
|
+
const parsed = standardExactRequirementSchema.safeParse(candidate);
|
|
1568
|
+
return parsed.success && stableJson(parsed.data) === stableJson(selected.requirement);
|
|
1569
|
+
}
|
|
1570
|
+
function stableJson(value) {
|
|
1571
|
+
return JSON.stringify(sortJson(value));
|
|
1572
|
+
}
|
|
1573
|
+
function sortJson(value) {
|
|
1574
|
+
if (Array.isArray(value)) {
|
|
1575
|
+
return value.map(sortJson);
|
|
1576
|
+
}
|
|
1577
|
+
if (value !== null && typeof value === "object") {
|
|
1578
|
+
return Object.fromEntries(
|
|
1579
|
+
Object.entries(value).filter(([, entry]) => entry !== void 0).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => [key, sortJson(entry)])
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
return value;
|
|
1583
|
+
}
|
|
1404
1584
|
|
|
1405
1585
|
// ../../src/client/standard-x402-payer.ts
|
|
1406
1586
|
var StandardX402PayError = class extends Error {
|
|
@@ -1420,6 +1600,10 @@ var StandardX402Payer = class {
|
|
|
1420
1600
|
defaultMaxAmountRawUsdc;
|
|
1421
1601
|
network;
|
|
1422
1602
|
usdcMint;
|
|
1603
|
+
stateStore;
|
|
1604
|
+
pending = /* @__PURE__ */ new Map();
|
|
1605
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1606
|
+
nowMs;
|
|
1423
1607
|
constructor(config) {
|
|
1424
1608
|
this.realizer = config.realizer;
|
|
1425
1609
|
this.x402Fetch = config.x402Fetch;
|
|
@@ -1427,10 +1611,59 @@ var StandardX402Payer = class {
|
|
|
1427
1611
|
this.defaultMaxAmountRawUsdc = config.defaultMaxAmountRawUsdc;
|
|
1428
1612
|
this.network = config.network ?? SOLANA_MAINNET_NETWORK;
|
|
1429
1613
|
this.usdcMint = config.usdcMint ?? SUBLY_VAULT.usdcMint;
|
|
1614
|
+
this.stateStore = config.stateStore ?? null;
|
|
1615
|
+
this.nowMs = config.nowMs ?? (() => Date.now());
|
|
1616
|
+
if (this.stateStore !== null) {
|
|
1617
|
+
for (const record of this.stateStore.load()) {
|
|
1618
|
+
this.pending.set(record.key, record);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1430
1621
|
}
|
|
1431
|
-
|
|
1622
|
+
pay(input) {
|
|
1623
|
+
const method2 = (input.method ?? "GET").toUpperCase();
|
|
1624
|
+
const requestBodyHash = requestBodyHashFor(input.body ?? null);
|
|
1625
|
+
const pendingKey = pendingPaymentKey({
|
|
1626
|
+
url: input.url,
|
|
1627
|
+
method: method2,
|
|
1628
|
+
requestBodyHash
|
|
1629
|
+
});
|
|
1630
|
+
const existingFlow = this.inFlight.get(pendingKey);
|
|
1631
|
+
if (existingFlow !== void 0) {
|
|
1632
|
+
return existingFlow;
|
|
1633
|
+
}
|
|
1634
|
+
const flow = this.run(input, {
|
|
1635
|
+
method: method2,
|
|
1636
|
+
requestBodyHash,
|
|
1637
|
+
pendingKey
|
|
1638
|
+
}).finally(() => {
|
|
1639
|
+
this.inFlight.delete(pendingKey);
|
|
1640
|
+
});
|
|
1641
|
+
this.inFlight.set(pendingKey, flow);
|
|
1642
|
+
return flow;
|
|
1643
|
+
}
|
|
1644
|
+
async run(input, computed) {
|
|
1645
|
+
const { method: method2, requestBodyHash, pendingKey } = computed;
|
|
1646
|
+
const existingPending = this.pending.get(pendingKey);
|
|
1647
|
+
if (existingPending !== void 0 && input.forceNewPayment !== true) {
|
|
1648
|
+
throw new StandardX402PayError(
|
|
1649
|
+
"payment_outcome_unknown",
|
|
1650
|
+
"a previous external x402 payment for this request has an unknown outcome. Verify whether it settled before purchasing again; to pay again anyway, call with forceNewPayment=true.",
|
|
1651
|
+
existingPending
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
if (existingPending !== void 0 && input.forceNewPayment === true) {
|
|
1655
|
+
try {
|
|
1656
|
+
this.untrack(pendingKey);
|
|
1657
|
+
} catch (error) {
|
|
1658
|
+
throw new StandardX402PayError(
|
|
1659
|
+
"state_persist_failed",
|
|
1660
|
+
"could not clear the previous pending x402 marker before forcing a new payment",
|
|
1661
|
+
error
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1432
1665
|
const init = {
|
|
1433
|
-
method:
|
|
1666
|
+
method: method2,
|
|
1434
1667
|
...input.body === void 0 ? {} : { body: input.body },
|
|
1435
1668
|
...input.headers === void 0 ? {} : { headers: input.headers }
|
|
1436
1669
|
};
|
|
@@ -1459,15 +1692,55 @@ var StandardX402Payer = class {
|
|
|
1459
1692
|
error
|
|
1460
1693
|
);
|
|
1461
1694
|
}
|
|
1462
|
-
const
|
|
1695
|
+
const pendingRecord = {
|
|
1696
|
+
key: pendingKey,
|
|
1697
|
+
url: input.url,
|
|
1698
|
+
method: method2,
|
|
1699
|
+
requestBodyHash,
|
|
1700
|
+
amountRawUsdc: selected.amountRawUsdc.toString(),
|
|
1701
|
+
payTo: selected.payTo,
|
|
1702
|
+
feePayer: selected.feePayer,
|
|
1703
|
+
realizedRawUsdc: realized.realizedRawUsdc.toString(),
|
|
1704
|
+
realizeTxSignature: realized.txSignature,
|
|
1705
|
+
status: "realized",
|
|
1706
|
+
createdAtMs: this.nowMs(),
|
|
1707
|
+
updatedAtMs: this.nowMs()
|
|
1708
|
+
};
|
|
1709
|
+
try {
|
|
1710
|
+
this.track(pendingRecord);
|
|
1711
|
+
} catch (error) {
|
|
1712
|
+
throw new StandardX402PayError(
|
|
1713
|
+
"state_persist_failed",
|
|
1714
|
+
"could not persist the pending x402 marker; refusing to attempt the external payment because a restart would not be double-payment safe",
|
|
1715
|
+
{ error, pendingPayment: pendingRecord }
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1718
|
+
let response;
|
|
1719
|
+
try {
|
|
1720
|
+
response = await this.x402Fetch(input.url, init, selected);
|
|
1721
|
+
} catch (error) {
|
|
1722
|
+
const persistError = this.tryMarkUnknown(pendingKey, {
|
|
1723
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1724
|
+
});
|
|
1725
|
+
throw new StandardX402PayError(
|
|
1726
|
+
"payment_outcome_unknown",
|
|
1727
|
+
`the x402 payment attempt failed after yield was realized; verify whether it settled before paying again: ${error instanceof Error ? error.message : String(error)}`,
|
|
1728
|
+
{ error, persistError }
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1463
1731
|
const bodyText = await response.text();
|
|
1464
1732
|
if (response.status !== 200) {
|
|
1733
|
+
const persistError = this.tryMarkUnknown(pendingKey, {
|
|
1734
|
+
status: response.status,
|
|
1735
|
+
body: bodyText
|
|
1736
|
+
});
|
|
1465
1737
|
throw new StandardX402PayError(
|
|
1466
|
-
"
|
|
1467
|
-
`the x402 payment
|
|
1468
|
-
{ status: response.status, body: bodyText }
|
|
1738
|
+
"payment_outcome_unknown",
|
|
1739
|
+
`the x402 payment attempt returned ${response.status} after yield was realized; verify whether it settled before paying again`,
|
|
1740
|
+
{ status: response.status, body: bodyText, persistError }
|
|
1469
1741
|
);
|
|
1470
1742
|
}
|
|
1743
|
+
this.clearDelivered(pendingKey);
|
|
1471
1744
|
return {
|
|
1472
1745
|
paid: true,
|
|
1473
1746
|
status: response.status,
|
|
@@ -1513,25 +1786,153 @@ var StandardX402Payer = class {
|
|
|
1513
1786
|
);
|
|
1514
1787
|
}
|
|
1515
1788
|
}
|
|
1789
|
+
track(record) {
|
|
1790
|
+
const previous = this.pending.get(record.key);
|
|
1791
|
+
this.pending.set(record.key, record);
|
|
1792
|
+
try {
|
|
1793
|
+
this.persist();
|
|
1794
|
+
} catch (error) {
|
|
1795
|
+
if (previous === void 0) {
|
|
1796
|
+
this.pending.delete(record.key);
|
|
1797
|
+
} else {
|
|
1798
|
+
this.pending.set(record.key, previous);
|
|
1799
|
+
}
|
|
1800
|
+
throw error;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
markUnknown(key, detail) {
|
|
1804
|
+
const current = this.pending.get(key);
|
|
1805
|
+
if (current === void 0) {
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
const next = {
|
|
1809
|
+
...current,
|
|
1810
|
+
status: "external_outcome_unknown",
|
|
1811
|
+
updatedAtMs: this.nowMs(),
|
|
1812
|
+
detail
|
|
1813
|
+
};
|
|
1814
|
+
this.pending.set(key, next);
|
|
1815
|
+
try {
|
|
1816
|
+
this.persist();
|
|
1817
|
+
} catch (error) {
|
|
1818
|
+
this.pending.set(key, current);
|
|
1819
|
+
throw error;
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
tryMarkUnknown(key, detail) {
|
|
1823
|
+
try {
|
|
1824
|
+
this.markUnknown(key, detail);
|
|
1825
|
+
return null;
|
|
1826
|
+
} catch (error) {
|
|
1827
|
+
return error;
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
untrack(key) {
|
|
1831
|
+
const previous = this.pending.get(key);
|
|
1832
|
+
const existed = previous !== void 0;
|
|
1833
|
+
this.pending.delete(key);
|
|
1834
|
+
try {
|
|
1835
|
+
this.persist();
|
|
1836
|
+
} catch (error) {
|
|
1837
|
+
if (existed) {
|
|
1838
|
+
this.pending.set(key, previous);
|
|
1839
|
+
}
|
|
1840
|
+
throw error;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
clearDelivered(key) {
|
|
1844
|
+
const previous = this.pending.get(key);
|
|
1845
|
+
this.pending.delete(key);
|
|
1846
|
+
try {
|
|
1847
|
+
this.persist();
|
|
1848
|
+
} catch (error) {
|
|
1849
|
+
if (previous !== void 0) {
|
|
1850
|
+
this.pending.set(key, previous);
|
|
1851
|
+
}
|
|
1852
|
+
console.error(
|
|
1853
|
+
`[subly-x402] payment delivered but pending marker could not be cleared: ${error instanceof Error ? error.message : String(error)}`
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
persist() {
|
|
1858
|
+
if (this.stateStore === null) {
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1861
|
+
this.stateStore.save([...this.pending.values()]);
|
|
1862
|
+
}
|
|
1516
1863
|
};
|
|
1864
|
+
function pendingPaymentKey(input) {
|
|
1865
|
+
return `${input.method}:${input.url}:${input.requestBodyHash}`;
|
|
1866
|
+
}
|
|
1517
1867
|
|
|
1518
1868
|
// ../../src/client/relayer-payer.ts
|
|
1519
1869
|
function createRelayerX402Payer(config) {
|
|
1520
1870
|
const realizer = new RelayerYieldRealizer({
|
|
1521
1871
|
facilitatorBaseUrl: config.facilitatorBaseUrl,
|
|
1522
1872
|
signer: config.signer,
|
|
1523
|
-
rpc: config.rpc
|
|
1524
|
-
forceRealizeFullAmount: config.forceRealizeFullAmount ?? false
|
|
1873
|
+
rpc: config.rpc
|
|
1525
1874
|
});
|
|
1526
1875
|
return new StandardX402Payer({
|
|
1527
1876
|
realizer,
|
|
1528
1877
|
x402Fetch: config.x402Fetch,
|
|
1529
|
-
defaultMaxAmountRawUsdc: config.defaultMaxAmountRawUsdc
|
|
1878
|
+
defaultMaxAmountRawUsdc: config.defaultMaxAmountRawUsdc,
|
|
1879
|
+
...config.stateStore === void 0 ? {} : { stateStore: config.stateStore }
|
|
1530
1880
|
});
|
|
1531
1881
|
}
|
|
1532
1882
|
|
|
1883
|
+
// ../../src/client/standard-x402-state-store.ts
|
|
1884
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
1885
|
+
import { basename, dirname, join } from "node:path";
|
|
1886
|
+
function fileStandardX402StateStore(path) {
|
|
1887
|
+
return {
|
|
1888
|
+
load() {
|
|
1889
|
+
let text;
|
|
1890
|
+
try {
|
|
1891
|
+
text = readFileSync(path, "utf8");
|
|
1892
|
+
} catch (error) {
|
|
1893
|
+
if (isMissingFileError(error)) {
|
|
1894
|
+
return [];
|
|
1895
|
+
}
|
|
1896
|
+
throw error;
|
|
1897
|
+
}
|
|
1898
|
+
const parsed = JSON.parse(text);
|
|
1899
|
+
if (!Array.isArray(parsed)) {
|
|
1900
|
+
throw new Error(`pending payment state is not an array: ${path}`);
|
|
1901
|
+
}
|
|
1902
|
+
for (const [index, record] of parsed.entries()) {
|
|
1903
|
+
if (!isPendingPaymentRecord(record)) {
|
|
1904
|
+
throw new Error(
|
|
1905
|
+
`pending payment state has an invalid record at index ${index}: ${path}`
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
return parsed;
|
|
1910
|
+
},
|
|
1911
|
+
save(records) {
|
|
1912
|
+
const directory = dirname(path);
|
|
1913
|
+
mkdirSync(directory, { recursive: true });
|
|
1914
|
+
const tempPath = join(
|
|
1915
|
+
directory,
|
|
1916
|
+
`.${basename(path)}.${process.pid}.${Date.now()}.tmp`
|
|
1917
|
+
);
|
|
1918
|
+
writeFileSync(tempPath, JSON.stringify(records, null, 2));
|
|
1919
|
+
renameSync(tempPath, path);
|
|
1920
|
+
}
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
function isMissingFileError(error) {
|
|
1924
|
+
return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
|
|
1925
|
+
}
|
|
1926
|
+
function isPendingPaymentRecord(value) {
|
|
1927
|
+
if (value === null || typeof value !== "object") {
|
|
1928
|
+
return false;
|
|
1929
|
+
}
|
|
1930
|
+
const record = value;
|
|
1931
|
+
return typeof record.key === "string" && typeof record.url === "string" && typeof record.method === "string" && typeof record.requestBodyHash === "string" && typeof record.amountRawUsdc === "string" && typeof record.payTo === "string" && (record.feePayer === null || typeof record.feePayer === "string") && typeof record.realizedRawUsdc === "string" && (record.realizeTxSignature === null || typeof record.realizeTxSignature === "string") && (record.status === "realized" || record.status === "external_outcome_unknown") && typeof record.createdAtMs === "number" && typeof record.updatedAtMs === "number";
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1533
1934
|
// ../../src/solana/keys.ts
|
|
1534
|
-
import { readFileSync } from "node:fs";
|
|
1935
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
1535
1936
|
import bs586 from "bs58";
|
|
1536
1937
|
import {
|
|
1537
1938
|
createKeyPairSignerFromBytes
|
|
@@ -1546,7 +1947,7 @@ async function loadKeyPairSigner(params) {
|
|
|
1546
1947
|
return createKeyPairSignerFromBytes(bytes);
|
|
1547
1948
|
}
|
|
1548
1949
|
if (jsonFilePath !== void 0 && jsonFilePath.length > 0) {
|
|
1549
|
-
const raw = JSON.parse(
|
|
1950
|
+
const raw = JSON.parse(readFileSync2(jsonFilePath, "utf8"));
|
|
1550
1951
|
if (!Array.isArray(raw) || raw.length !== 64) {
|
|
1551
1952
|
throw new Error(`${label} keypair file must be a 64-byte JSON array`);
|
|
1552
1953
|
}
|
|
@@ -1564,7 +1965,7 @@ function loadSecretKeyBytes(params) {
|
|
|
1564
1965
|
return bytes;
|
|
1565
1966
|
}
|
|
1566
1967
|
if (jsonFilePath !== void 0 && jsonFilePath.length > 0) {
|
|
1567
|
-
const raw = JSON.parse(
|
|
1968
|
+
const raw = JSON.parse(readFileSync2(jsonFilePath, "utf8"));
|
|
1568
1969
|
if (!Array.isArray(raw) || raw.length !== 64) {
|
|
1569
1970
|
throw new Error(`${label} keypair file must be a 64-byte JSON array`);
|
|
1570
1971
|
}
|
|
@@ -1587,15 +1988,29 @@ async function createSvmX402Fetch(params) {
|
|
|
1587
1988
|
const signer2 = toClientSvmSigner(
|
|
1588
1989
|
await createKeyPairSignerFromBytes2(params.agentSecretKey)
|
|
1589
1990
|
);
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1991
|
+
return (url2, init, expected) => {
|
|
1992
|
+
const wrapped = wrapFetchWithPaymentFromConfig(fetch, {
|
|
1993
|
+
schemes: [
|
|
1994
|
+
{
|
|
1995
|
+
network: expected.requirement.network,
|
|
1996
|
+
client: new ExactSvmScheme(signer2, { rpcUrl: params.rpcUrl })
|
|
1997
|
+
}
|
|
1998
|
+
],
|
|
1999
|
+
paymentRequirementsSelector: (_x402Version, requirements) => selectExpectedRequirement(requirements, expected)
|
|
2000
|
+
});
|
|
2001
|
+
return wrapped(url2, init);
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
function selectExpectedRequirement(requirements, expected) {
|
|
2005
|
+
const match = requirements.find(
|
|
2006
|
+
(candidate) => standardRequirementMatchesSelected(candidate, expected)
|
|
2007
|
+
);
|
|
2008
|
+
if (match === void 0) {
|
|
2009
|
+
throw new Error(
|
|
2010
|
+
"x402 challenge changed after preflight; refusing to pay an unchecked requirement"
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
return match;
|
|
1599
2014
|
}
|
|
1600
2015
|
|
|
1601
2016
|
// src/pay.ts
|
|
@@ -1609,9 +2024,10 @@ if (url === void 0 || !/^https?:\/\//.test(url)) {
|
|
|
1609
2024
|
fail("Usage: pay fetch <url> [maxAmountRawUsdc]");
|
|
1610
2025
|
}
|
|
1611
2026
|
var maxAmountArg = process.argv[3];
|
|
1612
|
-
var
|
|
2027
|
+
var relayerBaseUrl = process.env.SUBLY_RELAYER_URL ?? process.env.SUBLY_FACILITATOR_URL ?? "https://api.demo.sublyfi.com";
|
|
1613
2028
|
var rpcUrl = process.env.SOLANA_RPC_URL ?? "https://api.mainnet-beta.solana.com";
|
|
1614
2029
|
var defaultMaxAmountRawUsdc = process.env.SUBLY_MCP_MAX_AMOUNT_RAW_USDC === void 0 ? 10000n : BigInt(process.env.SUBLY_MCP_MAX_AMOUNT_RAW_USDC);
|
|
2030
|
+
var pendingStatePath = process.env.SUBLY_MCP_STATE_PATH ?? join2(homedir(), ".subly", "standard-x402-pending.json");
|
|
1615
2031
|
var keyPairSigner = await loadKeyPairSigner({
|
|
1616
2032
|
base58Secret: process.env.SUBLY_DEMO_AGENT_KEYPAIR,
|
|
1617
2033
|
jsonFilePath: process.env.SUBLY_DEMO_AGENT_KEYPAIR_PATH,
|
|
@@ -1625,17 +2041,18 @@ var agentSecretKey = loadSecretKeyBytes({
|
|
|
1625
2041
|
var signer = new LocalKeypairAgentWalletSigner(keyPairSigner);
|
|
1626
2042
|
var rpc = createRpc(rpcUrl);
|
|
1627
2043
|
var payer = createRelayerX402Payer({
|
|
1628
|
-
facilitatorBaseUrl,
|
|
2044
|
+
facilitatorBaseUrl: relayerBaseUrl,
|
|
1629
2045
|
signer,
|
|
1630
2046
|
rpc,
|
|
1631
2047
|
x402Fetch: await createSvmX402Fetch({ agentSecretKey, rpcUrl }),
|
|
1632
|
-
defaultMaxAmountRawUsdc
|
|
2048
|
+
defaultMaxAmountRawUsdc,
|
|
2049
|
+
stateStore: fileStandardX402StateStore(pendingStatePath)
|
|
1633
2050
|
});
|
|
1634
2051
|
var method = process.env.SUBLY_PAY_METHOD;
|
|
1635
2052
|
var body = process.env.SUBLY_PAY_BODY;
|
|
1636
2053
|
console.error(`[pay] agent ${signer.walletAddress} -> ${url}`);
|
|
1637
2054
|
try {
|
|
1638
|
-
await ensureWalletOnboarded({ facilitatorBaseUrl, signer });
|
|
2055
|
+
await ensureWalletOnboarded({ facilitatorBaseUrl: relayerBaseUrl, signer });
|
|
1639
2056
|
} catch (error) {
|
|
1640
2057
|
console.error(
|
|
1641
2058
|
`[pay] onboarding skipped: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -1646,7 +2063,8 @@ try {
|
|
|
1646
2063
|
url,
|
|
1647
2064
|
...method === void 0 ? {} : { method },
|
|
1648
2065
|
...body === void 0 ? {} : { body, headers: { "content-type": "application/json" } },
|
|
1649
|
-
...maxAmountArg === void 0 ? {} : { maxAmountRawUsdc: BigInt(maxAmountArg) }
|
|
2066
|
+
...maxAmountArg === void 0 ? {} : { maxAmountRawUsdc: BigInt(maxAmountArg) },
|
|
2067
|
+
...process.env.SUBLY_PAY_FORCE_NEW_PAYMENT === "1" ? { forceNewPayment: true } : {}
|
|
1650
2068
|
});
|
|
1651
2069
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
1652
2070
|
`);
|