@opendatalabs/vana-sdk 3.4.1 → 3.5.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/dist/account/personal-server-lite-owner-binding.cjs +3 -3
- package/dist/account/personal-server-lite-owner-binding.cjs.map +1 -1
- package/dist/account/personal-server-lite-owner-binding.d.ts +2 -2
- package/dist/account/personal-server-lite-owner-binding.js +1 -1
- package/dist/account/personal-server-lite-owner-binding.js.map +1 -1
- package/dist/index.browser.d.ts +2 -1
- package/dist/index.browser.js +208 -23
- package/dist/index.browser.js.map +4 -4
- package/dist/index.node.cjs +213 -23
- package/dist/index.node.cjs.map +4 -4
- package/dist/index.node.d.ts +2 -1
- package/dist/index.node.js +208 -23
- package/dist/index.node.js.map +4 -4
- package/dist/{protocol/personal-server-lite-owner-binding.cjs → personal-server-lite/owner-binding.cjs} +5 -5
- package/dist/personal-server-lite/owner-binding.cjs.map +1 -0
- package/dist/{protocol/personal-server-lite-owner-binding.d.ts → personal-server-lite/owner-binding.d.ts} +3 -3
- package/dist/{protocol/personal-server-lite-owner-binding.js → personal-server-lite/owner-binding.js} +2 -2
- package/dist/personal-server-lite/owner-binding.js.map +1 -0
- package/dist/protocol/escrow.cjs +146 -0
- package/dist/protocol/escrow.cjs.map +1 -0
- package/dist/protocol/escrow.d.ts +336 -0
- package/dist/protocol/escrow.js +118 -0
- package/dist/protocol/escrow.js.map +1 -0
- package/dist/protocol/escrow.test.d.ts +1 -0
- package/dist/protocol/personal-server-registration.cjs +16 -4
- package/dist/protocol/personal-server-registration.cjs.map +1 -1
- package/dist/protocol/personal-server-registration.d.ts +5 -2
- package/dist/protocol/personal-server-registration.js +16 -4
- package/dist/protocol/personal-server-registration.js.map +1 -1
- package/dist/storage/providers/vana-storage.cjs +75 -17
- package/dist/storage/providers/vana-storage.cjs.map +1 -1
- package/dist/storage/providers/vana-storage.js +75 -17
- package/dist/storage/providers/vana-storage.js.map +1 -1
- package/package.json +1 -1
- package/dist/protocol/personal-server-lite-owner-binding.cjs.map +0 -1
- package/dist/protocol/personal-server-lite-owner-binding.js.map +0 -1
- /package/dist/{protocol/personal-server-lite-owner-binding.test.d.ts → personal-server-lite/owner-binding.test.d.ts} +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const GENERIC_PAYMENT_TYPES = {
|
|
2
|
+
GenericPayment: [
|
|
3
|
+
{ name: "payerAddress", type: "address" },
|
|
4
|
+
{ name: "opType", type: "string" },
|
|
5
|
+
{ name: "opId", type: "bytes32" },
|
|
6
|
+
{ name: "asset", type: "address" },
|
|
7
|
+
{ name: "amount", type: "uint256" },
|
|
8
|
+
{ name: "paymentNonce", type: "uint256" }
|
|
9
|
+
]
|
|
10
|
+
};
|
|
11
|
+
function genericPaymentDomain(chainId, escrowContract) {
|
|
12
|
+
return {
|
|
13
|
+
name: "Vana Data Portability",
|
|
14
|
+
version: "1",
|
|
15
|
+
chainId,
|
|
16
|
+
verifyingContract: escrowContract
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const ESCROW_DEPOSIT_ABI = [
|
|
20
|
+
{
|
|
21
|
+
type: "function",
|
|
22
|
+
name: "depositNative",
|
|
23
|
+
stateMutability: "payable",
|
|
24
|
+
inputs: [{ name: "account", type: "address" }],
|
|
25
|
+
outputs: []
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: "function",
|
|
29
|
+
name: "depositToken",
|
|
30
|
+
stateMutability: "nonpayable",
|
|
31
|
+
inputs: [
|
|
32
|
+
{ name: "account", type: "address" },
|
|
33
|
+
{ name: "token", type: "address" },
|
|
34
|
+
{ name: "amount", type: "uint256" }
|
|
35
|
+
],
|
|
36
|
+
outputs: []
|
|
37
|
+
}
|
|
38
|
+
];
|
|
39
|
+
const NATIVE_ASSET_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
40
|
+
function createEscrowGatewayClient(baseUrl) {
|
|
41
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
42
|
+
async function throwOnError(res, context) {
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
let detail = "";
|
|
45
|
+
try {
|
|
46
|
+
const body = await res.json();
|
|
47
|
+
if (body.error) detail = `: ${body.error}`;
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Escrow gateway error (${context}): ${res.status} ${res.statusText}${detail}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
async submitDeposit({ txHash }) {
|
|
57
|
+
const res = await fetch(`${base}/v1/escrow/deposit`, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: { "Content-Type": "application/json" },
|
|
60
|
+
body: JSON.stringify({ txHash })
|
|
61
|
+
});
|
|
62
|
+
if (res.status !== 200 && res.status !== 202) {
|
|
63
|
+
await throwOnError(res, "POST /v1/escrow/deposit");
|
|
64
|
+
}
|
|
65
|
+
return res.json();
|
|
66
|
+
},
|
|
67
|
+
async getEscrowBalance(account) {
|
|
68
|
+
const res = await fetch(
|
|
69
|
+
`${base}/v1/escrow/balance?account=${encodeURIComponent(account)}`
|
|
70
|
+
);
|
|
71
|
+
await throwOnError(res, "GET /v1/escrow/balance");
|
|
72
|
+
return res.json();
|
|
73
|
+
},
|
|
74
|
+
async syncEscrowBalance(account) {
|
|
75
|
+
const res = await fetch(
|
|
76
|
+
`${base}/v1/escrow/balance/sync?account=${encodeURIComponent(account)}`,
|
|
77
|
+
{ method: "POST" }
|
|
78
|
+
);
|
|
79
|
+
await throwOnError(res, "POST /v1/escrow/balance/sync");
|
|
80
|
+
return res.json();
|
|
81
|
+
},
|
|
82
|
+
async payForOp({
|
|
83
|
+
payerAddress,
|
|
84
|
+
opType,
|
|
85
|
+
opId,
|
|
86
|
+
asset,
|
|
87
|
+
amount,
|
|
88
|
+
paymentNonce,
|
|
89
|
+
signature
|
|
90
|
+
}) {
|
|
91
|
+
const res = await fetch(`${base}/v1/escrow/pay`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: {
|
|
94
|
+
"Content-Type": "application/json",
|
|
95
|
+
Authorization: `Web3Signed ${signature}`
|
|
96
|
+
},
|
|
97
|
+
body: JSON.stringify({
|
|
98
|
+
payerAddress,
|
|
99
|
+
opType,
|
|
100
|
+
opId,
|
|
101
|
+
asset,
|
|
102
|
+
amount,
|
|
103
|
+
paymentNonce
|
|
104
|
+
})
|
|
105
|
+
});
|
|
106
|
+
await throwOnError(res, "POST /v1/escrow/pay");
|
|
107
|
+
return res.json();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export {
|
|
112
|
+
ESCROW_DEPOSIT_ABI,
|
|
113
|
+
GENERIC_PAYMENT_TYPES,
|
|
114
|
+
NATIVE_ASSET_ADDRESS,
|
|
115
|
+
createEscrowGatewayClient,
|
|
116
|
+
genericPaymentDomain
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=escrow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/protocol/escrow.ts"],"sourcesContent":["/**\n * DPv2 escrow payment helpers.\n *\n * Covers the three-phase flow used by builders to pay for data access:\n *\n * 1. **Deposit** — call `depositNative` or `depositToken` on the\n * DataPortabilityEscrow contract, then notify the gateway.\n * 2. **Balance** — read or force-sync the gateway's off-chain credit view.\n * 3. **Pay** — sign a `GenericPayment` EIP-712 message and POST it to the\n * gateway's `/v1/escrow/pay` endpoint.\n *\n * The gateway is the authority on balances; the on-chain contract is the\n * authority on what has been settled. Nothing in this module touches the\n * chain directly — signing is done by the caller's wallet.\n *\n * @category Protocol\n * @module escrow\n */\n\nimport type { TypedDataDomain } from \"viem\";\n\n// ---------------------------------------------------------------------------\n// EIP-712 — GenericPayment\n// ---------------------------------------------------------------------------\n\n/**\n * EIP-712 typed-data types for a generic op payment.\n *\n * The gateway verifies that the recovered signer == `payerAddress` and that\n * the (payer, paymentNonce) pair has not been seen before. Use a\n * monotonically-increasing nonce; the first payment for any payer should\n * start at 1.\n */\nexport const GENERIC_PAYMENT_TYPES = {\n GenericPayment: [\n { name: \"payerAddress\", type: \"address\" },\n { name: \"opType\", type: \"string\" },\n { name: \"opId\", type: \"bytes32\" },\n { name: \"asset\", type: \"address\" },\n { name: \"amount\", type: \"uint256\" },\n { name: \"paymentNonce\", type: \"uint256\" },\n ],\n} as const;\n\n/**\n * EIP-712 message payload for a generic op payment.\n *\n * - `opType` is currently `\"grant\"`. Additional types (file, builder, schema)\n * may be added in future protocol versions.\n * - `opId` is the bytes32 id of the operation being paid for (e.g. `grantId`).\n * - `asset` is the ERC-20 token address, or the zero address for native VANA.\n * - `amount` is the total amount in base units (wei for VANA). Must match the\n * sum the gateway expects for the current lifecycle of the op.\n * - `paymentNonce` must be a positive integer unique per `payerAddress`. Use 1\n * for the first payment; increment by at least 1 for each subsequent call.\n */\nexport interface GenericPaymentMessage {\n payerAddress: `0x${string}`;\n opType: string;\n opId: `0x${string}`;\n asset: `0x${string}`;\n amount: bigint;\n paymentNonce: bigint;\n}\n\n/**\n * Returns the EIP-712 domain for signing a `GenericPayment` message.\n *\n * The verifying contract is the `DataPortabilityEscrow` contract; all gateway\n * deployments share the same domain name and version.\n *\n * @param chainId - Chain ID of the Vana network (e.g. 1480 mainnet, 14800 testnet).\n * @param escrowContract - Deployed address of DataPortabilityEscrow.\n */\nexport function genericPaymentDomain(\n chainId: number,\n escrowContract: `0x${string}`,\n): TypedDataDomain {\n return {\n name: \"Vana Data Portability\",\n version: \"1\",\n chainId,\n verifyingContract: escrowContract,\n };\n}\n\n// ---------------------------------------------------------------------------\n// On-chain deposit ABI fragments\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal ABI for the two deposit entry points on `DataPortabilityEscrow`.\n *\n * - `depositNative(address account)` payable — credits native VANA.\n * - `depositToken(address account, address token, uint256 amount)` — credits\n * an ERC-20 token (caller must have pre-approved the escrow contract).\n *\n * Pass this to viem's `writeContract` or encode it manually.\n */\nexport const ESCROW_DEPOSIT_ABI = [\n {\n type: \"function\",\n name: \"depositNative\",\n stateMutability: \"payable\",\n inputs: [{ name: \"account\", type: \"address\" }],\n outputs: [],\n },\n {\n type: \"function\",\n name: \"depositToken\",\n stateMutability: \"nonpayable\",\n inputs: [\n { name: \"account\", type: \"address\" },\n { name: \"token\", type: \"address\" },\n { name: \"amount\", type: \"uint256\" },\n ],\n outputs: [],\n },\n] as const;\n\n/**\n * The zero address used by the DataPortabilityEscrow contract to represent\n * native VANA in `asset` fields of events and balance responses.\n */\nexport const NATIVE_ASSET_ADDRESS =\n \"0x0000000000000000000000000000000000000000\" as const;\n\n// ---------------------------------------------------------------------------\n// Gateway API client\n// ---------------------------------------------------------------------------\n\n/**\n * Per-asset balance entry returned by the gateway's escrow balance endpoints.\n *\n * - `balance` — gross finalized credit (deposits credited so far).\n * - `pendingAmount` — sum of submitted deposits not yet confirmed.\n * - `authorizedAmount` — sum of all in-flight payments authorized by\n * `/v1/escrow/pay` (soft-lock). May include payments not yet settled\n * on-chain.\n * - `availableAmount` — `max(balance − authorizedAmount, 0)`. This is what\n * the payer can authorize before the gateway rejects with 402.\n */\nexport interface EscrowBalanceEntry {\n asset: string;\n balance: string;\n pendingAmount: string;\n authorizedAmount: string;\n availableAmount: string;\n updatedAt: string | null;\n}\n\nexport interface SubmittedDepositEntry {\n txHash: string;\n submittedAt: string;\n claimedAsset: string;\n claimedAmount: string;\n}\n\nexport interface FinalizedDepositEntry {\n txHash: string;\n finalizedAt: string | null;\n blockNumber: string | null;\n claimedAsset: string;\n claimedAmount: string;\n}\n\nexport interface FailedDepositEntry {\n txHash: string;\n submittedAt: string;\n claimedAsset: string;\n claimedAmount: string;\n lastError: string | null;\n}\n\n/** Full balance read response from `GET /v1/escrow/balance`. */\nexport interface EscrowBalanceResult {\n account: string;\n balances: EscrowBalanceEntry[];\n deposits: {\n submitted: SubmittedDepositEntry[];\n finalized: FinalizedDepositEntry[];\n failed: FailedDepositEntry[];\n };\n}\n\n/**\n * Response from `POST /v1/escrow/balance/sync`.\n *\n * Extends {@link EscrowBalanceResult} with a `sync` summary of what the\n * lazy-confirmation pass did.\n */\nexport interface EscrowBalanceSyncResult extends EscrowBalanceResult {\n sync:\n | { scanned: number; finalized: number; stillPending: number; failed: number }\n | { skipped: true };\n}\n\n/** Response from `POST /v1/escrow/deposit`. */\nexport interface DepositSubmissionResult {\n success: true;\n txHash: string;\n account: string;\n status: \"submitted\" | \"finalized\" | \"failed\";\n blockNumber?: string | null;\n submittedAt: string;\n finalizedAt?: string | null;\n lastError?: string | null;\n}\n\n/** Breakdown returned by a successful `POST /v1/escrow/pay`. */\nexport interface PaymentBreakdown {\n registrationFee: string;\n dataAccessFee: string;\n /** True when this call settled the registration fee for the op. */\n registrationPaid: boolean;\n}\n\n/** Response from `POST /v1/escrow/pay`. */\nexport interface EscrowPayResult {\n success: true;\n opType: string;\n opId: string;\n payerAddress: string;\n asset: string;\n amount: string;\n breakdown: PaymentBreakdown;\n paymentNonce: string;\n paidAt: string;\n}\n\n/**\n * Parameters for submitting a deposit tx hash to the gateway.\n *\n * The gateway will decode the `account` from the tx's calldata and\n * credit the identified account once the tx reaches the configured\n * confirmation depth.\n */\nexport interface SubmitDepositParams {\n /** 0x-prefixed 32-byte transaction hash. */\n txHash: `0x${string}`;\n}\n\n/**\n * Parameters for the generic op payment endpoint (`POST /v1/escrow/pay`).\n *\n * The `signature` is an EIP-712 signature over a `GenericPayment` message\n * (see {@link GENERIC_PAYMENT_TYPES} and {@link genericPaymentDomain}).\n * Build and sign the typed data with your wallet before calling\n * {@link EscrowGatewayClient.payForOp}.\n */\nexport interface PayForOpParams {\n payerAddress: `0x${string}`;\n opType: string;\n opId: `0x${string}`;\n asset: `0x${string}`;\n /** Decimal string representation of the uint256 amount. */\n amount: string;\n /** Decimal string representation of the uint256 nonce. */\n paymentNonce: string;\n /** 0x-prefixed 65-byte EIP-712 signature hex string. */\n signature: `0x${string}`;\n}\n\n/**\n * Minimal client for the gateway's escrow endpoints.\n *\n * Construct with {@link createEscrowGatewayClient}.\n */\nexport interface EscrowGatewayClient {\n /**\n * Notify the gateway of a submitted deposit transaction.\n *\n * The gateway decodes the credited account from the on-chain tx calldata\n * and starts tracking the deposit. Call this immediately after your\n * `depositNative` or `depositToken` tx is broadcast (it accepts pending\n * mempool txs). Returns `202` while the tx awaits confirmation.\n */\n submitDeposit(params: SubmitDepositParams): Promise<DepositSubmissionResult>;\n\n /**\n * Read the current escrow balance for an account.\n *\n * Pure read — no chain calls. To force a reconciliation pass first,\n * use {@link syncEscrowBalance}.\n */\n getEscrowBalance(account: `0x${string}`): Promise<EscrowBalanceResult>;\n\n /**\n * Force a reconciliation pass then return the updated balance.\n *\n * Triggers the gateway's lazy-confirmation worker for the account — any\n * submitted deposits that have reached the configured confirmation level\n * are credited before the balance is returned. Prefer this over\n * {@link getEscrowBalance} when you need a fresh view after a deposit.\n */\n syncEscrowBalance(account: `0x${string}`): Promise<EscrowBalanceSyncResult>;\n\n /**\n * Authorize a payment against the payer's escrow balance.\n *\n * The caller must:\n * 1. Assemble a {@link GenericPaymentMessage}.\n * 2. Sign it with `signTypedData` using {@link GENERIC_PAYMENT_TYPES} and\n * the domain from {@link genericPaymentDomain}.\n * 3. Pass the message fields + signature here.\n *\n * The gateway verifies the signature, checks the soft-lock balance, and\n * records the payment. Returns 402 if the payer has insufficient balance.\n */\n payForOp(params: PayForOpParams): Promise<EscrowPayResult>;\n}\n\n/**\n * Creates a client for the gateway escrow endpoints.\n *\n * @param baseUrl - Base URL of the DP RPC gateway\n * (e.g. `\"https://dp.vana.org\"`). Trailing slashes are trimmed.\n *\n * @example\n * ```typescript\n * import {\n * createEscrowGatewayClient,\n * genericPaymentDomain,\n * GENERIC_PAYMENT_TYPES,\n * } from \"@opendatalabs/vana-sdk/node\";\n *\n * const escrow = createEscrowGatewayClient(\"https://dp.vana.org\");\n *\n * // 1. Submit your deposit tx hash after broadcasting depositNative on-chain\n * const deposit = await escrow.submitDeposit({ txHash: \"0xabc…\" });\n *\n * // 2. Force-sync and read the updated balance\n * const { balances } = await escrow.syncEscrowBalance(\"0xpayerAddress\");\n *\n * // 3. Sign and authorize a grant payment\n * const sig = await walletClient.signTypedData({\n * domain: genericPaymentDomain(1480, \"0xEscrowContract\"),\n * types: GENERIC_PAYMENT_TYPES,\n * primaryType: \"GenericPayment\",\n * message: {\n * payerAddress: \"0xpayerAddress\",\n * opType: \"grant\",\n * opId: \"0xgrantId\",\n * asset: \"0x0000000000000000000000000000000000000000\",\n * amount: 1000000000000000000n,\n * paymentNonce: 1n,\n * },\n * });\n * const result = await escrow.payForOp({\n * payerAddress: \"0xpayerAddress\",\n * opType: \"grant\",\n * opId: \"0xgrantId\",\n * asset: \"0x0000000000000000000000000000000000000000\",\n * amount: \"1000000000000000000\",\n * paymentNonce: \"1\",\n * signature: sig,\n * });\n * ```\n */\nexport function createEscrowGatewayClient(\n baseUrl: string,\n): EscrowGatewayClient {\n const base = baseUrl.replace(/\\/+$/, \"\");\n\n async function throwOnError(res: Response, context: string): Promise<void> {\n if (!res.ok) {\n let detail = \"\";\n try {\n const body = (await res.json()) as { error?: string };\n if (body.error) detail = `: ${body.error}`;\n } catch {\n // Ignore JSON parse errors; use status text only.\n }\n throw new Error(\n `Escrow gateway error (${context}): ${res.status} ${res.statusText}${detail}`,\n );\n }\n }\n\n return {\n async submitDeposit({ txHash }) {\n const res = await fetch(`${base}/v1/escrow/deposit`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ txHash }),\n });\n // 202 Accepted and 200 OK are both success states for deposit submission.\n if (res.status !== 200 && res.status !== 202) {\n await throwOnError(res, \"POST /v1/escrow/deposit\");\n }\n return res.json() as Promise<DepositSubmissionResult>;\n },\n\n async getEscrowBalance(account) {\n const res = await fetch(\n `${base}/v1/escrow/balance?account=${encodeURIComponent(account)}`,\n );\n await throwOnError(res, \"GET /v1/escrow/balance\");\n return res.json() as Promise<EscrowBalanceResult>;\n },\n\n async syncEscrowBalance(account) {\n const res = await fetch(\n `${base}/v1/escrow/balance/sync?account=${encodeURIComponent(account)}`,\n { method: \"POST\" },\n );\n await throwOnError(res, \"POST /v1/escrow/balance/sync\");\n return res.json() as Promise<EscrowBalanceSyncResult>;\n },\n\n async payForOp({\n payerAddress,\n opType,\n opId,\n asset,\n amount,\n paymentNonce,\n signature,\n }) {\n const res = await fetch(`${base}/v1/escrow/pay`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Web3Signed ${signature}`,\n },\n body: JSON.stringify({\n payerAddress,\n opType,\n opId,\n asset,\n amount,\n paymentNonce,\n }),\n });\n await throwOnError(res, \"POST /v1/escrow/pay\");\n return res.json() as Promise<EscrowPayResult>;\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -29,8 +29,13 @@ __export(personal_server_registration_exports, {
|
|
|
29
29
|
module.exports = __toCommonJS(personal_server_registration_exports);
|
|
30
30
|
var import_viem = require("viem");
|
|
31
31
|
var import_eip712 = require("./eip712");
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
var import_definitions = require("../chains/definitions");
|
|
33
|
+
var import_addresses = require("../generated/addresses");
|
|
34
|
+
const PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID = import_definitions.vanaMainnet.id;
|
|
35
|
+
const PERSONAL_SERVER_REGISTRATION_DEFAULT_VERIFYING_CONTRACT = (0, import_addresses.getContractAddress)(
|
|
36
|
+
PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID,
|
|
37
|
+
"DataPortabilityServers"
|
|
38
|
+
);
|
|
34
39
|
function assertAddress(value, name) {
|
|
35
40
|
if (!(0, import_viem.isAddress)(value)) {
|
|
36
41
|
throw new Error(`${name} must be a valid EVM address`);
|
|
@@ -45,6 +50,12 @@ function getAccountAddress(account) {
|
|
|
45
50
|
function isPersonalServerRegistrationSigner(source) {
|
|
46
51
|
return "address" in source && typeof source.signTypedData === "function";
|
|
47
52
|
}
|
|
53
|
+
function getDefaultServerRegistrationContract(chainId) {
|
|
54
|
+
return (0, import_addresses.getContractAddress)(
|
|
55
|
+
chainId,
|
|
56
|
+
"DataPortabilityServers"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
48
59
|
function createViemPersonalServerRegistrationSigner(source, options = {}) {
|
|
49
60
|
if (isPersonalServerRegistrationSigner(source)) {
|
|
50
61
|
return source;
|
|
@@ -67,12 +78,13 @@ function personalServerRegistrationDomain(input = {}) {
|
|
|
67
78
|
if (input.config) {
|
|
68
79
|
return (0, import_eip712.serverRegistrationDomain)(input.config);
|
|
69
80
|
}
|
|
70
|
-
const
|
|
81
|
+
const chainId = input.chainId ?? PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID;
|
|
82
|
+
const verifyingContract = input.verifyingContract ?? getDefaultServerRegistrationContract(chainId);
|
|
71
83
|
assertAddress(verifyingContract, "verifyingContract");
|
|
72
84
|
return {
|
|
73
85
|
name: "Vana Data Portability",
|
|
74
86
|
version: "1",
|
|
75
|
-
chainId
|
|
87
|
+
chainId,
|
|
76
88
|
verifyingContract
|
|
77
89
|
};
|
|
78
90
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/protocol/personal-server-registration.ts"],"sourcesContent":["/**\n * Personal Server registration typed-data and signing helpers.\n *\n * These helpers are protocol-owned and runtime-neutral. Apps can sign with\n * viem local accounts, wallet clients, Account products, or any equivalent\n * signer by adapting to {@link PersonalServerRegistrationSigner}.\n *\n * @category Protocol\n */\n\nimport {\n isAddress,\n type Account,\n type Address,\n type Hex,\n type TypedDataDomain,\n type TypedDataDefinition,\n} from \"viem\";\nimport {\n SERVER_REGISTRATION_TYPES,\n serverRegistrationDomain,\n type DataPortabilityGatewayConfig,\n type ServerRegistrationMessage,\n} from \"./eip712\";\n\nexport const PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID
|
|
1
|
+
{"version":3,"sources":["../../src/protocol/personal-server-registration.ts"],"sourcesContent":["/**\n * Personal Server registration typed-data and signing helpers.\n *\n * These helpers are protocol-owned and runtime-neutral. Apps can sign with\n * viem local accounts, wallet clients, Account products, or any equivalent\n * signer by adapting to {@link PersonalServerRegistrationSigner}.\n *\n * @category Protocol\n */\n\nimport {\n isAddress,\n type Account,\n type Address,\n type Hex,\n type TypedDataDomain,\n type TypedDataDefinition,\n} from \"viem\";\nimport {\n SERVER_REGISTRATION_TYPES,\n serverRegistrationDomain,\n type DataPortabilityGatewayConfig,\n type ServerRegistrationMessage,\n} from \"./eip712\";\nimport { vanaMainnet } from \"../chains/definitions\";\nimport { getContractAddress } from \"../generated/addresses\";\n\ntype SupportedContractAddressChainId = Parameters<typeof getContractAddress>[0];\n\nexport const PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID =\n vanaMainnet.id as SupportedContractAddressChainId;\nexport const PERSONAL_SERVER_REGISTRATION_DEFAULT_VERIFYING_CONTRACT =\n getContractAddress(\n PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID,\n \"DataPortabilityServers\",\n );\n\nexport type PersonalServerRegistrationTypedData = TypedDataDefinition<\n typeof SERVER_REGISTRATION_TYPES,\n \"ServerRegistration\"\n> & {\n message: ServerRegistrationMessage;\n};\n\nexport interface PersonalServerRegistrationSigner {\n address: Address;\n signTypedData(\n typedData: PersonalServerRegistrationTypedData,\n ): Promise<Hex> | Hex;\n}\n\nexport interface ViemPersonalServerRegistrationWalletClient {\n account?: Account | Address | null;\n signTypedData(\n typedData: PersonalServerRegistrationTypedData & {\n account?: Account | Address;\n },\n ): Promise<Hex>;\n}\n\nexport type ViemPersonalServerRegistrationSignerSource =\n | PersonalServerRegistrationSigner\n | ViemPersonalServerRegistrationWalletClient;\n\nexport interface BuildPersonalServerRegistrationTypedDataInput {\n ownerAddress: Address;\n serverAddress: Address;\n serverPublicKey: string;\n serverUrl: string;\n config?: DataPortabilityGatewayConfig;\n chainId?: number;\n verifyingContract?: Address;\n}\n\nexport interface BuildPersonalServerRegistrationSignatureInput {\n signer: PersonalServerRegistrationSigner;\n serverAddress: Address;\n serverPublicKey: string;\n serverUrl: string;\n config?: DataPortabilityGatewayConfig;\n chainId?: number;\n verifyingContract?: Address;\n}\n\nexport interface PersonalServerRegistrationSignature {\n signature: Hex;\n signerAddress: Address;\n typedData: PersonalServerRegistrationTypedData;\n}\n\nexport interface PersonalServerRegistrationDomainInput {\n config?: DataPortabilityGatewayConfig;\n chainId?: number;\n verifyingContract?: Address;\n}\n\nfunction assertAddress(value: Address, name: string): void {\n if (!isAddress(value)) {\n throw new Error(`${name} must be a valid EVM address`);\n }\n}\n\nfunction getAccountAddress(\n account: Account | Address | null | undefined,\n): Address | undefined {\n if (!account) {\n return undefined;\n }\n\n return typeof account === \"string\" ? account : account.address;\n}\n\nfunction isPersonalServerRegistrationSigner(\n source: ViemPersonalServerRegistrationSignerSource,\n): source is PersonalServerRegistrationSigner {\n return \"address\" in source && typeof source.signTypedData === \"function\";\n}\n\nfunction getDefaultServerRegistrationContract(chainId: number): Address {\n return getContractAddress(\n chainId as SupportedContractAddressChainId,\n \"DataPortabilityServers\",\n );\n}\n\nexport function createViemPersonalServerRegistrationSigner(\n source: ViemPersonalServerRegistrationSignerSource,\n options: { account?: Account | Address } = {},\n): PersonalServerRegistrationSigner {\n if (isPersonalServerRegistrationSigner(source)) {\n return source;\n }\n\n const accountAddress =\n getAccountAddress(options.account) ?? getAccountAddress(source.account);\n\n if (accountAddress) {\n return {\n address: accountAddress,\n signTypedData: (typedData) =>\n source.signTypedData({\n ...typedData,\n account: options.account ?? source.account ?? accountAddress,\n }),\n };\n }\n\n throw new Error(\n \"Viem wallet client requires an account option or account property\",\n );\n}\n\nexport function personalServerRegistrationDomain(\n input: PersonalServerRegistrationDomainInput = {},\n): TypedDataDomain {\n if (input.config) {\n return serverRegistrationDomain(input.config);\n }\n\n const chainId =\n input.chainId ?? PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID;\n const verifyingContract =\n input.verifyingContract ?? getDefaultServerRegistrationContract(chainId);\n assertAddress(verifyingContract, \"verifyingContract\");\n\n return {\n name: \"Vana Data Portability\",\n version: \"1\",\n chainId,\n verifyingContract,\n };\n}\n\nexport function buildPersonalServerRegistrationTypedData(\n input: BuildPersonalServerRegistrationTypedDataInput,\n): PersonalServerRegistrationTypedData {\n assertAddress(input.ownerAddress, \"ownerAddress\");\n assertAddress(input.serverAddress, \"serverAddress\");\n\n return {\n domain: personalServerRegistrationDomain(input),\n types: SERVER_REGISTRATION_TYPES,\n primaryType: \"ServerRegistration\",\n message: {\n ownerAddress: input.ownerAddress,\n serverAddress: input.serverAddress,\n publicKey: input.serverPublicKey,\n serverUrl: input.serverUrl,\n },\n };\n}\n\nexport async function buildPersonalServerRegistrationSignature(\n input: BuildPersonalServerRegistrationSignatureInput,\n): Promise<PersonalServerRegistrationSignature> {\n const typedData = buildPersonalServerRegistrationTypedData({\n ownerAddress: input.signer.address,\n serverAddress: input.serverAddress,\n serverPublicKey: input.serverPublicKey,\n serverUrl: input.serverUrl,\n config: input.config,\n chainId: input.chainId,\n verifyingContract: input.verifyingContract,\n });\n const signature = await input.signer.signTypedData(typedData);\n\n return {\n signature,\n signerAddress: input.signer.address,\n typedData,\n };\n}\n\nexport const registerPersonalServerSignature =\n buildPersonalServerRegistrationSignature;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,kBAOO;AACP,oBAKO;AACP,yBAA4B;AAC5B,uBAAmC;AAI5B,MAAM,gDACX,+BAAY;AACP,MAAM,8DACX;AAAA,EACE;AAAA,EACA;AACF;AA6DF,SAAS,cAAc,OAAgB,MAAoB;AACzD,MAAI,KAAC,uBAAU,KAAK,GAAG;AACrB,UAAM,IAAI,MAAM,GAAG,IAAI,8BAA8B;AAAA,EACvD;AACF;AAEA,SAAS,kBACP,SACqB;AACrB,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,YAAY,WAAW,UAAU,QAAQ;AACzD;AAEA,SAAS,mCACP,QAC4C;AAC5C,SAAO,aAAa,UAAU,OAAO,OAAO,kBAAkB;AAChE;AAEA,SAAS,qCAAqC,SAA0B;AACtE,aAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,2CACd,QACA,UAA2C,CAAC,GACV;AAClC,MAAI,mCAAmC,MAAM,GAAG;AAC9C,WAAO;AAAA,EACT;AAEA,QAAM,iBACJ,kBAAkB,QAAQ,OAAO,KAAK,kBAAkB,OAAO,OAAO;AAExE,MAAI,gBAAgB;AAClB,WAAO;AAAA,MACL,SAAS;AAAA,MACT,eAAe,CAAC,cACd,OAAO,cAAc;AAAA,QACnB,GAAG;AAAA,QACH,SAAS,QAAQ,WAAW,OAAO,WAAW;AAAA,MAChD,CAAC;AAAA,IACL;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAEO,SAAS,iCACd,QAA+C,CAAC,GAC/B;AACjB,MAAI,MAAM,QAAQ;AAChB,eAAO,wCAAyB,MAAM,MAAM;AAAA,EAC9C;AAEA,QAAM,UACJ,MAAM,WAAW;AACnB,QAAM,oBACJ,MAAM,qBAAqB,qCAAqC,OAAO;AACzE,gBAAc,mBAAmB,mBAAmB;AAEpD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,yCACd,OACqC;AACrC,gBAAc,MAAM,cAAc,cAAc;AAChD,gBAAc,MAAM,eAAe,eAAe;AAElD,SAAO;AAAA,IACL,QAAQ,iCAAiC,KAAK;AAAA,IAC9C,OAAO;AAAA,IACP,aAAa;AAAA,IACb,SAAS;AAAA,MACP,cAAc,MAAM;AAAA,MACpB,eAAe,MAAM;AAAA,MACrB,WAAW,MAAM;AAAA,MACjB,WAAW,MAAM;AAAA,IACnB;AAAA,EACF;AACF;AAEA,eAAsB,yCACpB,OAC8C;AAC9C,QAAM,YAAY,yCAAyC;AAAA,IACzD,cAAc,MAAM,OAAO;AAAA,IAC3B,eAAe,MAAM;AAAA,IACrB,iBAAiB,MAAM;AAAA,IACvB,WAAW,MAAM;AAAA,IACjB,QAAQ,MAAM;AAAA,IACd,SAAS,MAAM;AAAA,IACf,mBAAmB,MAAM;AAAA,EAC3B,CAAC;AACD,QAAM,YAAY,MAAM,MAAM,OAAO,cAAc,SAAS;AAE5D,SAAO;AAAA,IACL;AAAA,IACA,eAAe,MAAM,OAAO;AAAA,IAC5B;AAAA,EACF;AACF;AAEO,MAAM,kCACX;","names":[]}
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { type Account, type Address, type Hex, type TypedDataDomain, type TypedDataDefinition } from "viem";
|
|
11
11
|
import { SERVER_REGISTRATION_TYPES, type DataPortabilityGatewayConfig, type ServerRegistrationMessage } from "./eip712";
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
import { getContractAddress } from "../generated/addresses";
|
|
13
|
+
type SupportedContractAddressChainId = Parameters<typeof getContractAddress>[0];
|
|
14
|
+
export declare const PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID: SupportedContractAddressChainId;
|
|
15
|
+
export declare const PERSONAL_SERVER_REGISTRATION_DEFAULT_VERIFYING_CONTRACT: `0x${string}`;
|
|
14
16
|
export type PersonalServerRegistrationTypedData = TypedDataDefinition<typeof SERVER_REGISTRATION_TYPES, "ServerRegistration"> & {
|
|
15
17
|
message: ServerRegistrationMessage;
|
|
16
18
|
};
|
|
@@ -60,3 +62,4 @@ export declare function personalServerRegistrationDomain(input?: PersonalServerR
|
|
|
60
62
|
export declare function buildPersonalServerRegistrationTypedData(input: BuildPersonalServerRegistrationTypedDataInput): PersonalServerRegistrationTypedData;
|
|
61
63
|
export declare function buildPersonalServerRegistrationSignature(input: BuildPersonalServerRegistrationSignatureInput): Promise<PersonalServerRegistrationSignature>;
|
|
62
64
|
export declare const registerPersonalServerSignature: typeof buildPersonalServerRegistrationSignature;
|
|
65
|
+
export {};
|
|
@@ -5,8 +5,13 @@ import {
|
|
|
5
5
|
SERVER_REGISTRATION_TYPES,
|
|
6
6
|
serverRegistrationDomain
|
|
7
7
|
} from "./eip712.js";
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import { vanaMainnet } from "../chains/definitions.js";
|
|
9
|
+
import { getContractAddress } from "../generated/addresses.js";
|
|
10
|
+
const PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID = vanaMainnet.id;
|
|
11
|
+
const PERSONAL_SERVER_REGISTRATION_DEFAULT_VERIFYING_CONTRACT = getContractAddress(
|
|
12
|
+
PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID,
|
|
13
|
+
"DataPortabilityServers"
|
|
14
|
+
);
|
|
10
15
|
function assertAddress(value, name) {
|
|
11
16
|
if (!isAddress(value)) {
|
|
12
17
|
throw new Error(`${name} must be a valid EVM address`);
|
|
@@ -21,6 +26,12 @@ function getAccountAddress(account) {
|
|
|
21
26
|
function isPersonalServerRegistrationSigner(source) {
|
|
22
27
|
return "address" in source && typeof source.signTypedData === "function";
|
|
23
28
|
}
|
|
29
|
+
function getDefaultServerRegistrationContract(chainId) {
|
|
30
|
+
return getContractAddress(
|
|
31
|
+
chainId,
|
|
32
|
+
"DataPortabilityServers"
|
|
33
|
+
);
|
|
34
|
+
}
|
|
24
35
|
function createViemPersonalServerRegistrationSigner(source, options = {}) {
|
|
25
36
|
if (isPersonalServerRegistrationSigner(source)) {
|
|
26
37
|
return source;
|
|
@@ -43,12 +54,13 @@ function personalServerRegistrationDomain(input = {}) {
|
|
|
43
54
|
if (input.config) {
|
|
44
55
|
return serverRegistrationDomain(input.config);
|
|
45
56
|
}
|
|
46
|
-
const
|
|
57
|
+
const chainId = input.chainId ?? PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID;
|
|
58
|
+
const verifyingContract = input.verifyingContract ?? getDefaultServerRegistrationContract(chainId);
|
|
47
59
|
assertAddress(verifyingContract, "verifyingContract");
|
|
48
60
|
return {
|
|
49
61
|
name: "Vana Data Portability",
|
|
50
62
|
version: "1",
|
|
51
|
-
chainId
|
|
63
|
+
chainId,
|
|
52
64
|
verifyingContract
|
|
53
65
|
};
|
|
54
66
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/protocol/personal-server-registration.ts"],"sourcesContent":["/**\n * Personal Server registration typed-data and signing helpers.\n *\n * These helpers are protocol-owned and runtime-neutral. Apps can sign with\n * viem local accounts, wallet clients, Account products, or any equivalent\n * signer by adapting to {@link PersonalServerRegistrationSigner}.\n *\n * @category Protocol\n */\n\nimport {\n isAddress,\n type Account,\n type Address,\n type Hex,\n type TypedDataDomain,\n type TypedDataDefinition,\n} from \"viem\";\nimport {\n SERVER_REGISTRATION_TYPES,\n serverRegistrationDomain,\n type DataPortabilityGatewayConfig,\n type ServerRegistrationMessage,\n} from \"./eip712\";\n\nexport const PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID
|
|
1
|
+
{"version":3,"sources":["../../src/protocol/personal-server-registration.ts"],"sourcesContent":["/**\n * Personal Server registration typed-data and signing helpers.\n *\n * These helpers are protocol-owned and runtime-neutral. Apps can sign with\n * viem local accounts, wallet clients, Account products, or any equivalent\n * signer by adapting to {@link PersonalServerRegistrationSigner}.\n *\n * @category Protocol\n */\n\nimport {\n isAddress,\n type Account,\n type Address,\n type Hex,\n type TypedDataDomain,\n type TypedDataDefinition,\n} from \"viem\";\nimport {\n SERVER_REGISTRATION_TYPES,\n serverRegistrationDomain,\n type DataPortabilityGatewayConfig,\n type ServerRegistrationMessage,\n} from \"./eip712\";\nimport { vanaMainnet } from \"../chains/definitions\";\nimport { getContractAddress } from \"../generated/addresses\";\n\ntype SupportedContractAddressChainId = Parameters<typeof getContractAddress>[0];\n\nexport const PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID =\n vanaMainnet.id as SupportedContractAddressChainId;\nexport const PERSONAL_SERVER_REGISTRATION_DEFAULT_VERIFYING_CONTRACT =\n getContractAddress(\n PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID,\n \"DataPortabilityServers\",\n );\n\nexport type PersonalServerRegistrationTypedData = TypedDataDefinition<\n typeof SERVER_REGISTRATION_TYPES,\n \"ServerRegistration\"\n> & {\n message: ServerRegistrationMessage;\n};\n\nexport interface PersonalServerRegistrationSigner {\n address: Address;\n signTypedData(\n typedData: PersonalServerRegistrationTypedData,\n ): Promise<Hex> | Hex;\n}\n\nexport interface ViemPersonalServerRegistrationWalletClient {\n account?: Account | Address | null;\n signTypedData(\n typedData: PersonalServerRegistrationTypedData & {\n account?: Account | Address;\n },\n ): Promise<Hex>;\n}\n\nexport type ViemPersonalServerRegistrationSignerSource =\n | PersonalServerRegistrationSigner\n | ViemPersonalServerRegistrationWalletClient;\n\nexport interface BuildPersonalServerRegistrationTypedDataInput {\n ownerAddress: Address;\n serverAddress: Address;\n serverPublicKey: string;\n serverUrl: string;\n config?: DataPortabilityGatewayConfig;\n chainId?: number;\n verifyingContract?: Address;\n}\n\nexport interface BuildPersonalServerRegistrationSignatureInput {\n signer: PersonalServerRegistrationSigner;\n serverAddress: Address;\n serverPublicKey: string;\n serverUrl: string;\n config?: DataPortabilityGatewayConfig;\n chainId?: number;\n verifyingContract?: Address;\n}\n\nexport interface PersonalServerRegistrationSignature {\n signature: Hex;\n signerAddress: Address;\n typedData: PersonalServerRegistrationTypedData;\n}\n\nexport interface PersonalServerRegistrationDomainInput {\n config?: DataPortabilityGatewayConfig;\n chainId?: number;\n verifyingContract?: Address;\n}\n\nfunction assertAddress(value: Address, name: string): void {\n if (!isAddress(value)) {\n throw new Error(`${name} must be a valid EVM address`);\n }\n}\n\nfunction getAccountAddress(\n account: Account | Address | null | undefined,\n): Address | undefined {\n if (!account) {\n return undefined;\n }\n\n return typeof account === \"string\" ? account : account.address;\n}\n\nfunction isPersonalServerRegistrationSigner(\n source: ViemPersonalServerRegistrationSignerSource,\n): source is PersonalServerRegistrationSigner {\n return \"address\" in source && typeof source.signTypedData === \"function\";\n}\n\nfunction getDefaultServerRegistrationContract(chainId: number): Address {\n return getContractAddress(\n chainId as SupportedContractAddressChainId,\n \"DataPortabilityServers\",\n );\n}\n\nexport function createViemPersonalServerRegistrationSigner(\n source: ViemPersonalServerRegistrationSignerSource,\n options: { account?: Account | Address } = {},\n): PersonalServerRegistrationSigner {\n if (isPersonalServerRegistrationSigner(source)) {\n return source;\n }\n\n const accountAddress =\n getAccountAddress(options.account) ?? getAccountAddress(source.account);\n\n if (accountAddress) {\n return {\n address: accountAddress,\n signTypedData: (typedData) =>\n source.signTypedData({\n ...typedData,\n account: options.account ?? source.account ?? accountAddress,\n }),\n };\n }\n\n throw new Error(\n \"Viem wallet client requires an account option or account property\",\n );\n}\n\nexport function personalServerRegistrationDomain(\n input: PersonalServerRegistrationDomainInput = {},\n): TypedDataDomain {\n if (input.config) {\n return serverRegistrationDomain(input.config);\n }\n\n const chainId =\n input.chainId ?? PERSONAL_SERVER_REGISTRATION_DEFAULT_CHAIN_ID;\n const verifyingContract =\n input.verifyingContract ?? getDefaultServerRegistrationContract(chainId);\n assertAddress(verifyingContract, \"verifyingContract\");\n\n return {\n name: \"Vana Data Portability\",\n version: \"1\",\n chainId,\n verifyingContract,\n };\n}\n\nexport function buildPersonalServerRegistrationTypedData(\n input: BuildPersonalServerRegistrationTypedDataInput,\n): PersonalServerRegistrationTypedData {\n assertAddress(input.ownerAddress, \"ownerAddress\");\n assertAddress(input.serverAddress, \"serverAddress\");\n\n return {\n domain: personalServerRegistrationDomain(input),\n types: SERVER_REGISTRATION_TYPES,\n primaryType: \"ServerRegistration\",\n message: {\n ownerAddress: input.ownerAddress,\n serverAddress: input.serverAddress,\n publicKey: input.serverPublicKey,\n serverUrl: input.serverUrl,\n },\n };\n}\n\nexport async function buildPersonalServerRegistrationSignature(\n input: BuildPersonalServerRegistrationSignatureInput,\n): Promise<PersonalServerRegistrationSignature> {\n const typedData = buildPersonalServerRegistrationTypedData({\n ownerAddress: input.signer.address,\n serverAddress: input.serverAddress,\n serverPublicKey: input.serverPublicKey,\n serverUrl: input.serverUrl,\n config: input.config,\n chainId: input.chainId,\n verifyingContract: input.verifyingContract,\n });\n const signature = await input.signer.signTypedData(typedData);\n\n return {\n signature,\n signerAddress: input.signer.address,\n typedData,\n };\n}\n\nexport const registerPersonalServerSignature =\n buildPersonalServerRegistrationSignature;\n"],"mappings":"AAUA;AAAA,EACE;AAAA,OAMK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,mBAAmB;AAC5B,SAAS,0BAA0B;AAI5B,MAAM,gDACX,YAAY;AACP,MAAM,0DACX;AAAA,EACE;AAAA,EACA;AACF;AA6DF,SAAS,cAAc,OAAgB,MAAoB;AACzD,MAAI,CAAC,UAAU,KAAK,GAAG;AACrB,UAAM,IAAI,MAAM,GAAG,IAAI,8BAA8B;AAAA,EACvD;AACF;AAEA,SAAS,kBACP,SACqB;AACrB,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,YAAY,WAAW,UAAU,QAAQ;AACzD;AAEA,SAAS,mCACP,QAC4C;AAC5C,SAAO,aAAa,UAAU,OAAO,OAAO,kBAAkB;AAChE;AAEA,SAAS,qCAAqC,SAA0B;AACtE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,2CACd,QACA,UAA2C,CAAC,GACV;AAClC,MAAI,mCAAmC,MAAM,GAAG;AAC9C,WAAO;AAAA,EACT;AAEA,QAAM,iBACJ,kBAAkB,QAAQ,OAAO,KAAK,kBAAkB,OAAO,OAAO;AAExE,MAAI,gBAAgB;AAClB,WAAO;AAAA,MACL,SAAS;AAAA,MACT,eAAe,CAAC,cACd,OAAO,cAAc;AAAA,QACnB,GAAG;AAAA,QACH,SAAS,QAAQ,WAAW,OAAO,WAAW;AAAA,MAChD,CAAC;AAAA,IACL;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAEO,SAAS,iCACd,QAA+C,CAAC,GAC/B;AACjB,MAAI,MAAM,QAAQ;AAChB,WAAO,yBAAyB,MAAM,MAAM;AAAA,EAC9C;AAEA,QAAM,UACJ,MAAM,WAAW;AACnB,QAAM,oBACJ,MAAM,qBAAqB,qCAAqC,OAAO;AACzE,gBAAc,mBAAmB,mBAAmB;AAEpD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,yCACd,OACqC;AACrC,gBAAc,MAAM,cAAc,cAAc;AAChD,gBAAc,MAAM,eAAe,eAAe;AAElD,SAAO;AAAA,IACL,QAAQ,iCAAiC,KAAK;AAAA,IAC9C,OAAO;AAAA,IACP,aAAa;AAAA,IACb,SAAS;AAAA,MACP,cAAc,MAAM;AAAA,MACpB,eAAe,MAAM;AAAA,MACrB,WAAW,MAAM;AAAA,MACjB,WAAW,MAAM;AAAA,IACnB;AAAA,EACF;AACF;AAEA,eAAsB,yCACpB,OAC8C;AAC9C,QAAM,YAAY,yCAAyC;AAAA,IACzD,cAAc,MAAM,OAAO;AAAA,IAC3B,eAAe,MAAM;AAAA,IACrB,iBAAiB,MAAM;AAAA,IACvB,WAAW,MAAM;AAAA,IACjB,QAAQ,MAAM;AAAA,IACd,SAAS,MAAM;AAAA,IACf,mBAAmB,MAAM;AAAA,EAC3B,CAAC;AACD,QAAM,YAAY,MAAM,MAAM,OAAO,cAAc,SAAS;AAE5D,SAAO;AAAA,IACL;AAAA,IACA,eAAe,MAAM,OAAO;AAAA,IAC5B;AAAA,EACF;AACF;AAEO,MAAM,kCACX;","names":[]}
|
|
@@ -26,6 +26,8 @@ var import_web3_signed_builder = require("../../auth/web3-signed-builder");
|
|
|
26
26
|
const DEFAULT_ENDPOINT = "https://storage.vana.org";
|
|
27
27
|
const BLOB_PATH_PREFIX = "/v1/blobs";
|
|
28
28
|
const DEFAULT_TOKEN_TTL_SECONDS = 300;
|
|
29
|
+
const MAX_UPLOAD_ATTEMPTS = 4;
|
|
30
|
+
const MAX_RATE_LIMIT_DELAY_MS = 3e4;
|
|
29
31
|
class VanaStorage {
|
|
30
32
|
endpoint;
|
|
31
33
|
signer;
|
|
@@ -64,27 +66,46 @@ class VanaStorage {
|
|
|
64
66
|
const body = new Uint8Array(await file.arrayBuffer());
|
|
65
67
|
const contentType = file.type !== "" ? file.type : "application/octet-stream";
|
|
66
68
|
const header = await this.signRequest("PUT", path, body);
|
|
67
|
-
let response;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
let response = null;
|
|
70
|
+
let responseText = "";
|
|
71
|
+
for (let attempt = 1; attempt <= MAX_UPLOAD_ATTEMPTS; attempt++) {
|
|
72
|
+
try {
|
|
73
|
+
response = await this.fetchImpl(`${this.endpoint}${path}`, {
|
|
74
|
+
method: "PUT",
|
|
75
|
+
headers: {
|
|
76
|
+
authorization: header,
|
|
77
|
+
"content-type": contentType
|
|
78
|
+
},
|
|
79
|
+
body
|
|
80
|
+
});
|
|
81
|
+
} catch (cause) {
|
|
82
|
+
throw new import__.StorageError(
|
|
83
|
+
`vana-storage upload network error: ${describe(cause)}`,
|
|
84
|
+
"UPLOAD_ERROR",
|
|
85
|
+
"vana-storage",
|
|
86
|
+
{ cause: cause instanceof Error ? cause : void 0 }
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (response.ok) {
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
responseText = await safeText(response);
|
|
93
|
+
if (response.status === 429 && attempt < MAX_UPLOAD_ATTEMPTS) {
|
|
94
|
+
const delayMs = retryDelayMs(response, responseText);
|
|
95
|
+
if (delayMs > 0) {
|
|
96
|
+
await sleep(delayMs);
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
78
100
|
throw new import__.StorageError(
|
|
79
|
-
`vana-storage upload
|
|
80
|
-
"
|
|
81
|
-
"vana-storage"
|
|
82
|
-
{ cause: cause instanceof Error ? cause : void 0 }
|
|
101
|
+
`vana-storage upload failed: ${response.status} ${response.statusText} - ${responseText}`,
|
|
102
|
+
"UPLOAD_FAILED",
|
|
103
|
+
"vana-storage"
|
|
83
104
|
);
|
|
84
105
|
}
|
|
85
|
-
if (!response
|
|
106
|
+
if (!response?.ok) {
|
|
86
107
|
throw new import__.StorageError(
|
|
87
|
-
`vana-storage upload failed
|
|
108
|
+
`vana-storage upload failed after ${MAX_UPLOAD_ATTEMPTS} attempts - ${responseText}`,
|
|
88
109
|
"UPLOAD_FAILED",
|
|
89
110
|
"vana-storage"
|
|
90
111
|
);
|
|
@@ -237,6 +258,43 @@ function describe(value) {
|
|
|
237
258
|
if (value instanceof Error) return value.message;
|
|
238
259
|
return String(value);
|
|
239
260
|
}
|
|
261
|
+
function retryDelayMs(response, responseText) {
|
|
262
|
+
const headerDelayMs = parseRetryAfterHeaderMs(
|
|
263
|
+
response.headers.get("retry-after")
|
|
264
|
+
);
|
|
265
|
+
if (headerDelayMs !== null) {
|
|
266
|
+
return clampRateLimitDelay(headerDelayMs);
|
|
267
|
+
}
|
|
268
|
+
return clampRateLimitDelay(parseRetryAfterBodyMs(responseText) ?? 0);
|
|
269
|
+
}
|
|
270
|
+
function parseRetryAfterHeaderMs(value) {
|
|
271
|
+
if (!value) return null;
|
|
272
|
+
const seconds = Number(value);
|
|
273
|
+
if (Number.isFinite(seconds)) {
|
|
274
|
+
return seconds * 1e3;
|
|
275
|
+
}
|
|
276
|
+
const dateMs = Date.parse(value);
|
|
277
|
+
if (Number.isFinite(dateMs)) {
|
|
278
|
+
return Math.max(0, dateMs - Date.now());
|
|
279
|
+
}
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
function parseRetryAfterBodyMs(responseText) {
|
|
283
|
+
if (!responseText) return null;
|
|
284
|
+
try {
|
|
285
|
+
const parsed = JSON.parse(responseText);
|
|
286
|
+
const seconds = Number(parsed.retryAfter);
|
|
287
|
+
return Number.isFinite(seconds) ? seconds * 1e3 : null;
|
|
288
|
+
} catch {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function clampRateLimitDelay(delayMs) {
|
|
293
|
+
return Math.min(Math.max(0, delayMs), MAX_RATE_LIMIT_DELAY_MS);
|
|
294
|
+
}
|
|
295
|
+
function sleep(ms) {
|
|
296
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
297
|
+
}
|
|
240
298
|
async function safeText(response) {
|
|
241
299
|
try {
|
|
242
300
|
return await response.text();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/storage/providers/vana-storage.ts"],"sourcesContent":["import {\n StorageError,\n type StorageProvider,\n type StorageUploadResult,\n type StorageFile,\n type StorageListOptions,\n type StorageProviderConfig,\n} from \"../index\";\nimport {\n buildWeb3SignedHeader,\n type Web3SignedSignFn,\n} from \"../../auth/web3-signed-builder\";\n\nconst DEFAULT_ENDPOINT = \"https://storage.vana.org\";\nconst BLOB_PATH_PREFIX = \"/v1/blobs\";\nconst DEFAULT_TOKEN_TTL_SECONDS = 300;\n\n/**\n * Wallet-style signer used by {@link VanaStorage} to authenticate every\n * request. For Personal Server flows this can be a registered server wallet\n * signing requests for the owner's storage namespace.\n *\n * @category Storage\n */\nexport interface VanaStorageSigner {\n /** EIP-191 address (`0x...`). */\n address: `0x${string}`;\n /** EIP-191 personal_sign callback (e.g. viem `account.signMessage`). */\n signMessage: Web3SignedSignFn;\n}\n\n/**\n * Configuration for {@link VanaStorage}.\n *\n * @category Storage\n */\nexport interface VanaStorageConfig {\n /**\n * Base URL of the vana-storage Worker. Defaults to `https://storage.vana.org`.\n */\n endpoint?: string;\n /**\n * Wallet signer used to authenticate writes and reads.\n */\n signer: VanaStorageSigner;\n /**\n * Owner namespace under which blobs are stored. Defaults to the signer address.\n */\n ownerAddress?: `0x${string}`;\n /**\n * Optional `fetch` implementation. Defaults to the global `fetch`.\n * Useful for tests and for environments that need a custom HTTP client.\n */\n fetchImpl?: typeof fetch;\n}\n\ninterface VanaStorageUploadResponse {\n key: string;\n url: string;\n etag: string;\n size: number;\n}\n\n/**\n * Storage provider that talks to the vana-storage Worker\n * (`https://storage.vana.org` by default). All requests are authenticated\n * with Web3Signed headers signed by the configured wallet.\n *\n * @remarks\n * Filenames passed to {@link VanaStorage.upload} must be of the form\n * `\"{scope}/{collectedAt}\"` (e.g. `\"instagram.profile/2026-05-08T20:00:00.000Z\"`).\n * The owner address is prepended automatically to produce the canonical\n * blob path `/v1/blobs/{owner}/{scope}/{collectedAt}`.\n *\n * @category Storage\n *\n * @example\n * ```typescript\n * import { privateKeyToAccount } from \"viem/accounts\";\n * import { VanaStorage } from \"@opendatalabs/vana-sdk/node\";\n *\n * const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);\n * const storage = new VanaStorage({\n * signer: {\n * address: account.address,\n * signMessage: (msg) => account.signMessage({ message: msg }),\n * },\n * });\n *\n * const result = await storage.upload(\n * new Blob([ciphertext]),\n * \"instagram.profile/2026-05-08T20:00:00.000Z\",\n * );\n * ```\n */\nexport class VanaStorage implements StorageProvider {\n private readonly endpoint: string;\n private readonly signer: VanaStorageSigner;\n private readonly ownerAddress: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(config: VanaStorageConfig) {\n if (!config?.signer?.address || !config?.signer?.signMessage) {\n throw new StorageError(\n \"VanaStorage requires a signer with address and signMessage\",\n \"MISSING_SIGNER\",\n \"vana-storage\",\n );\n }\n this.endpoint = (config.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/+$/, \"\");\n this.signer = config.signer;\n this.ownerAddress = (\n config.ownerAddress ?? config.signer.address\n ).toLowerCase();\n this.fetchImpl = config.fetchImpl ?? globalThis.fetch.bind(globalThis);\n }\n\n /**\n * Upload an encrypted blob to vana-storage.\n *\n * @param file - The blob to upload.\n * @param filename - Required relative key in the form `\"{scope}/{collectedAt}\"`.\n * The owner address is prepended automatically.\n */\n async upload(file: Blob, filename?: string): Promise<StorageUploadResult> {\n if (!filename) {\n throw new StorageError(\n \"VanaStorage.upload requires a filename of the form '{scope}/{collectedAt}'\",\n \"MISSING_FILENAME\",\n \"vana-storage\",\n );\n }\n\n const subpath = encodeRelativePath(filename);\n const path = `${BLOB_PATH_PREFIX}/${this.ownerAddress}/${subpath}`;\n const body = new Uint8Array(await file.arrayBuffer());\n const contentType =\n file.type !== \"\" ? file.type : \"application/octet-stream\";\n\n const header = await this.signRequest(\"PUT\", path, body);\n\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.endpoint}${path}`, {\n method: \"PUT\",\n headers: {\n authorization: header,\n \"content-type\": contentType,\n },\n body,\n });\n } catch (cause) {\n throw new StorageError(\n `vana-storage upload network error: ${describe(cause)}`,\n \"UPLOAD_ERROR\",\n \"vana-storage\",\n { cause: cause instanceof Error ? cause : undefined },\n );\n }\n\n if (!response.ok) {\n throw new StorageError(\n `vana-storage upload failed: ${response.status} ${response.statusText} - ${await safeText(response)}`,\n \"UPLOAD_FAILED\",\n \"vana-storage\",\n );\n }\n\n const result = (await response.json()) as VanaStorageUploadResponse;\n return {\n url: result.url,\n size: result.size,\n contentType,\n metadata: { key: result.key, etag: result.etag },\n };\n }\n\n /**\n * Download a blob by URL. The URL must point at a path under this\n * provider's endpoint.\n */\n async download(url: string): Promise<Blob> {\n const path = this.pathFromUrl(url);\n const header = await this.signRequest(\"GET\", path);\n\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.endpoint}${path}`, {\n method: \"GET\",\n headers: { authorization: header },\n });\n } catch (cause) {\n throw new StorageError(\n `vana-storage download network error: ${describe(cause)}`,\n \"DOWNLOAD_ERROR\",\n \"vana-storage\",\n { cause: cause instanceof Error ? cause : undefined },\n );\n }\n\n if (!response.ok) {\n throw new StorageError(\n `vana-storage download failed: ${response.status} ${response.statusText}`,\n \"DOWNLOAD_FAILED\",\n \"vana-storage\",\n );\n }\n\n return await response.blob();\n }\n\n /**\n * Listing is not supported by vana-storage — file discovery is handled by\n * the Gateway DataRegistry, not the storage layer.\n */\n async list(_options?: StorageListOptions): Promise<StorageFile[]> {\n throw new StorageError(\n \"list is not supported by vana-storage; query the Gateway DataRegistry instead\",\n \"NOT_IMPLEMENTED\",\n \"vana-storage\",\n );\n }\n\n async delete(url: string): Promise<boolean> {\n const path = this.pathFromUrl(url);\n const header = await this.signRequest(\"DELETE\", path);\n\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.endpoint}${path}`, {\n method: \"DELETE\",\n headers: { authorization: header },\n });\n } catch (cause) {\n throw new StorageError(\n `vana-storage delete network error: ${describe(cause)}`,\n \"DELETE_ERROR\",\n \"vana-storage\",\n { cause: cause instanceof Error ? cause : undefined },\n );\n }\n\n if (response.status === 404) return false;\n if (!response.ok) {\n throw new StorageError(\n `vana-storage delete failed: ${response.status} ${response.statusText}`,\n \"DELETE_FAILED\",\n \"vana-storage\",\n );\n }\n return true;\n }\n\n getConfig(): StorageProviderConfig {\n return {\n name: \"vana-storage\",\n type: \"vana-storage\",\n requiresAuth: true,\n features: {\n upload: true,\n download: true,\n list: false,\n delete: true,\n },\n };\n }\n\n private async signRequest(\n method: \"GET\" | \"PUT\" | \"DELETE\",\n path: string,\n body?: Uint8Array,\n ): Promise<string> {\n const now = Math.floor(Date.now() / 1000);\n return buildWeb3SignedHeader({\n signMessage: this.signer.signMessage,\n aud: this.endpoint,\n method,\n uri: path,\n iat: now,\n exp: now + DEFAULT_TOKEN_TTL_SECONDS,\n ...(body !== undefined && body.length > 0 && { body }),\n });\n }\n\n private pathFromUrl(url: string): string {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new StorageError(\n `Invalid URL: ${url}`,\n \"INVALID_URL\",\n \"vana-storage\",\n );\n }\n const expectedHost = new URL(this.endpoint).host;\n if (parsed.host !== expectedHost) {\n throw new StorageError(\n `URL host '${parsed.host}' does not match storage endpoint '${expectedHost}'`,\n \"INVALID_URL\",\n \"vana-storage\",\n );\n }\n // Restrict to /v1/blobs/{owner}/{scope}/{collectedAt} so a caller\n // cannot induce this wallet to sign arbitrary same-host paths.\n const segments = parsed.pathname.split(\"/\").filter((s) => s.length > 0);\n const isTraversal = (s: string): boolean => s === \".\" || s === \"..\";\n const valid =\n segments.length === 5 &&\n segments[0] === \"v1\" &&\n segments[1] === \"blobs\" &&\n segments[2]?.toLowerCase() === this.ownerAddress &&\n segments[3] !== undefined &&\n !isTraversal(segments[3]) &&\n segments[4] !== undefined &&\n !isTraversal(segments[4]);\n if (!valid) {\n throw new StorageError(\n `URL path '${parsed.pathname}' must be /v1/blobs/${this.ownerAddress}/{scope}/{collectedAt}`,\n \"INVALID_URL\",\n \"vana-storage\",\n );\n }\n return parsed.pathname;\n }\n}\n\nfunction encodeRelativePath(filename: string): string {\n const parts = filename.split(\"/\");\n if (\n parts.length !== 2 ||\n parts.some((p) => p.length === 0 || p === \".\" || p === \"..\")\n ) {\n throw new StorageError(\n `filename must be exactly '{scope}/{collectedAt}' with non-empty segments, got '${filename}'`,\n \"INVALID_FILENAME\",\n \"vana-storage\",\n );\n }\n return parts.map((p) => encodeURIComponent(p)).join(\"/\");\n}\n\nfunction describe(value: unknown): string {\n if (value instanceof Error) return value.message;\n return String(value);\n}\n\nasync function safeText(response: Response): Promise<string> {\n try {\n return await response.text();\n } catch {\n return \"\";\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/storage/providers/vana-storage.ts"],"sourcesContent":["import {\n StorageError,\n type StorageProvider,\n type StorageUploadResult,\n type StorageFile,\n type StorageListOptions,\n type StorageProviderConfig,\n} from \"../index\";\nimport {\n buildWeb3SignedHeader,\n type Web3SignedSignFn,\n} from \"../../auth/web3-signed-builder\";\n\nconst DEFAULT_ENDPOINT = \"https://storage.vana.org\";\nconst BLOB_PATH_PREFIX = \"/v1/blobs\";\nconst DEFAULT_TOKEN_TTL_SECONDS = 300;\nconst MAX_UPLOAD_ATTEMPTS = 4;\nconst MAX_RATE_LIMIT_DELAY_MS = 30_000;\n\n/**\n * Wallet-style signer used by {@link VanaStorage} to authenticate every\n * request. For Personal Server flows this can be a registered server wallet\n * signing requests for the owner's storage namespace.\n *\n * @category Storage\n */\nexport interface VanaStorageSigner {\n /** EIP-191 address (`0x...`). */\n address: `0x${string}`;\n /** EIP-191 personal_sign callback (e.g. viem `account.signMessage`). */\n signMessage: Web3SignedSignFn;\n}\n\n/**\n * Configuration for {@link VanaStorage}.\n *\n * @category Storage\n */\nexport interface VanaStorageConfig {\n /**\n * Base URL of the vana-storage Worker. Defaults to `https://storage.vana.org`.\n */\n endpoint?: string;\n /**\n * Wallet signer used to authenticate writes and reads.\n */\n signer: VanaStorageSigner;\n /**\n * Owner namespace under which blobs are stored. Defaults to the signer address.\n */\n ownerAddress?: `0x${string}`;\n /**\n * Optional `fetch` implementation. Defaults to the global `fetch`.\n * Useful for tests and for environments that need a custom HTTP client.\n */\n fetchImpl?: typeof fetch;\n}\n\ninterface VanaStorageUploadResponse {\n key: string;\n url: string;\n etag: string;\n size: number;\n}\n\n/**\n * Storage provider that talks to the vana-storage Worker\n * (`https://storage.vana.org` by default). All requests are authenticated\n * with Web3Signed headers signed by the configured wallet.\n *\n * @remarks\n * Filenames passed to {@link VanaStorage.upload} must be of the form\n * `\"{scope}/{collectedAt}\"` (e.g. `\"instagram.profile/2026-05-08T20:00:00.000Z\"`).\n * The owner address is prepended automatically to produce the canonical\n * blob path `/v1/blobs/{owner}/{scope}/{collectedAt}`.\n *\n * @category Storage\n *\n * @example\n * ```typescript\n * import { privateKeyToAccount } from \"viem/accounts\";\n * import { VanaStorage } from \"@opendatalabs/vana-sdk/node\";\n *\n * const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);\n * const storage = new VanaStorage({\n * signer: {\n * address: account.address,\n * signMessage: (msg) => account.signMessage({ message: msg }),\n * },\n * });\n *\n * const result = await storage.upload(\n * new Blob([ciphertext]),\n * \"instagram.profile/2026-05-08T20:00:00.000Z\",\n * );\n * ```\n */\nexport class VanaStorage implements StorageProvider {\n private readonly endpoint: string;\n private readonly signer: VanaStorageSigner;\n private readonly ownerAddress: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(config: VanaStorageConfig) {\n if (!config?.signer?.address || !config?.signer?.signMessage) {\n throw new StorageError(\n \"VanaStorage requires a signer with address and signMessage\",\n \"MISSING_SIGNER\",\n \"vana-storage\",\n );\n }\n this.endpoint = (config.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/+$/, \"\");\n this.signer = config.signer;\n this.ownerAddress = (\n config.ownerAddress ?? config.signer.address\n ).toLowerCase();\n this.fetchImpl = config.fetchImpl ?? globalThis.fetch.bind(globalThis);\n }\n\n /**\n * Upload an encrypted blob to vana-storage.\n *\n * @param file - The blob to upload.\n * @param filename - Required relative key in the form `\"{scope}/{collectedAt}\"`.\n * The owner address is prepended automatically.\n */\n async upload(file: Blob, filename?: string): Promise<StorageUploadResult> {\n if (!filename) {\n throw new StorageError(\n \"VanaStorage.upload requires a filename of the form '{scope}/{collectedAt}'\",\n \"MISSING_FILENAME\",\n \"vana-storage\",\n );\n }\n\n const subpath = encodeRelativePath(filename);\n const path = `${BLOB_PATH_PREFIX}/${this.ownerAddress}/${subpath}`;\n const body = new Uint8Array(await file.arrayBuffer());\n const contentType =\n file.type !== \"\" ? file.type : \"application/octet-stream\";\n\n const header = await this.signRequest(\"PUT\", path, body);\n\n let response: Response | null = null;\n let responseText = \"\";\n for (let attempt = 1; attempt <= MAX_UPLOAD_ATTEMPTS; attempt++) {\n try {\n response = await this.fetchImpl(`${this.endpoint}${path}`, {\n method: \"PUT\",\n headers: {\n authorization: header,\n \"content-type\": contentType,\n },\n body,\n });\n } catch (cause) {\n throw new StorageError(\n `vana-storage upload network error: ${describe(cause)}`,\n \"UPLOAD_ERROR\",\n \"vana-storage\",\n { cause: cause instanceof Error ? cause : undefined },\n );\n }\n\n if (response.ok) {\n break;\n }\n\n responseText = await safeText(response);\n if (response.status === 429 && attempt < MAX_UPLOAD_ATTEMPTS) {\n const delayMs = retryDelayMs(response, responseText);\n if (delayMs > 0) {\n await sleep(delayMs);\n }\n continue;\n }\n\n throw new StorageError(\n `vana-storage upload failed: ${response.status} ${response.statusText} - ${responseText}`,\n \"UPLOAD_FAILED\",\n \"vana-storage\",\n );\n }\n\n if (!response?.ok) {\n throw new StorageError(\n `vana-storage upload failed after ${MAX_UPLOAD_ATTEMPTS} attempts - ${responseText}`,\n \"UPLOAD_FAILED\",\n \"vana-storage\",\n );\n }\n\n const result = (await response.json()) as VanaStorageUploadResponse;\n return {\n url: result.url,\n size: result.size,\n contentType,\n metadata: { key: result.key, etag: result.etag },\n };\n }\n\n /**\n * Download a blob by URL. The URL must point at a path under this\n * provider's endpoint.\n */\n async download(url: string): Promise<Blob> {\n const path = this.pathFromUrl(url);\n const header = await this.signRequest(\"GET\", path);\n\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.endpoint}${path}`, {\n method: \"GET\",\n headers: { authorization: header },\n });\n } catch (cause) {\n throw new StorageError(\n `vana-storage download network error: ${describe(cause)}`,\n \"DOWNLOAD_ERROR\",\n \"vana-storage\",\n { cause: cause instanceof Error ? cause : undefined },\n );\n }\n\n if (!response.ok) {\n throw new StorageError(\n `vana-storage download failed: ${response.status} ${response.statusText}`,\n \"DOWNLOAD_FAILED\",\n \"vana-storage\",\n );\n }\n\n return await response.blob();\n }\n\n /**\n * Listing is not supported by vana-storage — file discovery is handled by\n * the Gateway DataRegistry, not the storage layer.\n */\n async list(_options?: StorageListOptions): Promise<StorageFile[]> {\n throw new StorageError(\n \"list is not supported by vana-storage; query the Gateway DataRegistry instead\",\n \"NOT_IMPLEMENTED\",\n \"vana-storage\",\n );\n }\n\n async delete(url: string): Promise<boolean> {\n const path = this.pathFromUrl(url);\n const header = await this.signRequest(\"DELETE\", path);\n\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.endpoint}${path}`, {\n method: \"DELETE\",\n headers: { authorization: header },\n });\n } catch (cause) {\n throw new StorageError(\n `vana-storage delete network error: ${describe(cause)}`,\n \"DELETE_ERROR\",\n \"vana-storage\",\n { cause: cause instanceof Error ? cause : undefined },\n );\n }\n\n if (response.status === 404) return false;\n if (!response.ok) {\n throw new StorageError(\n `vana-storage delete failed: ${response.status} ${response.statusText}`,\n \"DELETE_FAILED\",\n \"vana-storage\",\n );\n }\n return true;\n }\n\n getConfig(): StorageProviderConfig {\n return {\n name: \"vana-storage\",\n type: \"vana-storage\",\n requiresAuth: true,\n features: {\n upload: true,\n download: true,\n list: false,\n delete: true,\n },\n };\n }\n\n private async signRequest(\n method: \"GET\" | \"PUT\" | \"DELETE\",\n path: string,\n body?: Uint8Array,\n ): Promise<string> {\n const now = Math.floor(Date.now() / 1000);\n return buildWeb3SignedHeader({\n signMessage: this.signer.signMessage,\n aud: this.endpoint,\n method,\n uri: path,\n iat: now,\n exp: now + DEFAULT_TOKEN_TTL_SECONDS,\n ...(body !== undefined && body.length > 0 && { body }),\n });\n }\n\n private pathFromUrl(url: string): string {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new StorageError(\n `Invalid URL: ${url}`,\n \"INVALID_URL\",\n \"vana-storage\",\n );\n }\n const expectedHost = new URL(this.endpoint).host;\n if (parsed.host !== expectedHost) {\n throw new StorageError(\n `URL host '${parsed.host}' does not match storage endpoint '${expectedHost}'`,\n \"INVALID_URL\",\n \"vana-storage\",\n );\n }\n // Restrict to /v1/blobs/{owner}/{scope}/{collectedAt} so a caller\n // cannot induce this wallet to sign arbitrary same-host paths.\n const segments = parsed.pathname.split(\"/\").filter((s) => s.length > 0);\n const isTraversal = (s: string): boolean => s === \".\" || s === \"..\";\n const valid =\n segments.length === 5 &&\n segments[0] === \"v1\" &&\n segments[1] === \"blobs\" &&\n segments[2]?.toLowerCase() === this.ownerAddress &&\n segments[3] !== undefined &&\n !isTraversal(segments[3]) &&\n segments[4] !== undefined &&\n !isTraversal(segments[4]);\n if (!valid) {\n throw new StorageError(\n `URL path '${parsed.pathname}' must be /v1/blobs/${this.ownerAddress}/{scope}/{collectedAt}`,\n \"INVALID_URL\",\n \"vana-storage\",\n );\n }\n return parsed.pathname;\n }\n}\n\nfunction encodeRelativePath(filename: string): string {\n const parts = filename.split(\"/\");\n if (\n parts.length !== 2 ||\n parts.some((p) => p.length === 0 || p === \".\" || p === \"..\")\n ) {\n throw new StorageError(\n `filename must be exactly '{scope}/{collectedAt}' with non-empty segments, got '${filename}'`,\n \"INVALID_FILENAME\",\n \"vana-storage\",\n );\n }\n return parts.map((p) => encodeURIComponent(p)).join(\"/\");\n}\n\nfunction describe(value: unknown): string {\n if (value instanceof Error) return value.message;\n return String(value);\n}\n\nfunction retryDelayMs(response: Response, responseText: string): number {\n const headerDelayMs = parseRetryAfterHeaderMs(\n response.headers.get(\"retry-after\"),\n );\n if (headerDelayMs !== null) {\n return clampRateLimitDelay(headerDelayMs);\n }\n\n return clampRateLimitDelay(parseRetryAfterBodyMs(responseText) ?? 0);\n}\n\nfunction parseRetryAfterHeaderMs(value: string | null): number | null {\n if (!value) return null;\n\n const seconds = Number(value);\n if (Number.isFinite(seconds)) {\n return seconds * 1000;\n }\n\n const dateMs = Date.parse(value);\n if (Number.isFinite(dateMs)) {\n return Math.max(0, dateMs - Date.now());\n }\n\n return null;\n}\n\nfunction parseRetryAfterBodyMs(responseText: string): number | null {\n if (!responseText) return null;\n\n try {\n const parsed = JSON.parse(responseText) as { retryAfter?: unknown };\n const seconds = Number(parsed.retryAfter);\n return Number.isFinite(seconds) ? seconds * 1000 : null;\n } catch {\n return null;\n }\n}\n\nfunction clampRateLimitDelay(delayMs: number): number {\n return Math.min(Math.max(0, delayMs), MAX_RATE_LIMIT_DELAY_MS);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function safeText(response: Response): Promise<string> {\n try {\n return await response.text();\n } catch {\n return \"\";\n }\n}\n"],"mappings":"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","names":[]}
|