@subly_fi/pay 0.2.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 +262 -71
- package/dist/mcp-server.js +1076 -411
- package/dist/pay.js +861 -543
- package/dist/withdraw.js +295 -102
- package/package.json +9 -7
package/dist/withdraw.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// src/client/agent-wallet-signer.ts
|
|
1
|
+
// ../../src/client/agent-wallet-signer.ts
|
|
2
2
|
import { signBytes } from "@solana/kit";
|
|
3
3
|
import bs584 from "bs58";
|
|
4
4
|
|
|
5
|
-
// src/solana/tx.ts
|
|
5
|
+
// ../../src/solana/tx.ts
|
|
6
6
|
import bs58 from "bs58";
|
|
7
7
|
import {
|
|
8
8
|
appendTransactionMessageInstructions,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
setTransactionMessageLifetimeUsingBlockhash
|
|
18
18
|
} from "@solana/kit";
|
|
19
19
|
|
|
20
|
-
// src/lib/hash.ts
|
|
20
|
+
// ../../src/lib/hash.ts
|
|
21
21
|
import { createHash } from "node:crypto";
|
|
22
22
|
function sha256TaggedHex(data) {
|
|
23
23
|
return `sha256-${createHash("sha256").update(data).digest("hex")}`;
|
|
@@ -42,16 +42,16 @@ function hashStableJson(value) {
|
|
|
42
42
|
return sha256TaggedHex(stableStringify(value));
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
// src/solana/tx.ts
|
|
45
|
+
// ../../src/solana/tx.ts
|
|
46
46
|
function decodeSerializedTransaction(serializedBase64) {
|
|
47
47
|
return getTransactionDecoder().decode(Buffer.from(serializedBase64, "base64"));
|
|
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) {
|
|
@@ -62,11 +62,11 @@ function signatureBase58ForSigner(transaction, signer2) {
|
|
|
62
62
|
return bs58.encode(signature);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
// src/client/transaction-intent-validator.ts
|
|
65
|
+
// ../../src/client/transaction-intent-validator.ts
|
|
66
66
|
import bs583 from "bs58";
|
|
67
67
|
import { getCompiledTransactionMessageDecoder } from "@solana/kit";
|
|
68
68
|
|
|
69
|
-
// src/config/constants.ts
|
|
69
|
+
// ../../src/config/constants.ts
|
|
70
70
|
var PAYMENT_SCHEME = "subly-yield-exact";
|
|
71
71
|
var SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
72
72
|
var SPL_TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
@@ -82,7 +82,7 @@ var SUBLY_VAULT = {
|
|
|
82
82
|
};
|
|
83
83
|
var USDC_DECIMALS = 6;
|
|
84
84
|
|
|
85
|
-
// src/domain/request-binding.ts
|
|
85
|
+
// ../../src/domain/request-binding.ts
|
|
86
86
|
function computeRequestBindingHash(fields) {
|
|
87
87
|
return hashStableJson({
|
|
88
88
|
sellerRequestId: fields.sellerRequestId,
|
|
@@ -97,7 +97,7 @@ function computeRequestBindingHash(fields) {
|
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
// src/lib/associated-token-account.ts
|
|
100
|
+
// ../../src/lib/associated-token-account.ts
|
|
101
101
|
import { createHash as createHash2 } from "node:crypto";
|
|
102
102
|
import bs582 from "bs58";
|
|
103
103
|
var PDA_MARKER = Buffer.from("ProgramDerivedAddress", "utf8");
|
|
@@ -189,7 +189,7 @@ function modPow(base, exponent, modulus) {
|
|
|
189
189
|
return result;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
// src/client/transaction-intent-validator.ts
|
|
192
|
+
// ../../src/client/transaction-intent-validator.ts
|
|
193
193
|
var COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111";
|
|
194
194
|
var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
|
|
195
195
|
var ASSOCIATED_TOKEN_PROGRAM_ID2 = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
@@ -875,7 +875,7 @@ function readShortVec(bytes, startOffset) {
|
|
|
875
875
|
return null;
|
|
876
876
|
}
|
|
877
877
|
|
|
878
|
-
// src/client/agent-wallet-signer.ts
|
|
878
|
+
// ../../src/client/agent-wallet-signer.ts
|
|
879
879
|
var LocalKeypairAgentWalletSigner = class {
|
|
880
880
|
validationMode = "structured_intent_transaction";
|
|
881
881
|
keyPairSigner;
|
|
@@ -945,41 +945,7 @@ var LocalKeypairAgentWalletSigner = class {
|
|
|
945
945
|
}
|
|
946
946
|
};
|
|
947
947
|
|
|
948
|
-
// src/
|
|
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
|
-
// src/client/lookup-tables.ts
|
|
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";
|
|
985
951
|
function lookupTableAddressesForTransaction(serializedTransaction) {
|
|
@@ -1019,7 +985,257 @@ async function fetchLookupTablesForTransaction(rpc2, serializedTransaction) {
|
|
|
1019
985
|
return result;
|
|
1020
986
|
}
|
|
1021
987
|
|
|
1022
|
-
// src/
|
|
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
|
+
|
|
1238
|
+
// ../../src/solana/keys.ts
|
|
1023
1239
|
import { readFileSync } from "node:fs";
|
|
1024
1240
|
import bs586 from "bs58";
|
|
1025
1241
|
import {
|
|
@@ -1044,13 +1260,13 @@ async function loadKeyPairSigner(params) {
|
|
|
1044
1260
|
throw new Error(`${label} keypair is not configured`);
|
|
1045
1261
|
}
|
|
1046
1262
|
|
|
1047
|
-
// src/solana/rpc.ts
|
|
1263
|
+
// ../../src/solana/rpc.ts
|
|
1048
1264
|
import { createSolanaRpc } from "@solana/kit";
|
|
1049
1265
|
function createRpc(url) {
|
|
1050
1266
|
return createSolanaRpc(url);
|
|
1051
1267
|
}
|
|
1052
1268
|
|
|
1053
|
-
// demo/shared.ts
|
|
1269
|
+
// ../../demo/shared.ts
|
|
1054
1270
|
function fail(message) {
|
|
1055
1271
|
console.error(message);
|
|
1056
1272
|
process.exit(1);
|
|
@@ -1062,8 +1278,8 @@ function formatRawUsdc(raw) {
|
|
|
1062
1278
|
return `${whole}.${fraction}`;
|
|
1063
1279
|
}
|
|
1064
1280
|
|
|
1065
|
-
// demo/withdraw.ts
|
|
1066
|
-
var
|
|
1281
|
+
// ../../demo/withdraw.ts
|
|
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",
|
|
@@ -15,20 +15,22 @@
|
|
|
15
15
|
"README.md"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
|
-
"build": "node build.mjs"
|
|
18
|
+
"build": "node build.mjs",
|
|
19
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
19
20
|
},
|
|
20
21
|
"dependencies": {
|
|
21
22
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
22
|
-
"@solana-program/address-lookup-table": "^0.
|
|
23
|
-
"@solana/kit": "^
|
|
24
|
-
"@
|
|
23
|
+
"@solana-program/address-lookup-table": "^0.10.0",
|
|
24
|
+
"@solana/kit": "^5.1.0",
|
|
25
|
+
"@x402/fetch": "^2.17.0",
|
|
26
|
+
"@x402/svm": "^2.17.0",
|
|
25
27
|
"bs58": "^6.0.0",
|
|
26
28
|
"tweetnacl": "^1.0.3",
|
|
27
|
-
"x402-solana": "^2.0.4",
|
|
28
29
|
"zod": "^4.1.13"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
|
-
"esbuild": "^0.28.1"
|
|
32
|
+
"esbuild": "^0.28.1",
|
|
33
|
+
"typescript": "^5.9.3"
|
|
32
34
|
},
|
|
33
35
|
"publishConfig": {
|
|
34
36
|
"access": "public"
|