@subly_fi/pay 0.7.3 → 0.8.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 +19 -13
- package/dist/budget.js +84 -2
- package/dist/cli.js +2 -1
- package/dist/deposit.js +84 -2
- package/dist/mcp-server.js +1548 -1238
- package/dist/pay.js +1381 -1117
- package/dist/setup-link.js +84 -2
- package/dist/status.js +2359 -0
- package/dist/withdraw.js +86 -4
- package/package.json +1 -1
package/dist/setup-link.js
CHANGED
|
@@ -1790,6 +1790,18 @@ async function assertWithdrawalPreview(input) {
|
|
|
1790
1790
|
}
|
|
1791
1791
|
}
|
|
1792
1792
|
|
|
1793
|
+
// ../../src/lib/canonical-json.ts
|
|
1794
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1795
|
+
function canonicalJson2(value) {
|
|
1796
|
+
return stableStringify(value);
|
|
1797
|
+
}
|
|
1798
|
+
function sha256HexOf(data) {
|
|
1799
|
+
return createHash3("sha256").update(data, "utf8").digest("hex");
|
|
1800
|
+
}
|
|
1801
|
+
function canonicalJsonHash(value) {
|
|
1802
|
+
return sha256HexOf(canonicalJson2(value));
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1793
1805
|
// ../../src/client/lookup-tables.ts
|
|
1794
1806
|
import { fetchAllMaybeAddressLookupTable } from "@solana-program/address-lookup-table";
|
|
1795
1807
|
import { address, getCompiledTransactionMessageDecoder as getCompiledTransactionMessageDecoder2 } from "@solana/kit";
|
|
@@ -1831,14 +1843,14 @@ async function fetchLookupTablesForTransaction(rpc, serializedTransaction) {
|
|
|
1831
1843
|
}
|
|
1832
1844
|
|
|
1833
1845
|
// ../../src/api/wallet-auth.ts
|
|
1834
|
-
import { createHash as
|
|
1846
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
1835
1847
|
import bs587 from "bs58";
|
|
1836
1848
|
import nacl3 from "tweetnacl";
|
|
1837
1849
|
var WALLET_AUTH_WALLET_HEADER = "x-subly-wallet";
|
|
1838
1850
|
var WALLET_AUTH_SIGNED_AT_HEADER = "x-subly-signed-at";
|
|
1839
1851
|
var WALLET_AUTH_SIGNATURE_HEADER = "x-subly-signature";
|
|
1840
1852
|
function sha256Hex(data) {
|
|
1841
|
-
return
|
|
1853
|
+
return createHash4("sha256").update(data, "utf8").digest("hex");
|
|
1842
1854
|
}
|
|
1843
1855
|
function walletAuthMessage(params) {
|
|
1844
1856
|
return new TextEncoder().encode(
|
|
@@ -1882,6 +1894,11 @@ var VaultFlowClientError = class extends Error {
|
|
|
1882
1894
|
code;
|
|
1883
1895
|
errorDetails;
|
|
1884
1896
|
};
|
|
1897
|
+
function vaultOperationKind(intentId) {
|
|
1898
|
+
if (/^dep_[0-9a-f]{32}$/.test(intentId)) return "deposit";
|
|
1899
|
+
if (/^wdr_[0-9a-f]{32}$/.test(intentId)) return "withdrawal";
|
|
1900
|
+
throw new VaultFlowClientError("read", "intentId must be the original dep_ or wdr_ ID followed by 32 lowercase hexadecimal characters");
|
|
1901
|
+
}
|
|
1885
1902
|
var VaultFlowClient = class {
|
|
1886
1903
|
vault;
|
|
1887
1904
|
rpc;
|
|
@@ -1984,9 +2001,36 @@ var VaultFlowClient = class {
|
|
|
1984
2001
|
...input.approvalId === void 0 ? {} : { approvalId: input.approvalId }
|
|
1985
2002
|
}
|
|
1986
2003
|
);
|
|
2004
|
+
this.assertPreparedWithdrawal(prepared, input);
|
|
2005
|
+
await input.onPrepared?.(prepared);
|
|
2006
|
+
return this.submitPreparedWithdrawal(prepared, input);
|
|
2007
|
+
}
|
|
2008
|
+
/** Reconcile or submit the original intent; never prepare a replacement. */
|
|
2009
|
+
async resumeWithdrawal(prepared, input) {
|
|
2010
|
+
this.assertPreparedWithdrawal(prepared, input);
|
|
2011
|
+
const current = await this.getJson(
|
|
2012
|
+
`/v1/withdrawals/${encodeURIComponent(prepared.withdrawalId)}`
|
|
2013
|
+
);
|
|
2014
|
+
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) {
|
|
2015
|
+
throw new VaultFlowClientError("read", "Saved withdrawal differs from the original operation; refusing to resume");
|
|
2016
|
+
}
|
|
2017
|
+
if (current.status === "prepared") {
|
|
2018
|
+
return this.submitPreparedWithdrawal(prepared, input);
|
|
2019
|
+
}
|
|
2020
|
+
if (!["submitted", "confirmed", "failed", "failed_not_submitted", "expired", "quarantined"].includes(current.status)) {
|
|
2021
|
+
throw new VaultFlowClientError("read", "Relayer returned an unknown withdrawal status");
|
|
2022
|
+
}
|
|
2023
|
+
return this.withdrawalOutcome(prepared, current);
|
|
2024
|
+
}
|
|
2025
|
+
assertPreparedWithdrawal(prepared, input) {
|
|
1987
2026
|
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) {
|
|
1988
2027
|
throw new VaultFlowClientError("prepare", "Prepared withdrawal differs from the requested operation");
|
|
1989
2028
|
}
|
|
2029
|
+
if (typeof prepared.withdrawalId !== "string" || prepared.withdrawalId.length === 0) {
|
|
2030
|
+
throw new VaultFlowClientError("prepare", "Prepared withdrawal has no withdrawal ID");
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
async submitPreparedWithdrawal(prepared, input) {
|
|
1990
2034
|
await assertWithdrawalPreview({
|
|
1991
2035
|
rpc: this.rpc,
|
|
1992
2036
|
serializedTransaction: prepared.serializedTransaction,
|
|
@@ -2000,6 +2044,7 @@ var VaultFlowClient = class {
|
|
|
2000
2044
|
serializedTransaction: prepared.serializedTransaction,
|
|
2001
2045
|
lookupTables: await this.lookupTablesFor(prepared.serializedTransaction)
|
|
2002
2046
|
});
|
|
2047
|
+
input.onBeforeSubmit?.();
|
|
2003
2048
|
let outcome = await this.postJson("submit", "/v1/withdrawals/submit", {
|
|
2004
2049
|
withdrawalId: prepared.withdrawalId,
|
|
2005
2050
|
serializedTransaction: signed.serializedTransaction,
|
|
@@ -2011,6 +2056,9 @@ var VaultFlowClient = class {
|
|
|
2011
2056
|
outcome
|
|
2012
2057
|
);
|
|
2013
2058
|
}
|
|
2059
|
+
return this.withdrawalOutcome(prepared, outcome);
|
|
2060
|
+
}
|
|
2061
|
+
withdrawalOutcome(prepared, outcome) {
|
|
2014
2062
|
return {
|
|
2015
2063
|
withdrawalId: prepared.withdrawalId,
|
|
2016
2064
|
status: outcome.status,
|
|
@@ -2021,6 +2069,40 @@ var VaultFlowClient = class {
|
|
|
2021
2069
|
errorCode: outcome.errorCode ?? null
|
|
2022
2070
|
};
|
|
2023
2071
|
}
|
|
2072
|
+
/** Authenticated read/reconciliation only: never prepare, sign or submit a transaction. */
|
|
2073
|
+
async getOperationStatus(intentId) {
|
|
2074
|
+
const kind = vaultOperationKind(intentId);
|
|
2075
|
+
const raw = await this.getJson(`/v1/${kind === "deposit" ? "deposits" : "withdrawals"}/${intentId}?resubmit=false`);
|
|
2076
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
2077
|
+
throw new VaultFlowClientError("read", "Relayer returned an invalid operation status");
|
|
2078
|
+
}
|
|
2079
|
+
const record = raw;
|
|
2080
|
+
if (record[kind === "deposit" ? "depositId" : "withdrawalId"] !== intentId || record.wallet !== this.signer.walletAddress || record.vault !== this.vault.address) {
|
|
2081
|
+
throw new VaultFlowClientError("read", "Operation does not match the requested ID, current wallet or selected vault; use the original wallet, vault and relayer");
|
|
2082
|
+
}
|
|
2083
|
+
const requested = record[kind === "deposit" ? "amountRawUsdc" : "requestedWithdrawRawUsdc"];
|
|
2084
|
+
const actual = record[kind === "deposit" ? "actualDepositRawUsdc" : "actualWithdrawRawUsdc"];
|
|
2085
|
+
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)) {
|
|
2086
|
+
throw new VaultFlowClientError("read", "Relayer returned incomplete or invalid operation status fields");
|
|
2087
|
+
}
|
|
2088
|
+
const status = record.status;
|
|
2089
|
+
const nextAction = status === "confirmed" ? "done" : status === "submitted" || status === "prepared" ? "check_again" : "reconcile_with_operator";
|
|
2090
|
+
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.";
|
|
2091
|
+
return {
|
|
2092
|
+
intentId,
|
|
2093
|
+
kind,
|
|
2094
|
+
wallet: this.signer.walletAddress,
|
|
2095
|
+
vault: this.vault.address,
|
|
2096
|
+
status,
|
|
2097
|
+
requestedAmountRawUsdc: requested,
|
|
2098
|
+
actualAmountRawUsdc: actual,
|
|
2099
|
+
txSignature: record.txSignature,
|
|
2100
|
+
errorCode: record.errorCode,
|
|
2101
|
+
stillConfirming: status === "submitted",
|
|
2102
|
+
nextAction,
|
|
2103
|
+
message
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2024
2106
|
/**
|
|
2025
2107
|
* Reads the yield budget. Syncs the relayer's ledger from chain first (so
|
|
2026
2108
|
* yield accrued since the last sync shows up); the sync is best-effort and
|