@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/withdraw.js
CHANGED
|
@@ -48,10 +48,10 @@ function decodeSerializedTransaction(serializedBase64) {
|
|
|
48
48
|
}
|
|
49
49
|
async function addSignaturesToSerializedTransaction(params) {
|
|
50
50
|
const decoded = decodeSerializedTransaction(params.serializedBase64);
|
|
51
|
-
const
|
|
51
|
+
const signed = await partiallySignTransaction(params.signers, decoded);
|
|
52
52
|
return {
|
|
53
|
-
serializedBase64: getBase64EncodedWireTransaction(
|
|
54
|
-
transaction:
|
|
53
|
+
serializedBase64: getBase64EncodedWireTransaction(signed),
|
|
54
|
+
transaction: signed
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
function signatureBase58ForSigner(transaction, signer2) {
|
|
@@ -945,40 +945,6 @@ var LocalKeypairAgentWalletSigner = class {
|
|
|
945
945
|
}
|
|
946
946
|
};
|
|
947
947
|
|
|
948
|
-
// ../../src/api/wallet-auth.ts
|
|
949
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
950
|
-
import bs585 from "bs58";
|
|
951
|
-
import nacl from "tweetnacl";
|
|
952
|
-
var WALLET_AUTH_WALLET_HEADER = "x-subly-wallet";
|
|
953
|
-
var WALLET_AUTH_SIGNED_AT_HEADER = "x-subly-signed-at";
|
|
954
|
-
var WALLET_AUTH_SIGNATURE_HEADER = "x-subly-signature";
|
|
955
|
-
function sha256Hex(data) {
|
|
956
|
-
return createHash3("sha256").update(data, "utf8").digest("hex");
|
|
957
|
-
}
|
|
958
|
-
function walletAuthMessage(params) {
|
|
959
|
-
return new TextEncoder().encode(
|
|
960
|
-
`subly-api:${params.method.toUpperCase()}:${params.path}:${sha256Hex(
|
|
961
|
-
params.rawBody
|
|
962
|
-
)}:${params.signedAtMs}`
|
|
963
|
-
);
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
// ../../src/client/wallet-auth-headers.ts
|
|
967
|
-
async function walletAuthHeaders(params) {
|
|
968
|
-
const signedAtMs = String(Date.now());
|
|
969
|
-
const message = walletAuthMessage({
|
|
970
|
-
method: params.method,
|
|
971
|
-
path: new URL(params.url).pathname,
|
|
972
|
-
rawBody: params.body ?? "",
|
|
973
|
-
signedAtMs
|
|
974
|
-
});
|
|
975
|
-
return {
|
|
976
|
-
[WALLET_AUTH_WALLET_HEADER]: params.signer.walletAddress,
|
|
977
|
-
[WALLET_AUTH_SIGNED_AT_HEADER]: signedAtMs,
|
|
978
|
-
[WALLET_AUTH_SIGNATURE_HEADER]: await params.signer.signApiMessage(message)
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
|
|
982
948
|
// ../../src/client/lookup-tables.ts
|
|
983
949
|
import { fetchAllMaybeAddressLookupTable } from "@solana-program/address-lookup-table";
|
|
984
950
|
import { address, getCompiledTransactionMessageDecoder as getCompiledTransactionMessageDecoder2 } from "@solana/kit";
|
|
@@ -1019,6 +985,256 @@ async function fetchLookupTablesForTransaction(rpc2, serializedTransaction) {
|
|
|
1019
985
|
return result;
|
|
1020
986
|
}
|
|
1021
987
|
|
|
988
|
+
// ../../src/api/wallet-auth.ts
|
|
989
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
990
|
+
import bs585 from "bs58";
|
|
991
|
+
import nacl from "tweetnacl";
|
|
992
|
+
var WALLET_AUTH_WALLET_HEADER = "x-subly-wallet";
|
|
993
|
+
var WALLET_AUTH_SIGNED_AT_HEADER = "x-subly-signed-at";
|
|
994
|
+
var WALLET_AUTH_SIGNATURE_HEADER = "x-subly-signature";
|
|
995
|
+
function sha256Hex(data) {
|
|
996
|
+
return createHash3("sha256").update(data, "utf8").digest("hex");
|
|
997
|
+
}
|
|
998
|
+
function walletAuthMessage(params) {
|
|
999
|
+
return new TextEncoder().encode(
|
|
1000
|
+
`subly-api:${params.method.toUpperCase()}:${params.path}:${sha256Hex(
|
|
1001
|
+
params.rawBody
|
|
1002
|
+
)}:${params.signedAtMs}`
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// ../../src/client/wallet-auth-headers.ts
|
|
1007
|
+
async function walletAuthHeaders(params) {
|
|
1008
|
+
const signedAtMs = String(Date.now());
|
|
1009
|
+
const message = walletAuthMessage({
|
|
1010
|
+
method: params.method,
|
|
1011
|
+
path: new URL(params.url).pathname,
|
|
1012
|
+
rawBody: params.body ?? "",
|
|
1013
|
+
signedAtMs
|
|
1014
|
+
});
|
|
1015
|
+
return {
|
|
1016
|
+
[WALLET_AUTH_WALLET_HEADER]: params.signer.walletAddress,
|
|
1017
|
+
[WALLET_AUTH_SIGNED_AT_HEADER]: signedAtMs,
|
|
1018
|
+
[WALLET_AUTH_SIGNATURE_HEADER]: await params.signer.signApiMessage(message)
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// ../../src/client/vault-flows.ts
|
|
1023
|
+
var VaultFlowClientError = class extends Error {
|
|
1024
|
+
constructor(step, message, detail = null) {
|
|
1025
|
+
super(message);
|
|
1026
|
+
this.step = step;
|
|
1027
|
+
this.detail = detail;
|
|
1028
|
+
this.name = "VaultFlowClientError";
|
|
1029
|
+
}
|
|
1030
|
+
step;
|
|
1031
|
+
detail;
|
|
1032
|
+
};
|
|
1033
|
+
var VaultFlowClient = class {
|
|
1034
|
+
baseUrl;
|
|
1035
|
+
signer;
|
|
1036
|
+
fetchImpl;
|
|
1037
|
+
lookupTablesFor;
|
|
1038
|
+
pollTimeoutMs;
|
|
1039
|
+
pollIntervalMs;
|
|
1040
|
+
constructor(config) {
|
|
1041
|
+
this.baseUrl = config.facilitatorBaseUrl.replace(/\/$/, "");
|
|
1042
|
+
this.signer = config.signer;
|
|
1043
|
+
this.fetchImpl = config.fetchImpl ?? fetch;
|
|
1044
|
+
this.lookupTablesFor = config.lookupTablesFor ?? ((serializedTransaction) => fetchLookupTablesForTransaction(config.rpc, serializedTransaction));
|
|
1045
|
+
this.pollTimeoutMs = config.pollTimeoutMs ?? 9e4;
|
|
1046
|
+
this.pollIntervalMs = config.pollIntervalMs ?? 2500;
|
|
1047
|
+
}
|
|
1048
|
+
/** Moves USDC from the agent wallet into the vault (fee sponsored). */
|
|
1049
|
+
async deposit(input) {
|
|
1050
|
+
const prepared = await this.postJson("prepare", "/v1/deposits/prepare", {
|
|
1051
|
+
wallet: this.signer.walletAddress,
|
|
1052
|
+
amountRawUsdc: input.amountRawUsdc.toString()
|
|
1053
|
+
});
|
|
1054
|
+
const signed = await this.signer.signDeposit({
|
|
1055
|
+
intent: prepared.signingIntent,
|
|
1056
|
+
serializedTransaction: prepared.serializedTransaction,
|
|
1057
|
+
lookupTables: await this.lookupTablesFor(prepared.serializedTransaction)
|
|
1058
|
+
});
|
|
1059
|
+
let outcome = await this.postJson("submit", "/v1/deposits/submit", {
|
|
1060
|
+
depositId: prepared.depositId,
|
|
1061
|
+
serializedTransaction: signed.serializedTransaction,
|
|
1062
|
+
agentSignature: signed.agentSignature
|
|
1063
|
+
});
|
|
1064
|
+
if (outcome.status === "submitted") {
|
|
1065
|
+
outcome = await this.pollUntilTerminal(
|
|
1066
|
+
`/v1/deposits/${prepared.depositId}`,
|
|
1067
|
+
outcome
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
return {
|
|
1071
|
+
depositId: prepared.depositId,
|
|
1072
|
+
status: outcome.status,
|
|
1073
|
+
txSignature: outcome.txSignature ?? null,
|
|
1074
|
+
actualDepositRawUsdc: outcome.actualDepositRawUsdc ?? null,
|
|
1075
|
+
sharesMintedRaw: outcome.sharesMintedRaw ?? null,
|
|
1076
|
+
errorCode: outcome.errorCode ?? null
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Moves USDC from the vault back to the agent wallet's USDC ATA (fee
|
|
1081
|
+
* sponsored). A plain withdrawal is the exit path and MAY spend principal;
|
|
1082
|
+
* with purpose "yield_realize" the relayer refuses anything beyond the
|
|
1083
|
+
* spendable yield (the payment path, via RelayerYieldRealizer).
|
|
1084
|
+
*/
|
|
1085
|
+
async withdraw(input) {
|
|
1086
|
+
const prepared = await this.postJson(
|
|
1087
|
+
"prepare",
|
|
1088
|
+
"/v1/withdrawals/prepare",
|
|
1089
|
+
{
|
|
1090
|
+
wallet: this.signer.walletAddress,
|
|
1091
|
+
amountRawUsdc: input.amountRawUsdc.toString(),
|
|
1092
|
+
...input.purpose === void 0 ? {} : { purpose: input.purpose }
|
|
1093
|
+
}
|
|
1094
|
+
);
|
|
1095
|
+
const signed = await this.signer.signWithdrawal({
|
|
1096
|
+
intent: prepared.signingIntent,
|
|
1097
|
+
serializedTransaction: prepared.serializedTransaction,
|
|
1098
|
+
lookupTables: await this.lookupTablesFor(prepared.serializedTransaction)
|
|
1099
|
+
});
|
|
1100
|
+
let outcome = await this.postJson("submit", "/v1/withdrawals/submit", {
|
|
1101
|
+
withdrawalId: prepared.withdrawalId,
|
|
1102
|
+
serializedTransaction: signed.serializedTransaction,
|
|
1103
|
+
agentSignature: signed.agentSignature
|
|
1104
|
+
});
|
|
1105
|
+
if (outcome.status === "submitted") {
|
|
1106
|
+
outcome = await this.pollUntilTerminal(
|
|
1107
|
+
`/v1/withdrawals/${prepared.withdrawalId}`,
|
|
1108
|
+
outcome
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
return {
|
|
1112
|
+
withdrawalId: prepared.withdrawalId,
|
|
1113
|
+
status: outcome.status,
|
|
1114
|
+
txSignature: outcome.txSignature ?? null,
|
|
1115
|
+
destinationUsdcAta: prepared.destinationUsdcAta,
|
|
1116
|
+
actualWithdrawRawUsdc: outcome.actualWithdrawRawUsdc ?? null,
|
|
1117
|
+
actualSharesBurnedRaw: outcome.actualSharesBurnedRaw ?? null,
|
|
1118
|
+
errorCode: outcome.errorCode ?? null
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Reads the yield budget. Syncs the relayer's ledger from chain first (so
|
|
1123
|
+
* yield accrued since the last sync shows up); the sync is best-effort and
|
|
1124
|
+
* on failure the last-synced view is returned.
|
|
1125
|
+
*/
|
|
1126
|
+
async getBudget(options = {}) {
|
|
1127
|
+
if (options.refreshFromChain !== false) {
|
|
1128
|
+
try {
|
|
1129
|
+
await this.postJson(
|
|
1130
|
+
"sync",
|
|
1131
|
+
`/v1/wallets/${this.signer.walletAddress}/sync`,
|
|
1132
|
+
{ source: "chain" }
|
|
1133
|
+
);
|
|
1134
|
+
} catch {
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
const url = `${this.baseUrl}/v1/wallets/${this.signer.walletAddress}/budget`;
|
|
1138
|
+
const response = await this.fetchImpl(url, {
|
|
1139
|
+
headers: await walletAuthHeaders({
|
|
1140
|
+
signer: this.signer,
|
|
1141
|
+
method: "GET",
|
|
1142
|
+
url
|
|
1143
|
+
})
|
|
1144
|
+
});
|
|
1145
|
+
const text = await response.text();
|
|
1146
|
+
if (response.status !== 200) {
|
|
1147
|
+
throw new VaultFlowClientError(
|
|
1148
|
+
"budget",
|
|
1149
|
+
`budget endpoint returned ${response.status}: ${text}`
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
let parsed;
|
|
1153
|
+
try {
|
|
1154
|
+
parsed = JSON.parse(text);
|
|
1155
|
+
} catch {
|
|
1156
|
+
throw new VaultFlowClientError(
|
|
1157
|
+
"budget",
|
|
1158
|
+
"budget endpoint returned 200 with a non-JSON body",
|
|
1159
|
+
text
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
const body = parsed;
|
|
1163
|
+
return {
|
|
1164
|
+
wallet: this.signer.walletAddress,
|
|
1165
|
+
principalBasisRawUsdc: body.position?.principalBasisRawUsdc ?? "0",
|
|
1166
|
+
positionValueRawUsdc: body.budget?.positionValueRawUsdc ?? "0",
|
|
1167
|
+
grossYieldRawUsdc: body.budget?.grossYieldRawUsdc ?? "0",
|
|
1168
|
+
spendableYieldRawUsdc: body.budget?.spendableYieldRawUsdc ?? "0"
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Polls the reconciling GET endpoint until the intent leaves "submitted"
|
|
1173
|
+
* (each read looks the tx up on-chain) or the timeout elapses.
|
|
1174
|
+
*/
|
|
1175
|
+
async pollUntilTerminal(path, last) {
|
|
1176
|
+
const deadline = Date.now() + this.pollTimeoutMs;
|
|
1177
|
+
let latest = last;
|
|
1178
|
+
while (Date.now() < deadline) {
|
|
1179
|
+
await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
|
|
1180
|
+
const url = `${this.baseUrl}${path}`;
|
|
1181
|
+
const response = await this.fetchImpl(url, {
|
|
1182
|
+
headers: await walletAuthHeaders({
|
|
1183
|
+
signer: this.signer,
|
|
1184
|
+
method: "GET",
|
|
1185
|
+
url
|
|
1186
|
+
})
|
|
1187
|
+
});
|
|
1188
|
+
if (response.status !== 200) {
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
try {
|
|
1192
|
+
latest = await response.json();
|
|
1193
|
+
} catch {
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
if (latest.status !== "submitted") {
|
|
1197
|
+
return latest;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return latest;
|
|
1201
|
+
}
|
|
1202
|
+
async postJson(step, path, body) {
|
|
1203
|
+
const url = `${this.baseUrl}${path}`;
|
|
1204
|
+
const serialized = JSON.stringify(body);
|
|
1205
|
+
const response = await this.fetchImpl(url, {
|
|
1206
|
+
method: "POST",
|
|
1207
|
+
headers: {
|
|
1208
|
+
...await walletAuthHeaders({
|
|
1209
|
+
signer: this.signer,
|
|
1210
|
+
method: "POST",
|
|
1211
|
+
url,
|
|
1212
|
+
body: serialized
|
|
1213
|
+
}),
|
|
1214
|
+
"content-type": "application/json"
|
|
1215
|
+
},
|
|
1216
|
+
body: serialized
|
|
1217
|
+
});
|
|
1218
|
+
const text = await response.text();
|
|
1219
|
+
if (response.status !== 200) {
|
|
1220
|
+
throw new VaultFlowClientError(
|
|
1221
|
+
step,
|
|
1222
|
+
`${path} failed with ${response.status}: ${text}`,
|
|
1223
|
+
text
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
try {
|
|
1227
|
+
return JSON.parse(text);
|
|
1228
|
+
} catch {
|
|
1229
|
+
throw new VaultFlowClientError(
|
|
1230
|
+
step,
|
|
1231
|
+
`${path} returned 200 with a non-JSON body`,
|
|
1232
|
+
text
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
|
|
1022
1238
|
// ../../src/solana/keys.ts
|
|
1023
1239
|
import { readFileSync } from "node:fs";
|
|
1024
1240
|
import bs586 from "bs58";
|
|
@@ -1063,7 +1279,7 @@ function formatRawUsdc(raw) {
|
|
|
1063
1279
|
}
|
|
1064
1280
|
|
|
1065
1281
|
// ../../demo/withdraw.ts
|
|
1066
|
-
var
|
|
1282
|
+
var relayerBaseUrl = process.env.SUBLY_RELAYER_URL ?? process.env.SUBLY_FACILITATOR_URL ?? "https://api.demo.sublyfi.com";
|
|
1067
1283
|
var amountRawUsdc = process.argv[2];
|
|
1068
1284
|
if (amountRawUsdc === void 0 || !/^[1-9]\d*$/.test(amountRawUsdc)) {
|
|
1069
1285
|
fail(
|
|
@@ -1079,67 +1295,44 @@ var signer = new LocalKeypairAgentWalletSigner(keyPairSigner);
|
|
|
1079
1295
|
var rpc = createRpc(
|
|
1080
1296
|
process.env.SOLANA_RPC_URL ?? "https://api.mainnet-beta.solana.com"
|
|
1081
1297
|
);
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
headers: {
|
|
1088
|
-
...await walletAuthHeaders({
|
|
1089
|
-
signer,
|
|
1090
|
-
method: "POST",
|
|
1091
|
-
url,
|
|
1092
|
-
body: serialized
|
|
1093
|
-
}),
|
|
1094
|
-
"content-type": "application/json"
|
|
1095
|
-
},
|
|
1096
|
-
body: serialized
|
|
1097
|
-
});
|
|
1098
|
-
const text = await response.text();
|
|
1099
|
-
if (response.status !== 200) {
|
|
1100
|
-
fail(`[withdraw] ${path} failed with ${response.status}: ${text}`);
|
|
1101
|
-
}
|
|
1102
|
-
return JSON.parse(text);
|
|
1103
|
-
}
|
|
1298
|
+
var vaultFlows = new VaultFlowClient({
|
|
1299
|
+
facilitatorBaseUrl: relayerBaseUrl,
|
|
1300
|
+
signer,
|
|
1301
|
+
rpc
|
|
1302
|
+
});
|
|
1104
1303
|
console.log(`[withdraw] agent wallet: ${signer.walletAddress}`);
|
|
1105
|
-
console.log(`[withdraw]
|
|
1304
|
+
console.log(`[withdraw] relayer: ${relayerBaseUrl}`);
|
|
1106
1305
|
console.log(
|
|
1107
1306
|
`
|
|
1108
|
-
[withdraw] step 1:
|
|
1307
|
+
[withdraw] step 1: withdraw ${formatRawUsdc(amountRawUsdc)} USDC (prepare -> validate intent + sign locally -> sponsor submits)`
|
|
1109
1308
|
);
|
|
1110
|
-
var
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
lookupTables: await fetchLookupTablesForTransaction(
|
|
1123
|
-
rpc,
|
|
1124
|
-
prepared.serializedTransaction
|
|
1125
|
-
)
|
|
1126
|
-
});
|
|
1127
|
-
console.log("[withdraw] agent signature attached");
|
|
1128
|
-
console.log("\n[withdraw] step 3: submit (sponsor co-signs and broadcasts)");
|
|
1129
|
-
var submitted = await postJson("/v1/withdrawals/submit", {
|
|
1130
|
-
withdrawalId: prepared.withdrawalId,
|
|
1131
|
-
serializedTransaction: signed.serializedTransaction,
|
|
1132
|
-
agentSignature: signed.agentSignature
|
|
1133
|
-
});
|
|
1309
|
+
var submitted;
|
|
1310
|
+
try {
|
|
1311
|
+
submitted = await vaultFlows.withdraw({
|
|
1312
|
+
amountRawUsdc: BigInt(amountRawUsdc)
|
|
1313
|
+
});
|
|
1314
|
+
} catch (error) {
|
|
1315
|
+
if (error instanceof VaultFlowClientError) {
|
|
1316
|
+
fail(`[withdraw] ${error.step} failed: ${error.message}`);
|
|
1317
|
+
}
|
|
1318
|
+
throw error;
|
|
1319
|
+
}
|
|
1320
|
+
console.log(`[withdraw] withdrawalId: ${submitted.withdrawalId}`);
|
|
1134
1321
|
console.log(`[withdraw] status: ${submitted.status}`);
|
|
1322
|
+
console.log(`[withdraw] destination USDC ATA: ${submitted.destinationUsdcAta}`);
|
|
1135
1323
|
if (submitted.txSignature !== null) {
|
|
1136
1324
|
console.log(
|
|
1137
1325
|
`[withdraw] transaction: https://solscan.io/tx/${submitted.txSignature}`
|
|
1138
1326
|
);
|
|
1139
1327
|
}
|
|
1328
|
+
if (submitted.status === "submitted") {
|
|
1329
|
+
fail(
|
|
1330
|
+
`[withdraw] broadcast but not yet confirmed \u2014 it may still land. Do NOT resubmit; check GET /v1/withdrawals/${submitted.withdrawalId} (or the tx link above) first`
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1140
1333
|
if (submitted.status !== "confirmed") {
|
|
1141
1334
|
fail(
|
|
1142
|
-
`[withdraw] not confirmed (errorCode=${submitted.errorCode}); check GET /v1/withdrawals/${
|
|
1335
|
+
`[withdraw] not confirmed (errorCode=${submitted.errorCode}); check GET /v1/withdrawals/${submitted.withdrawalId} and the relayer logs`
|
|
1143
1336
|
);
|
|
1144
1337
|
}
|
|
1145
1338
|
console.log(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@subly_fi/pay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
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).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|