@subly_fi/pay 0.7.3 → 0.8.1
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 +195 -46
- package/dist/budget.js +84 -2
- package/dist/cli.js +10 -6
- package/dist/deposit.js +94 -5
- package/dist/mcp-server.js +1548 -1238
- package/dist/pay.js +1390 -1119
- package/dist/setup-link.js +92 -3
- package/dist/setup-status.js +8 -1
- package/dist/status.js +2366 -0
- package/dist/withdraw.js +95 -6
- package/package.json +1 -1
package/dist/deposit.js
CHANGED
|
@@ -1886,6 +1886,18 @@ async function assertWithdrawalPreview(input) {
|
|
|
1886
1886
|
}
|
|
1887
1887
|
}
|
|
1888
1888
|
|
|
1889
|
+
// ../../src/lib/canonical-json.ts
|
|
1890
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
1891
|
+
function canonicalJson2(value) {
|
|
1892
|
+
return stableStringify(value);
|
|
1893
|
+
}
|
|
1894
|
+
function sha256HexOf(data) {
|
|
1895
|
+
return createHash4("sha256").update(data, "utf8").digest("hex");
|
|
1896
|
+
}
|
|
1897
|
+
function canonicalJsonHash(value) {
|
|
1898
|
+
return sha256HexOf(canonicalJson2(value));
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1889
1901
|
// ../../src/client/lookup-tables.ts
|
|
1890
1902
|
import { fetchAllMaybeAddressLookupTable } from "@solana-program/address-lookup-table";
|
|
1891
1903
|
import { address, getCompiledTransactionMessageDecoder as getCompiledTransactionMessageDecoder2 } from "@solana/kit";
|
|
@@ -1941,6 +1953,11 @@ var VaultFlowClientError = class extends Error {
|
|
|
1941
1953
|
code;
|
|
1942
1954
|
errorDetails;
|
|
1943
1955
|
};
|
|
1956
|
+
function vaultOperationKind(intentId) {
|
|
1957
|
+
if (/^dep_[0-9a-f]{32}$/.test(intentId)) return "deposit";
|
|
1958
|
+
if (/^wdr_[0-9a-f]{32}$/.test(intentId)) return "withdrawal";
|
|
1959
|
+
throw new VaultFlowClientError("read", "intentId must be the original dep_ or wdr_ ID followed by 32 lowercase hexadecimal characters");
|
|
1960
|
+
}
|
|
1944
1961
|
var VaultFlowClient = class {
|
|
1945
1962
|
vault;
|
|
1946
1963
|
rpc;
|
|
@@ -2043,9 +2060,36 @@ var VaultFlowClient = class {
|
|
|
2043
2060
|
...input.approvalId === void 0 ? {} : { approvalId: input.approvalId }
|
|
2044
2061
|
}
|
|
2045
2062
|
);
|
|
2063
|
+
this.assertPreparedWithdrawal(prepared, input);
|
|
2064
|
+
await input.onPrepared?.(prepared);
|
|
2065
|
+
return this.submitPreparedWithdrawal(prepared, input);
|
|
2066
|
+
}
|
|
2067
|
+
/** Reconcile or submit the original intent; never prepare a replacement. */
|
|
2068
|
+
async resumeWithdrawal(prepared, input) {
|
|
2069
|
+
this.assertPreparedWithdrawal(prepared, input);
|
|
2070
|
+
const current = await this.getJson(
|
|
2071
|
+
`/v1/withdrawals/${encodeURIComponent(prepared.withdrawalId)}`
|
|
2072
|
+
);
|
|
2073
|
+
if (current.withdrawalId !== prepared.withdrawalId || current.wallet !== this.signer.walletAddress || current.vault !== this.vault.address || current.requestedWithdrawRawUsdc !== input.amountRawUsdc.toString() || current.purpose !== (input.purpose ?? "normal") || canonicalJsonHash(current.paymentBinding ?? null) !== canonicalJsonHash(input.payment ?? null) || current.serializedTransaction !== prepared.serializedTransaction || current.preparedMessageHash !== prepared.signingIntent.preparedMessageHash || current.destinationUsdcAta !== prepared.destinationUsdcAta) {
|
|
2074
|
+
throw new VaultFlowClientError("read", "Saved withdrawal differs from the original operation; refusing to resume");
|
|
2075
|
+
}
|
|
2076
|
+
if (current.status === "prepared") {
|
|
2077
|
+
return this.submitPreparedWithdrawal(prepared, input);
|
|
2078
|
+
}
|
|
2079
|
+
if (!["submitted", "confirmed", "failed", "failed_not_submitted", "expired", "quarantined"].includes(current.status)) {
|
|
2080
|
+
throw new VaultFlowClientError("read", "Relayer returned an unknown withdrawal status");
|
|
2081
|
+
}
|
|
2082
|
+
return this.withdrawalOutcome(prepared, current);
|
|
2083
|
+
}
|
|
2084
|
+
assertPreparedWithdrawal(prepared, input) {
|
|
2046
2085
|
if (prepared.signingIntent?.wallet !== this.signer.walletAddress || prepared.signingIntent.vault !== this.vault.address || prepared.requestedWithdrawRawUsdc !== input.amountRawUsdc.toString() || prepared.purpose !== (input.purpose ?? "normal") || input.purpose === "yield_realize" && prepared.signingIntent.allowFullExit) {
|
|
2047
2086
|
throw new VaultFlowClientError("prepare", "Prepared withdrawal differs from the requested operation");
|
|
2048
2087
|
}
|
|
2088
|
+
if (typeof prepared.withdrawalId !== "string" || prepared.withdrawalId.length === 0) {
|
|
2089
|
+
throw new VaultFlowClientError("prepare", "Prepared withdrawal has no withdrawal ID");
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
async submitPreparedWithdrawal(prepared, input) {
|
|
2049
2093
|
await assertWithdrawalPreview({
|
|
2050
2094
|
rpc: this.rpc,
|
|
2051
2095
|
serializedTransaction: prepared.serializedTransaction,
|
|
@@ -2059,6 +2103,7 @@ var VaultFlowClient = class {
|
|
|
2059
2103
|
serializedTransaction: prepared.serializedTransaction,
|
|
2060
2104
|
lookupTables: await this.lookupTablesFor(prepared.serializedTransaction)
|
|
2061
2105
|
});
|
|
2106
|
+
input.onBeforeSubmit?.();
|
|
2062
2107
|
let outcome = await this.postJson("submit", "/v1/withdrawals/submit", {
|
|
2063
2108
|
withdrawalId: prepared.withdrawalId,
|
|
2064
2109
|
serializedTransaction: signed.serializedTransaction,
|
|
@@ -2070,6 +2115,9 @@ var VaultFlowClient = class {
|
|
|
2070
2115
|
outcome
|
|
2071
2116
|
);
|
|
2072
2117
|
}
|
|
2118
|
+
return this.withdrawalOutcome(prepared, outcome);
|
|
2119
|
+
}
|
|
2120
|
+
withdrawalOutcome(prepared, outcome) {
|
|
2073
2121
|
return {
|
|
2074
2122
|
withdrawalId: prepared.withdrawalId,
|
|
2075
2123
|
status: outcome.status,
|
|
@@ -2080,6 +2128,40 @@ var VaultFlowClient = class {
|
|
|
2080
2128
|
errorCode: outcome.errorCode ?? null
|
|
2081
2129
|
};
|
|
2082
2130
|
}
|
|
2131
|
+
/** Authenticated read/reconciliation only: never prepare, sign or submit a transaction. */
|
|
2132
|
+
async getOperationStatus(intentId) {
|
|
2133
|
+
const kind = vaultOperationKind(intentId);
|
|
2134
|
+
const raw = await this.getJson(`/v1/${kind === "deposit" ? "deposits" : "withdrawals"}/${intentId}?resubmit=false`);
|
|
2135
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
2136
|
+
throw new VaultFlowClientError("read", "Relayer returned an invalid operation status");
|
|
2137
|
+
}
|
|
2138
|
+
const record = raw;
|
|
2139
|
+
if (record[kind === "deposit" ? "depositId" : "withdrawalId"] !== intentId || record.wallet !== this.signer.walletAddress || record.vault !== this.vault.address) {
|
|
2140
|
+
throw new VaultFlowClientError("read", "Operation does not match the requested ID, current wallet or selected vault; use the original wallet, vault and relayer");
|
|
2141
|
+
}
|
|
2142
|
+
const requested = record[kind === "deposit" ? "amountRawUsdc" : "requestedWithdrawRawUsdc"];
|
|
2143
|
+
const actual = record[kind === "deposit" ? "actualDepositRawUsdc" : "actualWithdrawRawUsdc"];
|
|
2144
|
+
if (typeof record.status !== "string" || !["prepared", "submitted", "confirmed", "failed", "expired", "failed_not_submitted"].includes(record.status) || typeof requested !== "string" || !/^\d+$/.test(requested) || actual !== null && (typeof actual !== "string" || !/^\d+$/.test(actual)) || record.txSignature !== null && (typeof record.txSignature !== "string" || record.txSignature.length === 0) || record.errorCode !== null && typeof record.errorCode !== "string" || record.status === "confirmed" && (actual === null || record.txSignature === null)) {
|
|
2145
|
+
throw new VaultFlowClientError("read", "Relayer returned incomplete or invalid operation status fields");
|
|
2146
|
+
}
|
|
2147
|
+
const status = record.status;
|
|
2148
|
+
const nextAction = status === "confirmed" ? "done" : status === "submitted" || status === "prepared" ? "check_again" : "reconcile_with_operator";
|
|
2149
|
+
const message = status === "confirmed" ? `The original ${kind} is confirmed.` : status === "submitted" ? "The original transaction is still confirming. Check this same intent ID again; do not repeat the deposit or withdrawal." : status === "prepared" ? "The original intent is prepared. This status check does not submit it. Check the same ID again or ask the operator to reconcile it before starting another operation." : "The original operation ended without a confirmed result. Reconcile its intent ID and transaction with the operator before starting another operation.";
|
|
2150
|
+
return {
|
|
2151
|
+
intentId,
|
|
2152
|
+
kind,
|
|
2153
|
+
wallet: this.signer.walletAddress,
|
|
2154
|
+
vault: this.vault.address,
|
|
2155
|
+
status,
|
|
2156
|
+
requestedAmountRawUsdc: requested,
|
|
2157
|
+
actualAmountRawUsdc: actual,
|
|
2158
|
+
txSignature: record.txSignature,
|
|
2159
|
+
errorCode: record.errorCode,
|
|
2160
|
+
stillConfirming: status === "submitted",
|
|
2161
|
+
nextAction,
|
|
2162
|
+
message
|
|
2163
|
+
};
|
|
2164
|
+
}
|
|
2083
2165
|
/**
|
|
2084
2166
|
* Reads the yield budget. Syncs the relayer's ledger from chain first (so
|
|
2085
2167
|
* yield accrued since the last sync shows up); the sync is best-effort and
|
|
@@ -2333,12 +2415,19 @@ function formatRawUsdc(raw) {
|
|
|
2333
2415
|
return `${whole}.${fraction}`;
|
|
2334
2416
|
}
|
|
2335
2417
|
|
|
2418
|
+
// ../../demo/cli-command.ts
|
|
2419
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
2420
|
+
var { version } = JSON.parse(
|
|
2421
|
+
readFileSync3(new URL("../package.json", import.meta.url), "utf8")
|
|
2422
|
+
);
|
|
2423
|
+
var PAY_COMMAND = `npx -y @subly_fi/pay@${version}`;
|
|
2424
|
+
|
|
2336
2425
|
// ../../demo/deposit.ts
|
|
2337
2426
|
var relayerBaseUrl = process.env.SUBLY_RELAYER_URL ?? process.env.SUBLY_FACILITATOR_URL ?? "https://api.demo.sublyfi.com";
|
|
2338
2427
|
var amountRawUsdc = process.argv[2];
|
|
2339
2428
|
if (amountRawUsdc === void 0 || !/^[1-9]\d*$/.test(amountRawUsdc)) {
|
|
2340
2429
|
fail(
|
|
2341
|
-
|
|
2430
|
+
`Usage: ${PAY_COMMAND} deposit <amountRawUsdc> [apr_<approvalId>] (positive integer, e.g. 60000000 = 60 USDC)`
|
|
2342
2431
|
);
|
|
2343
2432
|
}
|
|
2344
2433
|
var approvalId = process.argv[3];
|
|
@@ -2381,7 +2470,7 @@ try {
|
|
|
2381
2470
|
approvalId: details.approvalId ?? null,
|
|
2382
2471
|
approveUrl: details.approveUrl ?? null,
|
|
2383
2472
|
expiresAtMs: details.expiresAtMs ?? null,
|
|
2384
|
-
message: `This deposit needs the owner's approval. Paste approveUrl to the user; once they approve (Face ID / wallet sign), retry:
|
|
2473
|
+
message: `This deposit needs the owner's approval. Paste approveUrl to the user; once they approve (Face ID / wallet sign), retry: ${PAY_COMMAND} deposit ${amountRawUsdc} ${details.approvalId ?? "<approvalId>"}`
|
|
2385
2474
|
},
|
|
2386
2475
|
null,
|
|
2387
2476
|
2
|
|
@@ -2396,7 +2485,7 @@ try {
|
|
|
2396
2485
|
{
|
|
2397
2486
|
ok: false,
|
|
2398
2487
|
setupRequired: true,
|
|
2399
|
-
message: `Deposits require a registered owner. Create a setup link (
|
|
2488
|
+
message: `Deposits require a registered owner. Create a setup link (${PAY_COMMAND} setup-link --initial-deposit ${amountRawUsdc}), paste the setupUrl to the user, wait for their Face ID, then deposit again \u2014 the first deposit is pre-approved.`
|
|
2400
2489
|
},
|
|
2401
2490
|
null,
|
|
2402
2491
|
2
|
|
@@ -2418,12 +2507,12 @@ if (submitted.txSignature !== null) {
|
|
|
2418
2507
|
}
|
|
2419
2508
|
if (submitted.status === "submitted") {
|
|
2420
2509
|
fail(
|
|
2421
|
-
`[deposit] broadcast but not yet confirmed \u2014 it may still land. Do NOT resubmit;
|
|
2510
|
+
`[deposit] broadcast but not yet confirmed \u2014 it may still land. Do NOT resubmit; run ${PAY_COMMAND} status ${submitted.depositId} with the same wallet, vault and relayer first`
|
|
2422
2511
|
);
|
|
2423
2512
|
}
|
|
2424
2513
|
if (submitted.status !== "confirmed") {
|
|
2425
2514
|
fail(
|
|
2426
|
-
`[deposit] not confirmed (errorCode=${submitted.errorCode});
|
|
2515
|
+
`[deposit] not confirmed (errorCode=${submitted.errorCode}); run ${PAY_COMMAND} status ${submitted.depositId} and ask the operator to reconcile it`
|
|
2427
2516
|
);
|
|
2428
2517
|
}
|
|
2429
2518
|
console.log(
|