@opendatalabs/vana-sdk 3.13.4 → 3.14.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.
Files changed (116) hide show
  1. package/dist/account/personal-server-lite-owner-binding.d.ts +1 -1
  2. package/dist/account/personal-server-registration.d.ts +1 -1
  3. package/dist/auth/errors.d.ts +1 -1
  4. package/dist/auth/oauth-client.d.ts +1 -1
  5. package/dist/browser.d.ts +1 -1
  6. package/dist/chains/index.d.ts +2 -2
  7. package/dist/chains.browser.d.ts +2 -2
  8. package/dist/chains.d.ts +2 -2
  9. package/dist/chains.node.d.ts +2 -2
  10. package/dist/config/contracts.config.cjs +1 -1
  11. package/dist/config/contracts.config.cjs.map +1 -1
  12. package/dist/config/contracts.config.js +1 -1
  13. package/dist/config/contracts.config.js.map +1 -1
  14. package/dist/contracts/contractController.d.ts +4 -4
  15. package/dist/core/client.d.ts +1 -1
  16. package/dist/crypto/ecies/base.d.ts +1 -1
  17. package/dist/crypto/ecies/browser.d.ts +1 -1
  18. package/dist/crypto/ecies/index.d.ts +2 -2
  19. package/dist/crypto/ecies/node.d.ts +1 -1
  20. package/dist/crypto/services/WalletKeyEncryptionService.d.ts +1 -1
  21. package/dist/direct/access-request-client.cjs +3 -1
  22. package/dist/direct/access-request-client.cjs.map +1 -1
  23. package/dist/direct/access-request-client.d.ts +2 -2
  24. package/dist/direct/access-request-client.js +3 -1
  25. package/dist/direct/access-request-client.js.map +1 -1
  26. package/dist/direct/connect-flow.d.ts +1 -1
  27. package/dist/direct/controller.cjs +72 -32
  28. package/dist/direct/controller.cjs.map +1 -1
  29. package/dist/direct/controller.d.ts +48 -10
  30. package/dist/direct/controller.js +77 -33
  31. package/dist/direct/controller.js.map +1 -1
  32. package/dist/direct/endpoints.d.ts +1 -1
  33. package/dist/direct/errors.cjs +11 -2
  34. package/dist/direct/errors.cjs.map +1 -1
  35. package/dist/direct/errors.d.ts +14 -1
  36. package/dist/direct/errors.js +9 -1
  37. package/dist/direct/errors.js.map +1 -1
  38. package/dist/direct/escrow-payment.cjs +147 -12
  39. package/dist/direct/escrow-payment.cjs.map +1 -1
  40. package/dist/direct/escrow-payment.d.ts +65 -12
  41. package/dist/direct/escrow-payment.js +143 -12
  42. package/dist/direct/escrow-payment.js.map +1 -1
  43. package/dist/direct/personal-server-read.cjs +164 -23
  44. package/dist/direct/personal-server-read.cjs.map +1 -1
  45. package/dist/direct/personal-server-read.d.ts +28 -18
  46. package/dist/direct/personal-server-read.js +167 -25
  47. package/dist/direct/personal-server-read.js.map +1 -1
  48. package/dist/direct/types.cjs.map +1 -1
  49. package/dist/direct/types.d.ts +81 -21
  50. package/dist/direct/types.js.map +1 -1
  51. package/dist/direct/use-direct-vana-connect.d.ts +1 -1
  52. package/dist/generated/abi/index.d.ts +25 -25
  53. package/dist/generated/addresses.cjs +6 -6
  54. package/dist/generated/addresses.cjs.map +1 -1
  55. package/dist/generated/addresses.d.ts +6 -6
  56. package/dist/generated/addresses.js +6 -6
  57. package/dist/generated/addresses.js.map +1 -1
  58. package/dist/index.browser.d.ts +44 -44
  59. package/dist/index.browser.js +6 -6
  60. package/dist/index.browser.js.map +2 -2
  61. package/dist/index.node.cjs +498 -6
  62. package/dist/index.node.cjs.map +4 -4
  63. package/dist/index.node.d.ts +48 -45
  64. package/dist/index.node.js +486 -6
  65. package/dist/index.node.js.map +4 -4
  66. package/dist/node.d.ts +1 -1
  67. package/dist/platform/browser-only.d.ts +3 -3
  68. package/dist/platform/browser-safe.d.ts +3 -3
  69. package/dist/platform/browser.d.ts +1 -1
  70. package/dist/platform/index.d.ts +5 -5
  71. package/dist/platform/node.d.ts +1 -1
  72. package/dist/platform/ports/openpgp-port.d.ts +1 -1
  73. package/dist/platform/utils.d.ts +1 -1
  74. package/dist/platform.browser.d.ts +4 -4
  75. package/dist/platform.d.ts +5 -5
  76. package/dist/platform.node.d.ts +5 -5
  77. package/dist/protocol/data-point-status.d.ts +1 -1
  78. package/dist/protocol/eip712.cjs.map +1 -1
  79. package/dist/protocol/eip712.js.map +1 -1
  80. package/dist/protocol/escrow-deposit.d.ts +1 -1
  81. package/dist/protocol/escrow.cjs.map +1 -1
  82. package/dist/protocol/escrow.d.ts +11 -4
  83. package/dist/protocol/escrow.js.map +1 -1
  84. package/dist/protocol/fee-registry.d.ts +1 -1
  85. package/dist/protocol/gateway.cjs.map +1 -1
  86. package/dist/protocol/gateway.js.map +1 -1
  87. package/dist/protocol/grants.d.ts +1 -1
  88. package/dist/protocol/personal-server-data.d.ts +2 -2
  89. package/dist/protocol/personal-server-registration.d.ts +2 -2
  90. package/dist/react.d.ts +3 -3
  91. package/dist/server.cjs +12 -0
  92. package/dist/server.cjs.map +1 -1
  93. package/dist/server.d.ts +8 -8
  94. package/dist/server.js +12 -0
  95. package/dist/server.js.map +1 -1
  96. package/dist/session-relay/client.d.ts +1 -1
  97. package/dist/session-relay/endpoints.d.ts +1 -1
  98. package/dist/session-relay/errors.d.ts +1 -1
  99. package/dist/session-relay/index.d.ts +5 -5
  100. package/dist/session-relay/signing.d.ts +1 -1
  101. package/dist/session-relay/types.d.ts +1 -1
  102. package/dist/session-relay.d.ts +5 -5
  103. package/dist/storage/default.d.ts +2 -2
  104. package/dist/storage/index.d.ts +16 -16
  105. package/dist/storage/manager.d.ts +1 -1
  106. package/dist/storage/providers/callback-storage.d.ts +2 -2
  107. package/dist/storage/providers/dropbox.d.ts +1 -1
  108. package/dist/storage/providers/google-drive.d.ts +1 -1
  109. package/dist/storage/providers/ipfs.d.ts +1 -1
  110. package/dist/storage/providers/pinata.d.ts +1 -1
  111. package/dist/storage/providers/r2.d.ts +1 -1
  112. package/dist/storage/providers/vana-storage.d.ts +3 -3
  113. package/dist/types/config.d.ts +1 -1
  114. package/dist/types/index.d.ts +6 -6
  115. package/dist/types.d.ts +1 -1
  116. package/package.json +13 -1
@@ -18,17 +18,22 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var escrow_payment_exports = {};
20
20
  __export(escrow_payment_exports, {
21
+ DATA_ACCESS_OP_TYPE: () => DATA_ACCESS_OP_TYPE,
21
22
  GRANT_OP_TYPE: () => GRANT_OP_TYPE,
23
+ authorizeEscrowPayment: () => authorizeEscrowPayment,
22
24
  authorizeGrantPayment: () => authorizeGrantPayment,
25
+ buildEscrowPaymentHeader: () => buildEscrowPaymentHeader,
23
26
  buildGrantPaymentHeader: () => buildGrantPaymentHeader,
24
27
  createDefaultNonceSource: () => createDefaultNonceSource,
25
28
  paymentReceiptFromHeader: () => paymentReceiptFromHeader,
29
+ paymentResponseMetadataFromHeader: () => paymentResponseMetadataFromHeader,
26
30
  toDirectFeeBreakdown: () => toDirectFeeBreakdown,
27
31
  toDirectPaymentReceipt: () => toDirectPaymentReceipt
28
32
  });
29
33
  module.exports = __toCommonJS(escrow_payment_exports);
30
34
  var import_escrow = require("../protocol/escrow");
31
35
  const GRANT_OP_TYPE = "grant";
36
+ const DATA_ACCESS_OP_TYPE = "data_access";
32
37
  function toDirectFeeBreakdown(breakdown) {
33
38
  return {
34
39
  registrationFee: breakdown.registrationFee,
@@ -57,6 +62,58 @@ function createDefaultNonceSource() {
57
62
  };
58
63
  }
59
64
  const processLocalNonceSource = createDefaultNonceSource();
65
+ const UINT256_MAX = (1n << 256n) - 1n;
66
+ const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
67
+ const BYTES32_RE = /^0x[0-9a-fA-F]{64}$/;
68
+ const SIGNATURE_RE = /^0x[0-9a-fA-F]{130}$/;
69
+ function isUint256Decimal(value, allowZero) {
70
+ const pattern = allowZero ? /^(0|[1-9]\d*)$/ : /^[1-9]\d*$/;
71
+ return value.length <= UINT256_MAX.toString().length && pattern.test(value) && BigInt(value) <= UINT256_MAX;
72
+ }
73
+ function isValidAccessRecord(record) {
74
+ return BYTES32_RE.test(record.dataPointId) && isUint256Decimal(record.version, false) && ADDRESS_RE.test(record.accessor) && BYTES32_RE.test(record.recordId) && SIGNATURE_RE.test(record.signature);
75
+ }
76
+ function validateSigningOperation(payerAddress, required) {
77
+ if (!ADDRESS_RE.test(payerAddress)) {
78
+ throw new Error("Payment payer must be a 20-byte EVM address");
79
+ }
80
+ if (!BYTES32_RE.test(required.opId)) {
81
+ throw new Error("Payment operation id must be a 32-byte hex value");
82
+ }
83
+ if (!ADDRESS_RE.test(required.asset || import_escrow.NATIVE_ASSET_ADDRESS)) {
84
+ throw new Error("Payment asset must be a 20-byte EVM address");
85
+ }
86
+ if (!isUint256Decimal(required.amount, true)) {
87
+ throw new Error("Payment amount must be a canonical uint256 decimal");
88
+ }
89
+ if (required.paymentNonce !== void 0 && !isUint256Decimal(required.paymentNonce, false)) {
90
+ throw new Error("Payment nonce must be a positive uint256 decimal");
91
+ }
92
+ const accessRecord = required.accessRecord;
93
+ if (required.opType === DATA_ACCESS_OP_TYPE) {
94
+ if (!accessRecord || !isValidAccessRecord(accessRecord)) {
95
+ throw new Error("Data-access payment requires a valid access record");
96
+ }
97
+ if (required.opId.toLowerCase() !== accessRecord.recordId.toLowerCase()) {
98
+ throw new Error(
99
+ "Data-access payment operation id must equal the access record id"
100
+ );
101
+ }
102
+ if (accessRecord.accessor.toLowerCase() !== payerAddress.toLowerCase()) {
103
+ throw new Error(
104
+ "Data-access payment accessor must equal the payment payer address"
105
+ );
106
+ }
107
+ return;
108
+ }
109
+ if (required.amount === "0") {
110
+ if (!accessRecord || !isValidAccessRecord(accessRecord) || accessRecord.accessor.toLowerCase() !== payerAddress.toLowerCase()) {
111
+ throw new Error(
112
+ "Zero-amount grant payments require a valid access record for the payer"
113
+ );
114
+ }
115
+ }
116
+ }
60
117
  function base64EncodeJson(value) {
61
118
  const bytes = new TextEncoder().encode(JSON.stringify(value));
62
119
  let binary = "";
@@ -68,18 +125,25 @@ function base64DecodeJson(value) {
68
125
  const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
69
126
  return JSON.parse(new TextDecoder().decode(bytes));
70
127
  }
71
- async function signGrantPayment(params) {
128
+ async function signEscrowPayment(params) {
72
129
  const { payerAddress, required, config } = params;
130
+ validateSigningOperation(payerAddress, required);
73
131
  const nonceSource = config.nonceSource ?? processLocalNonceSource;
74
132
  const paymentNonce = BigInt(
75
133
  required.paymentNonce ?? await nonceSource(payerAddress)
76
134
  );
77
135
  const asset = required.asset || import_escrow.NATIVE_ASSET_ADDRESS;
78
- const opId = required.grantId;
136
+ const opId = required.opId;
79
137
  const amount = BigInt(required.amount);
138
+ if (amount < 0n || amount > UINT256_MAX) {
139
+ throw new Error("Payment amount must be a uint256");
140
+ }
141
+ if (paymentNonce <= 0n || paymentNonce > UINT256_MAX) {
142
+ throw new Error("Payment nonce must be a positive uint256");
143
+ }
80
144
  const message = {
81
145
  payerAddress,
82
- opType: GRANT_OP_TYPE,
146
+ opType: required.opType,
83
147
  opId,
84
148
  asset,
85
149
  amount,
@@ -101,46 +165,117 @@ async function signGrantPayment(params) {
101
165
  ...required.accessRecord ? { accessRecord: required.accessRecord } : {}
102
166
  };
103
167
  }
104
- async function buildGrantPaymentHeader(params) {
105
- const signed = await signGrantPayment(params);
168
+ async function buildEscrowPaymentHeader(params) {
169
+ const network = params.required.network ?? `vana:${params.config.chainId}`;
170
+ if (network !== `vana:${params.config.chainId}`) {
171
+ throw new Error("Payment network must match the configured chain");
172
+ }
173
+ const signed = await signEscrowPayment(params);
106
174
  const payment = {
107
175
  x402Version: 1,
108
176
  scheme: "vana-escrow-grant",
109
- network: params.required.network ?? `vana:${params.config.chainId}`,
177
+ network,
110
178
  payload: signed
111
179
  };
112
180
  return base64EncodeJson(payment);
113
181
  }
114
- function paymentReceiptFromHeader(header) {
182
+ async function buildGrantPaymentHeader(params) {
183
+ return buildEscrowPaymentHeader({
184
+ ...params,
185
+ required: {
186
+ ...params.required,
187
+ opType: GRANT_OP_TYPE,
188
+ opId: params.required.grantId
189
+ }
190
+ });
191
+ }
192
+ function asRecord(value) {
193
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
194
+ }
195
+ function stringField(value, key) {
196
+ const field = value?.[key];
197
+ return typeof field === "string" ? field : void 0;
198
+ }
199
+ function isCanonicalIsoTimestamp(value) {
200
+ try {
201
+ return new Date(value).toISOString() === value;
202
+ } catch {
203
+ return false;
204
+ }
205
+ }
206
+ function paymentResponseMetadataFromHeader(header) {
115
207
  if (!header) return void 0;
116
208
  try {
117
- return toDirectPaymentReceipt(base64DecodeJson(header));
209
+ const result = asRecord(base64DecodeJson(header));
210
+ const breakdown = asRecord(result?.breakdown);
211
+ const opType = stringField(result, "opType");
212
+ const opId = stringField(result, "opId");
213
+ const payerAddress = stringField(result, "payerAddress");
214
+ const asset = stringField(result, "asset");
215
+ const amount = stringField(result, "amount");
216
+ const paymentNonce = stringField(result, "paymentNonce");
217
+ const registrationFee = stringField(breakdown, "registrationFee");
218
+ const dataAccessFee = stringField(breakdown, "dataAccessFee");
219
+ const paidAt = stringField(result, "paidAt");
220
+ if (result?.success !== true || !opType || !opId || !BYTES32_RE.test(opId) || !payerAddress || !ADDRESS_RE.test(payerAddress) || !asset || !ADDRESS_RE.test(asset) || !amount || !isUint256Decimal(amount, true) || !paymentNonce || !isUint256Decimal(paymentNonce, false) || !registrationFee || !isUint256Decimal(registrationFee, true) || !dataAccessFee || !isUint256Decimal(dataAccessFee, true) || typeof breakdown?.registrationPaid !== "boolean" || !paidAt || !isCanonicalIsoTimestamp(paidAt)) {
221
+ return void 0;
222
+ }
223
+ return {
224
+ opType,
225
+ opId,
226
+ asset,
227
+ amount,
228
+ paymentNonce,
229
+ breakdown: {
230
+ registrationFee,
231
+ dataAccessFee,
232
+ registrationPaid: breakdown.registrationPaid
233
+ },
234
+ paidAt
235
+ };
118
236
  } catch {
119
237
  return void 0;
120
238
  }
121
239
  }
240
+ function paymentReceiptFromHeader(header) {
241
+ return paymentResponseMetadataFromHeader(header);
242
+ }
122
243
  async function authorizeGrantPayment(params) {
123
- const { payerAddress, required, config } = params;
124
- const signed = await signGrantPayment(params);
244
+ return authorizeEscrowPayment({
245
+ ...params,
246
+ required: {
247
+ ...params.required,
248
+ opType: GRANT_OP_TYPE,
249
+ opId: params.required.grantId
250
+ }
251
+ });
252
+ }
253
+ async function authorizeEscrowPayment(params) {
254
+ const { payerAddress, config } = params;
255
+ const signed = await signEscrowPayment(params);
125
256
  const result = await config.client.payForOp({
126
257
  payerAddress,
127
- opType: GRANT_OP_TYPE,
258
+ opType: signed.message.opType,
128
259
  opId: signed.message.opId,
129
260
  asset: signed.message.asset,
130
261
  amount: signed.message.amount,
131
262
  paymentNonce: signed.message.paymentNonce,
132
263
  signature: signed.signature,
133
- accessRecord: required.accessRecord
264
+ accessRecord: signed.accessRecord
134
265
  });
135
266
  return toDirectPaymentReceipt(result);
136
267
  }
137
268
  // Annotate the CommonJS export names for ESM import in node:
138
269
  0 && (module.exports = {
270
+ DATA_ACCESS_OP_TYPE,
139
271
  GRANT_OP_TYPE,
272
+ authorizeEscrowPayment,
140
273
  authorizeGrantPayment,
274
+ buildEscrowPaymentHeader,
141
275
  buildGrantPaymentHeader,
142
276
  createDefaultNonceSource,
143
277
  paymentReceiptFromHeader,
278
+ paymentResponseMetadataFromHeader,
144
279
  toDirectFeeBreakdown,
145
280
  toDirectPaymentReceipt
146
281
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/direct/escrow-payment.ts"],"sourcesContent":["/**\n * Escrow-backed payment authorization for the Direct Data Controller.\n *\n * @remarks\n * Builds on the DPv2 escrow surface added in `protocol/escrow`. When a Personal\n * Server read returns `402 Payment Required`, the controller settles the\n * grant's data-access fee through the escrow gateway:\n *\n * 1. Sign a `GenericPayment` EIP-712 message (op `\"grant\"`, opId = grantId)\n * with the app key.\n * 2. POST it to the gateway's `/v1/escrow/pay` via {@link EscrowGatewayClient}.\n * 3. Map the gateway's {@link EscrowPayResult} into a typed\n * {@link DirectPaymentReceipt} for the caller to inspect.\n *\n * This module adapts the escrow `payForOp` flow to the direct-read use case; it\n * does not define its own payment scheme.\n *\n * @category Direct\n * @module direct/escrow-payment\n */\n\nimport {\n GENERIC_PAYMENT_TYPES,\n NATIVE_ASSET_ADDRESS,\n genericPaymentDomain,\n type EscrowAccessRecord,\n type EscrowGatewayClient,\n type EscrowPayResult,\n type PaymentBreakdown,\n} from \"../protocol/escrow\";\nimport type {\n DirectFeeBreakdown,\n DirectPaymentReceipt,\n PersonalServerPaymentRequired,\n} from \"./types\";\n\n/** The escrow `GenericPayment.opType` used for grant-lifecycle payments. */\nexport const GRANT_OP_TYPE = \"grant\" as const;\n\n/**\n * EIP-712 typed-data signer (e.g. viem `account.signTypedData`).\n *\n * @remarks\n * Kept structurally minimal so any viem account/wallet client satisfies it\n * without the SDK depending on viem's exact `signTypedData` overload set.\n */\nexport type SignTypedDataFn = (args: {\n domain: ReturnType<typeof genericPaymentDomain>;\n types: typeof GENERIC_PAYMENT_TYPES;\n primaryType: \"GenericPayment\";\n message: {\n payerAddress: `0x${string}`;\n opType: string;\n opId: `0x${string}`;\n asset: `0x${string}`;\n amount: bigint;\n paymentNonce: bigint;\n };\n}) => Promise<`0x${string}`>;\n\n/** Supplies a monotonically-increasing payment nonce per payer. */\nexport type PaymentNonceSource = (\n payerAddress: string,\n) => Promise<bigint> | bigint;\n\ninterface GrantPaymentMessage {\n payerAddress: `0x${string}`;\n opType: typeof GRANT_OP_TYPE;\n opId: `0x${string}`;\n asset: `0x${string}`;\n amount: string;\n paymentNonce: string;\n}\n\ninterface SignedGrantPayment {\n message: GrantPaymentMessage;\n signature: `0x${string}`;\n accessRecord?: EscrowAccessRecord;\n}\n\ninterface X402PaymentHeader {\n x402Version: 1;\n scheme: \"vana-escrow-grant\";\n network: string;\n payload: SignedGrantPayment;\n}\n\n/** Escrow settlement configuration for the controller. */\nexport interface EscrowPaymentConfig {\n /** Client for the gateway escrow endpoints (`/v1/escrow/*`). */\n client: EscrowGatewayClient;\n /** Deployed `DataPortabilityEscrow` contract address. */\n escrowContract: `0x${string}`;\n /** Chain id for the EIP-712 domain (1480 mainnet, 14800 moksha). */\n chainId: number;\n /** App EIP-712 signer. */\n signTypedData: SignTypedDataFn;\n /**\n * Supplies the next payment nonce for a payer. Defaults to a process-local\n * monotonic counter seeded at 1. Provide a durable source in production so\n * nonces survive restarts (the gateway rejects reused (payer, nonce) pairs).\n */\n nonceSource?: PaymentNonceSource;\n}\n\n/** Map the gateway {@link PaymentBreakdown} into the public {@link DirectFeeBreakdown}. */\nexport function toDirectFeeBreakdown(\n breakdown: PaymentBreakdown,\n): DirectFeeBreakdown {\n return {\n registrationFee: breakdown.registrationFee,\n dataAccessFee: breakdown.dataAccessFee,\n registrationPaid: breakdown.registrationPaid,\n };\n}\n\n/** Map a gateway {@link EscrowPayResult} into the public {@link DirectPaymentReceipt}. */\nexport function toDirectPaymentReceipt(\n result: EscrowPayResult,\n): DirectPaymentReceipt {\n return {\n opType: result.opType,\n opId: result.opId,\n asset: result.asset,\n amount: result.amount,\n paymentNonce: result.paymentNonce,\n breakdown: toDirectFeeBreakdown(result.breakdown),\n paidAt: result.paidAt,\n };\n}\n\n/** Default in-process monotonic nonce counter (seeded at 1 per payer). */\nexport function createDefaultNonceSource(): PaymentNonceSource {\n const counters = new Map<string, bigint>();\n return (payerAddress: string): bigint => {\n const key = payerAddress.toLowerCase();\n const next = (counters.get(key) ?? 0n) + 1n;\n counters.set(key, next);\n return next;\n };\n}\n\nconst processLocalNonceSource = createDefaultNonceSource();\n\nfunction base64EncodeJson(value: unknown): string {\n const bytes = new TextEncoder().encode(JSON.stringify(value));\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n\nfunction base64DecodeJson(value: string): unknown {\n const binary = atob(value);\n const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));\n return JSON.parse(new TextDecoder().decode(bytes));\n}\n\nasync function signGrantPayment(params: {\n payerAddress: `0x${string}`;\n required: PersonalServerPaymentRequired;\n config: EscrowPaymentConfig;\n}): Promise<SignedGrantPayment> {\n const { payerAddress, required, config } = params;\n const nonceSource = config.nonceSource ?? processLocalNonceSource;\n const paymentNonce = BigInt(\n required.paymentNonce ?? (await nonceSource(payerAddress)),\n );\n const asset = (required.asset || NATIVE_ASSET_ADDRESS) as `0x${string}`;\n const opId = required.grantId as `0x${string}`;\n const amount = BigInt(required.amount);\n\n const message = {\n payerAddress,\n opType: GRANT_OP_TYPE,\n opId,\n asset,\n amount,\n paymentNonce,\n };\n\n const signature = await config.signTypedData({\n domain: genericPaymentDomain(config.chainId, config.escrowContract),\n types: GENERIC_PAYMENT_TYPES,\n primaryType: \"GenericPayment\",\n message,\n });\n\n return {\n message: {\n ...message,\n amount: amount.toString(),\n paymentNonce: paymentNonce.toString(),\n },\n signature,\n ...(required.accessRecord ? { accessRecord: required.accessRecord } : {}),\n };\n}\n\nexport async function buildGrantPaymentHeader(params: {\n payerAddress: `0x${string}`;\n required: PersonalServerPaymentRequired;\n config: EscrowPaymentConfig;\n}): Promise<string> {\n const signed = await signGrantPayment(params);\n const payment: X402PaymentHeader = {\n x402Version: 1,\n scheme: \"vana-escrow-grant\",\n network: params.required.network ?? `vana:${params.config.chainId}`,\n payload: signed,\n };\n return base64EncodeJson(payment);\n}\n\nexport function paymentReceiptFromHeader(\n header: string | null | undefined,\n): DirectPaymentReceipt | undefined {\n if (!header) return undefined;\n try {\n return toDirectPaymentReceipt(base64DecodeJson(header) as EscrowPayResult);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Authorize an escrow payment for a grant data-access fee.\n *\n * @param params - The payment requirement, the payer address, and escrow config.\n * @returns The gateway's {@link EscrowPayResult} as a typed\n * {@link DirectPaymentReceipt}.\n */\nexport async function authorizeGrantPayment(params: {\n payerAddress: `0x${string}`;\n required: PersonalServerPaymentRequired;\n config: EscrowPaymentConfig;\n}): Promise<DirectPaymentReceipt> {\n const { payerAddress, required, config } = params;\n const signed = await signGrantPayment(params);\n\n const result = await config.client.payForOp({\n payerAddress,\n opType: GRANT_OP_TYPE,\n opId: signed.message.opId,\n asset: signed.message.asset,\n amount: signed.message.amount,\n paymentNonce: signed.message.paymentNonce,\n signature: signed.signature,\n accessRecord: required.accessRecord,\n });\n\n return toDirectPaymentReceipt(result);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/direct/escrow-payment.ts"],"sourcesContent":["/**\n * Escrow-backed payment authorization for the Direct Data Controller.\n *\n * @remarks\n * Builds on the DPv2 escrow surface added in `protocol/escrow`. When a Personal\n * Server read returns `402 Payment Required`, the controller settles the\n * challenged operation through the escrow gateway:\n *\n * 1. Sign the challenge's `GenericPayment` EIP-712 message with the app key.\n * 2. POST it to the gateway's `/v1/escrow/pay` via {@link EscrowGatewayClient}.\n * 3. Map the gateway's {@link EscrowPayResult} into a typed\n * {@link DirectPaymentReceipt} for the caller to inspect.\n *\n * This module supports legacy `\"grant\"` operations and receipt-bound\n * `\"data_access\"` operations. It adapts the escrow `payForOp` flow to the\n * direct-read use case; it does not define its own payment scheme.\n *\n * @category Direct\n * @module direct/escrow-payment\n */\n\nimport {\n GENERIC_PAYMENT_TYPES,\n NATIVE_ASSET_ADDRESS,\n genericPaymentDomain,\n type EscrowAccessRecord,\n type EscrowGatewayClient,\n type EscrowPayResult,\n type PaymentBreakdown,\n} from \"../protocol/escrow\";\nimport type {\n DirectFeeBreakdown,\n DirectPaymentReceipt,\n DirectPaymentResponseMetadata,\n PersonalServerPaymentOperation,\n PersonalServerPaymentRequired,\n} from \"./types\";\n\n/** The escrow `GenericPayment.opType` used for grant-lifecycle payments. */\nexport const GRANT_OP_TYPE = \"grant\" as const;\n/** The escrow `GenericPayment.opType` used for receipt-bound data access. */\nexport const DATA_ACCESS_OP_TYPE = \"data_access\" as const;\n\n/**\n * EIP-712 typed-data signer (e.g. viem `account.signTypedData`).\n *\n * @remarks\n * Kept structurally minimal so any viem account/wallet client satisfies it\n * without the SDK depending on viem's exact `signTypedData` overload set.\n */\nexport type SignTypedDataFn = (args: {\n domain: ReturnType<typeof genericPaymentDomain>;\n types: typeof GENERIC_PAYMENT_TYPES;\n primaryType: \"GenericPayment\";\n message: {\n payerAddress: `0x${string}`;\n opType: string;\n opId: `0x${string}`;\n asset: `0x${string}`;\n amount: bigint;\n paymentNonce: bigint;\n };\n}) => Promise<`0x${string}`>;\n\n/** Supplies a monotonically-increasing payment nonce per payer. */\nexport type PaymentNonceSource = (\n payerAddress: string,\n) => Promise<bigint> | bigint;\n\ninterface EscrowPaymentMessage {\n payerAddress: `0x${string}`;\n opType: typeof GRANT_OP_TYPE | typeof DATA_ACCESS_OP_TYPE;\n opId: `0x${string}`;\n asset: `0x${string}`;\n amount: string;\n paymentNonce: string;\n}\n\ninterface SignedEscrowPayment {\n message: EscrowPaymentMessage;\n signature: `0x${string}`;\n accessRecord?: EscrowAccessRecord;\n}\n\ninterface X402PaymentHeader {\n x402Version: 1;\n scheme: \"vana-escrow-grant\";\n network: string;\n payload: SignedEscrowPayment;\n}\n\n/** Configuration required to sign an escrow X-PAYMENT header. */\nexport interface EscrowPaymentHeaderConfig {\n /** Deployed `DataPortabilityEscrow` contract address. */\n escrowContract: `0x${string}`;\n /** Chain id for the EIP-712 domain (1480 mainnet, 14800 moksha). */\n chainId: number;\n /** App EIP-712 signer. */\n signTypedData: SignTypedDataFn;\n /**\n * Supplies the next payment nonce for a payer. Defaults to a process-local\n * monotonic counter seeded at 1. Provide a durable source in production so\n * nonces survive restarts (the gateway rejects reused (payer, nonce) pairs).\n */\n nonceSource?: PaymentNonceSource;\n}\n\n/**\n * Escrow settlement configuration for gateway authorization.\n *\n * @remarks\n * Extends the header-signing boundary with the gateway client used by\n * {@link authorizeEscrowPayment}. Existing controller and legacy wrapper\n * callers can continue to provide this full configuration.\n */\nexport interface EscrowPaymentConfig extends EscrowPaymentHeaderConfig {\n /** Client for the gateway escrow endpoints (`/v1/escrow/*`). */\n client: EscrowGatewayClient;\n}\n\n/** Map the gateway {@link PaymentBreakdown} into the public {@link DirectFeeBreakdown}. */\nexport function toDirectFeeBreakdown(\n breakdown: PaymentBreakdown,\n): DirectFeeBreakdown {\n return {\n registrationFee: breakdown.registrationFee,\n dataAccessFee: breakdown.dataAccessFee,\n registrationPaid: breakdown.registrationPaid,\n };\n}\n\n/** Map a gateway {@link EscrowPayResult} into the public {@link DirectPaymentReceipt}. */\nexport function toDirectPaymentReceipt(\n result: EscrowPayResult,\n): DirectPaymentReceipt {\n return {\n opType: result.opType,\n opId: result.opId,\n asset: result.asset,\n amount: result.amount,\n paymentNonce: result.paymentNonce,\n breakdown: toDirectFeeBreakdown(result.breakdown),\n paidAt: result.paidAt,\n };\n}\n\n/** Default in-process monotonic nonce counter (seeded at 1 per payer). */\nexport function createDefaultNonceSource(): PaymentNonceSource {\n const counters = new Map<string, bigint>();\n return (payerAddress: string): bigint => {\n const key = payerAddress.toLowerCase();\n const next = (counters.get(key) ?? 0n) + 1n;\n counters.set(key, next);\n return next;\n };\n}\n\nconst processLocalNonceSource = createDefaultNonceSource();\nconst UINT256_MAX = (1n << 256n) - 1n;\nconst ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;\nconst BYTES32_RE = /^0x[0-9a-fA-F]{64}$/;\nconst SIGNATURE_RE = /^0x[0-9a-fA-F]{130}$/;\n\nfunction isUint256Decimal(value: string, allowZero: boolean): boolean {\n const pattern = allowZero ? /^(0|[1-9]\\d*)$/ : /^[1-9]\\d*$/;\n return (\n value.length <= UINT256_MAX.toString().length &&\n pattern.test(value) &&\n BigInt(value) <= UINT256_MAX\n );\n}\n\nfunction isValidAccessRecord(record: EscrowAccessRecord): boolean {\n return (\n BYTES32_RE.test(record.dataPointId) &&\n isUint256Decimal(record.version, false) &&\n ADDRESS_RE.test(record.accessor) &&\n BYTES32_RE.test(record.recordId) &&\n SIGNATURE_RE.test(record.signature)\n );\n}\n\nfunction validateSigningOperation(\n payerAddress: `0x${string}`,\n required: PersonalServerPaymentOperation,\n): void {\n if (!ADDRESS_RE.test(payerAddress)) {\n throw new Error(\"Payment payer must be a 20-byte EVM address\");\n }\n if (!BYTES32_RE.test(required.opId)) {\n throw new Error(\"Payment operation id must be a 32-byte hex value\");\n }\n if (!ADDRESS_RE.test(required.asset || NATIVE_ASSET_ADDRESS)) {\n throw new Error(\"Payment asset must be a 20-byte EVM address\");\n }\n if (!isUint256Decimal(required.amount, true)) {\n throw new Error(\"Payment amount must be a canonical uint256 decimal\");\n }\n if (\n required.paymentNonce !== undefined &&\n !isUint256Decimal(required.paymentNonce, false)\n ) {\n throw new Error(\"Payment nonce must be a positive uint256 decimal\");\n }\n\n const accessRecord = required.accessRecord;\n if (required.opType === DATA_ACCESS_OP_TYPE) {\n if (!accessRecord || !isValidAccessRecord(accessRecord)) {\n throw new Error(\"Data-access payment requires a valid access record\");\n }\n if (required.opId.toLowerCase() !== accessRecord.recordId.toLowerCase()) {\n throw new Error(\n \"Data-access payment operation id must equal the access record id\",\n );\n }\n if (accessRecord.accessor.toLowerCase() !== payerAddress.toLowerCase()) {\n throw new Error(\n \"Data-access payment accessor must equal the payment payer address\",\n );\n }\n return;\n }\n\n if (required.amount === \"0\") {\n if (\n !accessRecord ||\n !isValidAccessRecord(accessRecord) ||\n accessRecord.accessor.toLowerCase() !== payerAddress.toLowerCase()\n ) {\n throw new Error(\n \"Zero-amount grant payments require a valid access record for the payer\",\n );\n }\n }\n}\n\nfunction base64EncodeJson(value: unknown): string {\n const bytes = new TextEncoder().encode(JSON.stringify(value));\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n\nfunction base64DecodeJson(value: string): unknown {\n const binary = atob(value);\n const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));\n return JSON.parse(new TextDecoder().decode(bytes));\n}\n\nasync function signEscrowPayment(params: {\n payerAddress: `0x${string}`;\n required: PersonalServerPaymentOperation;\n config: EscrowPaymentHeaderConfig;\n}): Promise<SignedEscrowPayment> {\n const { payerAddress, required, config } = params;\n validateSigningOperation(payerAddress, required);\n const nonceSource = config.nonceSource ?? processLocalNonceSource;\n const paymentNonce = BigInt(\n required.paymentNonce ?? (await nonceSource(payerAddress)),\n );\n const asset = (required.asset || NATIVE_ASSET_ADDRESS) as `0x${string}`;\n const opId = required.opId as `0x${string}`;\n const amount = BigInt(required.amount);\n if (amount < 0n || amount > UINT256_MAX) {\n throw new Error(\"Payment amount must be a uint256\");\n }\n if (paymentNonce <= 0n || paymentNonce > UINT256_MAX) {\n throw new Error(\"Payment nonce must be a positive uint256\");\n }\n\n const message = {\n payerAddress,\n opType: required.opType,\n opId,\n asset,\n amount,\n paymentNonce,\n };\n\n const signature = await config.signTypedData({\n domain: genericPaymentDomain(config.chainId, config.escrowContract),\n types: GENERIC_PAYMENT_TYPES,\n primaryType: \"GenericPayment\",\n message,\n });\n\n return {\n message: {\n ...message,\n amount: amount.toString(),\n paymentNonce: paymentNonce.toString(),\n },\n signature,\n ...(required.accessRecord ? { accessRecord: required.accessRecord } : {}),\n };\n}\n\n/**\n * Build the canonical X-PAYMENT header for a validated escrow operation.\n *\n * @remarks\n * Supports both legacy grant payments and receipt-bound data-access payments.\n * Signing is injected through {@link EscrowPaymentHeaderConfig.signTypedData}.\n */\nexport async function buildEscrowPaymentHeader(params: {\n /** Address whose escrow balance pays for the operation. */\n payerAddress: `0x${string}`;\n /** Validated operation parsed from the Personal Server challenge. */\n required: PersonalServerPaymentOperation;\n /** Escrow contract, chain, signer, and nonce configuration. */\n config: EscrowPaymentHeaderConfig;\n}): Promise<string> {\n const network = params.required.network ?? `vana:${params.config.chainId}`;\n if (network !== `vana:${params.config.chainId}`) {\n throw new Error(\"Payment network must match the configured chain\");\n }\n\n const signed = await signEscrowPayment(params);\n const payment: X402PaymentHeader = {\n x402Version: 1,\n scheme: \"vana-escrow-grant\",\n network,\n payload: signed,\n };\n return base64EncodeJson(payment);\n}\n\n/** Build a legacy grant X-PAYMENT header. */\nexport async function buildGrantPaymentHeader(params: {\n payerAddress: `0x${string}`;\n required: PersonalServerPaymentRequired;\n config: EscrowPaymentConfig;\n}): Promise<string> {\n return buildEscrowPaymentHeader({\n ...params,\n required: {\n ...params.required,\n opType: GRANT_OP_TYPE,\n opId: params.required.grantId,\n },\n });\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction stringField(\n value: Record<string, unknown> | undefined,\n key: string,\n): string | undefined {\n const field = value?.[key];\n return typeof field === \"string\" ? field : undefined;\n}\n\nfunction isCanonicalIsoTimestamp(value: string): boolean {\n try {\n return new Date(value).toISOString() === value;\n } catch {\n return false;\n }\n}\n\n/**\n * Parse shape-validated payment response metadata echoed by a Personal Server.\n *\n * @remarks\n * This metadata is not authenticated by the gateway. It is suitable for\n * display and debugging, not as proof that a payment occurred.\n */\nexport function paymentResponseMetadataFromHeader(\n header: string | null | undefined,\n): DirectPaymentResponseMetadata | undefined {\n if (!header) return undefined;\n try {\n const result = asRecord(base64DecodeJson(header));\n const breakdown = asRecord(result?.breakdown);\n const opType = stringField(result, \"opType\");\n const opId = stringField(result, \"opId\");\n const payerAddress = stringField(result, \"payerAddress\");\n const asset = stringField(result, \"asset\");\n const amount = stringField(result, \"amount\");\n const paymentNonce = stringField(result, \"paymentNonce\");\n const registrationFee = stringField(breakdown, \"registrationFee\");\n const dataAccessFee = stringField(breakdown, \"dataAccessFee\");\n const paidAt = stringField(result, \"paidAt\");\n if (\n result?.success !== true ||\n !opType ||\n !opId ||\n !BYTES32_RE.test(opId) ||\n !payerAddress ||\n !ADDRESS_RE.test(payerAddress) ||\n !asset ||\n !ADDRESS_RE.test(asset) ||\n !amount ||\n !isUint256Decimal(amount, true) ||\n !paymentNonce ||\n !isUint256Decimal(paymentNonce, false) ||\n !registrationFee ||\n !isUint256Decimal(registrationFee, true) ||\n !dataAccessFee ||\n !isUint256Decimal(dataAccessFee, true) ||\n typeof breakdown?.registrationPaid !== \"boolean\" ||\n !paidAt ||\n !isCanonicalIsoTimestamp(paidAt)\n ) {\n return undefined;\n }\n return {\n opType,\n opId,\n asset,\n amount,\n paymentNonce,\n breakdown: {\n registrationFee,\n dataAccessFee,\n registrationPaid: breakdown.registrationPaid,\n },\n paidAt,\n };\n } catch {\n return undefined;\n }\n}\n\n/**\n * @deprecated Use {@link paymentResponseMetadataFromHeader}. A Personal\n * Server response header is untrusted metadata, not a gateway-authenticated\n * receipt.\n */\nexport function paymentReceiptFromHeader(\n header: string | null | undefined,\n): DirectPaymentResponseMetadata | undefined {\n return paymentResponseMetadataFromHeader(header);\n}\n\n/**\n * Authorize an escrow payment for a grant data-access fee.\n *\n * @param params - The payment requirement, the payer address, and escrow config.\n * @returns The gateway's {@link EscrowPayResult} as a typed\n * {@link DirectPaymentReceipt}.\n */\nexport async function authorizeGrantPayment(params: {\n payerAddress: `0x${string}`;\n required: PersonalServerPaymentRequired;\n config: EscrowPaymentConfig;\n}): Promise<DirectPaymentReceipt> {\n return authorizeEscrowPayment({\n ...params,\n required: {\n ...params.required,\n opType: GRANT_OP_TYPE,\n opId: params.required.grantId,\n },\n });\n}\n\n/**\n * Authorize a validated grant or data-access operation through the escrow\n * gateway.\n */\nexport async function authorizeEscrowPayment(params: {\n /** Address whose escrow balance pays for the operation. */\n payerAddress: `0x${string}`;\n /** Validated operation to authorize. */\n required: PersonalServerPaymentOperation;\n /** Escrow gateway and signing configuration. */\n config: EscrowPaymentConfig;\n}): Promise<DirectPaymentReceipt> {\n const { payerAddress, config } = params;\n const signed = await signEscrowPayment(params);\n\n const result = await config.client.payForOp({\n payerAddress,\n opType: signed.message.opType,\n opId: signed.message.opId,\n asset: signed.message.asset,\n amount: signed.message.amount,\n paymentNonce: signed.message.paymentNonce,\n signature: signed.signature,\n accessRecord: signed.accessRecord,\n });\n\n return toDirectPaymentReceipt(result);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBA,oBAQO;AAUA,MAAM,gBAAgB;AAEtB,MAAM,sBAAsB;AAgF5B,SAAS,qBACd,WACoB;AACpB,SAAO;AAAA,IACL,iBAAiB,UAAU;AAAA,IAC3B,eAAe,UAAU;AAAA,IACzB,kBAAkB,UAAU;AAAA,EAC9B;AACF;AAGO,SAAS,uBACd,QACsB;AACtB,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,MAAM,OAAO;AAAA,IACb,OAAO,OAAO;AAAA,IACd,QAAQ,OAAO;AAAA,IACf,cAAc,OAAO;AAAA,IACrB,WAAW,qBAAqB,OAAO,SAAS;AAAA,IAChD,QAAQ,OAAO;AAAA,EACjB;AACF;AAGO,SAAS,2BAA+C;AAC7D,QAAM,WAAW,oBAAI,IAAoB;AACzC,SAAO,CAAC,iBAAiC;AACvC,UAAM,MAAM,aAAa,YAAY;AACrC,UAAM,QAAQ,SAAS,IAAI,GAAG,KAAK,MAAM;AACzC,aAAS,IAAI,KAAK,IAAI;AACtB,WAAO;AAAA,EACT;AACF;AAEA,MAAM,0BAA0B,yBAAyB;AACzD,MAAM,eAAe,MAAM,QAAQ;AACnC,MAAM,aAAa;AACnB,MAAM,aAAa;AACnB,MAAM,eAAe;AAErB,SAAS,iBAAiB,OAAe,WAA6B;AACpE,QAAM,UAAU,YAAY,mBAAmB;AAC/C,SACE,MAAM,UAAU,YAAY,SAAS,EAAE,UACvC,QAAQ,KAAK,KAAK,KAClB,OAAO,KAAK,KAAK;AAErB;AAEA,SAAS,oBAAoB,QAAqC;AAChE,SACE,WAAW,KAAK,OAAO,WAAW,KAClC,iBAAiB,OAAO,SAAS,KAAK,KACtC,WAAW,KAAK,OAAO,QAAQ,KAC/B,WAAW,KAAK,OAAO,QAAQ,KAC/B,aAAa,KAAK,OAAO,SAAS;AAEtC;AAEA,SAAS,yBACP,cACA,UACM;AACN,MAAI,CAAC,WAAW,KAAK,YAAY,GAAG;AAClC,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AACA,MAAI,CAAC,WAAW,KAAK,SAAS,IAAI,GAAG;AACnC,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AACA,MAAI,CAAC,WAAW,KAAK,SAAS,SAAS,kCAAoB,GAAG;AAC5D,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AACA,MAAI,CAAC,iBAAiB,SAAS,QAAQ,IAAI,GAAG;AAC5C,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AACA,MACE,SAAS,iBAAiB,UAC1B,CAAC,iBAAiB,SAAS,cAAc,KAAK,GAC9C;AACA,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AAEA,QAAM,eAAe,SAAS;AAC9B,MAAI,SAAS,WAAW,qBAAqB;AAC3C,QAAI,CAAC,gBAAgB,CAAC,oBAAoB,YAAY,GAAG;AACvD,YAAM,IAAI,MAAM,oDAAoD;AAAA,IACtE;AACA,QAAI,SAAS,KAAK,YAAY,MAAM,aAAa,SAAS,YAAY,GAAG;AACvE,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,aAAa,SAAS,YAAY,MAAM,aAAa,YAAY,GAAG;AACtE,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA;AAAA,EACF;AAEA,MAAI,SAAS,WAAW,KAAK;AAC3B,QACE,CAAC,gBACD,CAAC,oBAAoB,YAAY,KACjC,aAAa,SAAS,YAAY,MAAM,aAAa,YAAY,GACjE;AACA,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,OAAwB;AAChD,QAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,KAAK,CAAC;AAC5D,MAAI,SAAS;AACb,aAAW,QAAQ,MAAO,WAAU,OAAO,aAAa,IAAI;AAC5D,SAAO,KAAK,MAAM;AACpB;AAEA,SAAS,iBAAiB,OAAwB;AAChD,QAAM,SAAS,KAAK,KAAK;AACzB,QAAM,QAAQ,WAAW,KAAK,QAAQ,CAAC,SAAS,KAAK,WAAW,CAAC,CAAC;AAClE,SAAO,KAAK,MAAM,IAAI,YAAY,EAAE,OAAO,KAAK,CAAC;AACnD;AAEA,eAAe,kBAAkB,QAIA;AAC/B,QAAM,EAAE,cAAc,UAAU,OAAO,IAAI;AAC3C,2BAAyB,cAAc,QAAQ;AAC/C,QAAM,cAAc,OAAO,eAAe;AAC1C,QAAM,eAAe;AAAA,IACnB,SAAS,gBAAiB,MAAM,YAAY,YAAY;AAAA,EAC1D;AACA,QAAM,QAAS,SAAS,SAAS;AACjC,QAAM,OAAO,SAAS;AACtB,QAAM,SAAS,OAAO,SAAS,MAAM;AACrC,MAAI,SAAS,MAAM,SAAS,aAAa;AACvC,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AACA,MAAI,gBAAgB,MAAM,eAAe,aAAa;AACpD,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AAEA,QAAM,UAAU;AAAA,IACd;AAAA,IACA,QAAQ,SAAS;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,YAAY,MAAM,OAAO,cAAc;AAAA,IAC3C,YAAQ,oCAAqB,OAAO,SAAS,OAAO,cAAc;AAAA,IAClE,OAAO;AAAA,IACP,aAAa;AAAA,IACb;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL,SAAS;AAAA,MACP,GAAG;AAAA,MACH,QAAQ,OAAO,SAAS;AAAA,MACxB,cAAc,aAAa,SAAS;AAAA,IACtC;AAAA,IACA;AAAA,IACA,GAAI,SAAS,eAAe,EAAE,cAAc,SAAS,aAAa,IAAI,CAAC;AAAA,EACzE;AACF;AASA,eAAsB,yBAAyB,QAO3B;AAClB,QAAM,UAAU,OAAO,SAAS,WAAW,QAAQ,OAAO,OAAO,OAAO;AACxE,MAAI,YAAY,QAAQ,OAAO,OAAO,OAAO,IAAI;AAC/C,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AAEA,QAAM,SAAS,MAAM,kBAAkB,MAAM;AAC7C,QAAM,UAA6B;AAAA,IACjC,aAAa;AAAA,IACb,QAAQ;AAAA,IACR;AAAA,IACA,SAAS;AAAA,EACX;AACA,SAAO,iBAAiB,OAAO;AACjC;AAGA,eAAsB,wBAAwB,QAI1B;AAClB,SAAO,yBAAyB;AAAA,IAC9B,GAAG;AAAA,IACH,UAAU;AAAA,MACR,GAAG,OAAO;AAAA,MACV,QAAQ;AAAA,MACR,MAAM,OAAO,SAAS;AAAA,IACxB;AAAA,EACF,CAAC;AACH;AAEA,SAAS,SAAS,OAAqD;AACrE,SAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAC5D,QACD;AACN;AAEA,SAAS,YACP,OACA,KACoB;AACpB,QAAM,QAAQ,QAAQ,GAAG;AACzB,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAEA,SAAS,wBAAwB,OAAwB;AACvD,MAAI;AACF,WAAO,IAAI,KAAK,KAAK,EAAE,YAAY,MAAM;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASO,SAAS,kCACd,QAC2C;AAC3C,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI;AACF,UAAM,SAAS,SAAS,iBAAiB,MAAM,CAAC;AAChD,UAAM,YAAY,SAAS,QAAQ,SAAS;AAC5C,UAAM,SAAS,YAAY,QAAQ,QAAQ;AAC3C,UAAM,OAAO,YAAY,QAAQ,MAAM;AACvC,UAAM,eAAe,YAAY,QAAQ,cAAc;AACvD,UAAM,QAAQ,YAAY,QAAQ,OAAO;AACzC,UAAM,SAAS,YAAY,QAAQ,QAAQ;AAC3C,UAAM,eAAe,YAAY,QAAQ,cAAc;AACvD,UAAM,kBAAkB,YAAY,WAAW,iBAAiB;AAChE,UAAM,gBAAgB,YAAY,WAAW,eAAe;AAC5D,UAAM,SAAS,YAAY,QAAQ,QAAQ;AAC3C,QACE,QAAQ,YAAY,QACpB,CAAC,UACD,CAAC,QACD,CAAC,WAAW,KAAK,IAAI,KACrB,CAAC,gBACD,CAAC,WAAW,KAAK,YAAY,KAC7B,CAAC,SACD,CAAC,WAAW,KAAK,KAAK,KACtB,CAAC,UACD,CAAC,iBAAiB,QAAQ,IAAI,KAC9B,CAAC,gBACD,CAAC,iBAAiB,cAAc,KAAK,KACrC,CAAC,mBACD,CAAC,iBAAiB,iBAAiB,IAAI,KACvC,CAAC,iBACD,CAAC,iBAAiB,eAAe,IAAI,KACrC,OAAO,WAAW,qBAAqB,aACvC,CAAC,UACD,CAAC,wBAAwB,MAAM,GAC/B;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA,kBAAkB,UAAU;AAAA,MAC9B;AAAA,MACA;AAAA,IACF;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOO,SAAS,yBACd,QAC2C;AAC3C,SAAO,kCAAkC,MAAM;AACjD;AASA,eAAsB,sBAAsB,QAIV;AAChC,SAAO,uBAAuB;AAAA,IAC5B,GAAG;AAAA,IACH,UAAU;AAAA,MACR,GAAG,OAAO;AAAA,MACV,QAAQ;AAAA,MACR,MAAM,OAAO,SAAS;AAAA,IACxB;AAAA,EACF,CAAC;AACH;AAMA,eAAsB,uBAAuB,QAOX;AAChC,QAAM,EAAE,cAAc,OAAO,IAAI;AACjC,QAAM,SAAS,MAAM,kBAAkB,MAAM;AAE7C,QAAM,SAAS,MAAM,OAAO,OAAO,SAAS;AAAA,IAC1C;AAAA,IACA,QAAQ,OAAO,QAAQ;AAAA,IACvB,MAAM,OAAO,QAAQ;AAAA,IACrB,OAAO,OAAO,QAAQ;AAAA,IACtB,QAAQ,OAAO,QAAQ;AAAA,IACvB,cAAc,OAAO,QAAQ;AAAA,IAC7B,WAAW,OAAO;AAAA,IAClB,cAAc,OAAO;AAAA,EACvB,CAAC;AAED,SAAO,uBAAuB,MAAM;AACtC;","names":[]}
@@ -4,24 +4,26 @@
4
4
  * @remarks
5
5
  * Builds on the DPv2 escrow surface added in `protocol/escrow`. When a Personal
6
6
  * Server read returns `402 Payment Required`, the controller settles the
7
- * grant's data-access fee through the escrow gateway:
7
+ * challenged operation through the escrow gateway:
8
8
  *
9
- * 1. Sign a `GenericPayment` EIP-712 message (op `"grant"`, opId = grantId)
10
- * with the app key.
9
+ * 1. Sign the challenge's `GenericPayment` EIP-712 message with the app key.
11
10
  * 2. POST it to the gateway's `/v1/escrow/pay` via {@link EscrowGatewayClient}.
12
11
  * 3. Map the gateway's {@link EscrowPayResult} into a typed
13
12
  * {@link DirectPaymentReceipt} for the caller to inspect.
14
13
  *
15
- * This module adapts the escrow `payForOp` flow to the direct-read use case; it
16
- * does not define its own payment scheme.
14
+ * This module supports legacy `"grant"` operations and receipt-bound
15
+ * `"data_access"` operations. It adapts the escrow `payForOp` flow to the
16
+ * direct-read use case; it does not define its own payment scheme.
17
17
  *
18
18
  * @category Direct
19
19
  * @module direct/escrow-payment
20
20
  */
21
- import { GENERIC_PAYMENT_TYPES, genericPaymentDomain, type EscrowGatewayClient, type EscrowPayResult, type PaymentBreakdown } from "../protocol/escrow";
22
- import type { DirectFeeBreakdown, DirectPaymentReceipt, PersonalServerPaymentRequired } from "./types";
21
+ import { GENERIC_PAYMENT_TYPES, genericPaymentDomain, type EscrowGatewayClient, type EscrowPayResult, type PaymentBreakdown } from "../protocol/escrow.js";
22
+ import type { DirectFeeBreakdown, DirectPaymentReceipt, DirectPaymentResponseMetadata, PersonalServerPaymentOperation, PersonalServerPaymentRequired } from "./types.js";
23
23
  /** The escrow `GenericPayment.opType` used for grant-lifecycle payments. */
24
24
  export declare const GRANT_OP_TYPE: "grant";
25
+ /** The escrow `GenericPayment.opType` used for receipt-bound data access. */
26
+ export declare const DATA_ACCESS_OP_TYPE: "data_access";
25
27
  /**
26
28
  * EIP-712 typed-data signer (e.g. viem `account.signTypedData`).
27
29
  *
@@ -44,10 +46,8 @@ export type SignTypedDataFn = (args: {
44
46
  }) => Promise<`0x${string}`>;
45
47
  /** Supplies a monotonically-increasing payment nonce per payer. */
46
48
  export type PaymentNonceSource = (payerAddress: string) => Promise<bigint> | bigint;
47
- /** Escrow settlement configuration for the controller. */
48
- export interface EscrowPaymentConfig {
49
- /** Client for the gateway escrow endpoints (`/v1/escrow/*`). */
50
- client: EscrowGatewayClient;
49
+ /** Configuration required to sign an escrow X-PAYMENT header. */
50
+ export interface EscrowPaymentHeaderConfig {
51
51
  /** Deployed `DataPortabilityEscrow` contract address. */
52
52
  escrowContract: `0x${string}`;
53
53
  /** Chain id for the EIP-712 domain (1480 mainnet, 14800 moksha). */
@@ -61,18 +61,59 @@ export interface EscrowPaymentConfig {
61
61
  */
62
62
  nonceSource?: PaymentNonceSource;
63
63
  }
64
+ /**
65
+ * Escrow settlement configuration for gateway authorization.
66
+ *
67
+ * @remarks
68
+ * Extends the header-signing boundary with the gateway client used by
69
+ * {@link authorizeEscrowPayment}. Existing controller and legacy wrapper
70
+ * callers can continue to provide this full configuration.
71
+ */
72
+ export interface EscrowPaymentConfig extends EscrowPaymentHeaderConfig {
73
+ /** Client for the gateway escrow endpoints (`/v1/escrow/*`). */
74
+ client: EscrowGatewayClient;
75
+ }
64
76
  /** Map the gateway {@link PaymentBreakdown} into the public {@link DirectFeeBreakdown}. */
65
77
  export declare function toDirectFeeBreakdown(breakdown: PaymentBreakdown): DirectFeeBreakdown;
66
78
  /** Map a gateway {@link EscrowPayResult} into the public {@link DirectPaymentReceipt}. */
67
79
  export declare function toDirectPaymentReceipt(result: EscrowPayResult): DirectPaymentReceipt;
68
80
  /** Default in-process monotonic nonce counter (seeded at 1 per payer). */
69
81
  export declare function createDefaultNonceSource(): PaymentNonceSource;
82
+ /**
83
+ * Build the canonical X-PAYMENT header for a validated escrow operation.
84
+ *
85
+ * @remarks
86
+ * Supports both legacy grant payments and receipt-bound data-access payments.
87
+ * Signing is injected through {@link EscrowPaymentHeaderConfig.signTypedData}.
88
+ */
89
+ export declare function buildEscrowPaymentHeader(params: {
90
+ /** Address whose escrow balance pays for the operation. */
91
+ payerAddress: `0x${string}`;
92
+ /** Validated operation parsed from the Personal Server challenge. */
93
+ required: PersonalServerPaymentOperation;
94
+ /** Escrow contract, chain, signer, and nonce configuration. */
95
+ config: EscrowPaymentHeaderConfig;
96
+ }): Promise<string>;
97
+ /** Build a legacy grant X-PAYMENT header. */
70
98
  export declare function buildGrantPaymentHeader(params: {
71
99
  payerAddress: `0x${string}`;
72
100
  required: PersonalServerPaymentRequired;
73
101
  config: EscrowPaymentConfig;
74
102
  }): Promise<string>;
75
- export declare function paymentReceiptFromHeader(header: string | null | undefined): DirectPaymentReceipt | undefined;
103
+ /**
104
+ * Parse shape-validated payment response metadata echoed by a Personal Server.
105
+ *
106
+ * @remarks
107
+ * This metadata is not authenticated by the gateway. It is suitable for
108
+ * display and debugging, not as proof that a payment occurred.
109
+ */
110
+ export declare function paymentResponseMetadataFromHeader(header: string | null | undefined): DirectPaymentResponseMetadata | undefined;
111
+ /**
112
+ * @deprecated Use {@link paymentResponseMetadataFromHeader}. A Personal
113
+ * Server response header is untrusted metadata, not a gateway-authenticated
114
+ * receipt.
115
+ */
116
+ export declare function paymentReceiptFromHeader(header: string | null | undefined): DirectPaymentResponseMetadata | undefined;
76
117
  /**
77
118
  * Authorize an escrow payment for a grant data-access fee.
78
119
  *
@@ -85,3 +126,15 @@ export declare function authorizeGrantPayment(params: {
85
126
  required: PersonalServerPaymentRequired;
86
127
  config: EscrowPaymentConfig;
87
128
  }): Promise<DirectPaymentReceipt>;
129
+ /**
130
+ * Authorize a validated grant or data-access operation through the escrow
131
+ * gateway.
132
+ */
133
+ export declare function authorizeEscrowPayment(params: {
134
+ /** Address whose escrow balance pays for the operation. */
135
+ payerAddress: `0x${string}`;
136
+ /** Validated operation to authorize. */
137
+ required: PersonalServerPaymentOperation;
138
+ /** Escrow gateway and signing configuration. */
139
+ config: EscrowPaymentConfig;
140
+ }): Promise<DirectPaymentReceipt>;
@@ -4,6 +4,7 @@ import {
4
4
  genericPaymentDomain
5
5
  } from "../protocol/escrow.js";
6
6
  const GRANT_OP_TYPE = "grant";
7
+ const DATA_ACCESS_OP_TYPE = "data_access";
7
8
  function toDirectFeeBreakdown(breakdown) {
8
9
  return {
9
10
  registrationFee: breakdown.registrationFee,
@@ -32,6 +33,58 @@ function createDefaultNonceSource() {
32
33
  };
33
34
  }
34
35
  const processLocalNonceSource = createDefaultNonceSource();
36
+ const UINT256_MAX = (1n << 256n) - 1n;
37
+ const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
38
+ const BYTES32_RE = /^0x[0-9a-fA-F]{64}$/;
39
+ const SIGNATURE_RE = /^0x[0-9a-fA-F]{130}$/;
40
+ function isUint256Decimal(value, allowZero) {
41
+ const pattern = allowZero ? /^(0|[1-9]\d*)$/ : /^[1-9]\d*$/;
42
+ return value.length <= UINT256_MAX.toString().length && pattern.test(value) && BigInt(value) <= UINT256_MAX;
43
+ }
44
+ function isValidAccessRecord(record) {
45
+ return BYTES32_RE.test(record.dataPointId) && isUint256Decimal(record.version, false) && ADDRESS_RE.test(record.accessor) && BYTES32_RE.test(record.recordId) && SIGNATURE_RE.test(record.signature);
46
+ }
47
+ function validateSigningOperation(payerAddress, required) {
48
+ if (!ADDRESS_RE.test(payerAddress)) {
49
+ throw new Error("Payment payer must be a 20-byte EVM address");
50
+ }
51
+ if (!BYTES32_RE.test(required.opId)) {
52
+ throw new Error("Payment operation id must be a 32-byte hex value");
53
+ }
54
+ if (!ADDRESS_RE.test(required.asset || NATIVE_ASSET_ADDRESS)) {
55
+ throw new Error("Payment asset must be a 20-byte EVM address");
56
+ }
57
+ if (!isUint256Decimal(required.amount, true)) {
58
+ throw new Error("Payment amount must be a canonical uint256 decimal");
59
+ }
60
+ if (required.paymentNonce !== void 0 && !isUint256Decimal(required.paymentNonce, false)) {
61
+ throw new Error("Payment nonce must be a positive uint256 decimal");
62
+ }
63
+ const accessRecord = required.accessRecord;
64
+ if (required.opType === DATA_ACCESS_OP_TYPE) {
65
+ if (!accessRecord || !isValidAccessRecord(accessRecord)) {
66
+ throw new Error("Data-access payment requires a valid access record");
67
+ }
68
+ if (required.opId.toLowerCase() !== accessRecord.recordId.toLowerCase()) {
69
+ throw new Error(
70
+ "Data-access payment operation id must equal the access record id"
71
+ );
72
+ }
73
+ if (accessRecord.accessor.toLowerCase() !== payerAddress.toLowerCase()) {
74
+ throw new Error(
75
+ "Data-access payment accessor must equal the payment payer address"
76
+ );
77
+ }
78
+ return;
79
+ }
80
+ if (required.amount === "0") {
81
+ if (!accessRecord || !isValidAccessRecord(accessRecord) || accessRecord.accessor.toLowerCase() !== payerAddress.toLowerCase()) {
82
+ throw new Error(
83
+ "Zero-amount grant payments require a valid access record for the payer"
84
+ );
85
+ }
86
+ }
87
+ }
35
88
  function base64EncodeJson(value) {
36
89
  const bytes = new TextEncoder().encode(JSON.stringify(value));
37
90
  let binary = "";
@@ -43,18 +96,25 @@ function base64DecodeJson(value) {
43
96
  const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
44
97
  return JSON.parse(new TextDecoder().decode(bytes));
45
98
  }
46
- async function signGrantPayment(params) {
99
+ async function signEscrowPayment(params) {
47
100
  const { payerAddress, required, config } = params;
101
+ validateSigningOperation(payerAddress, required);
48
102
  const nonceSource = config.nonceSource ?? processLocalNonceSource;
49
103
  const paymentNonce = BigInt(
50
104
  required.paymentNonce ?? await nonceSource(payerAddress)
51
105
  );
52
106
  const asset = required.asset || NATIVE_ASSET_ADDRESS;
53
- const opId = required.grantId;
107
+ const opId = required.opId;
54
108
  const amount = BigInt(required.amount);
109
+ if (amount < 0n || amount > UINT256_MAX) {
110
+ throw new Error("Payment amount must be a uint256");
111
+ }
112
+ if (paymentNonce <= 0n || paymentNonce > UINT256_MAX) {
113
+ throw new Error("Payment nonce must be a positive uint256");
114
+ }
55
115
  const message = {
56
116
  payerAddress,
57
- opType: GRANT_OP_TYPE,
117
+ opType: required.opType,
58
118
  opId,
59
119
  asset,
60
120
  amount,
@@ -76,45 +136,116 @@ async function signGrantPayment(params) {
76
136
  ...required.accessRecord ? { accessRecord: required.accessRecord } : {}
77
137
  };
78
138
  }
79
- async function buildGrantPaymentHeader(params) {
80
- const signed = await signGrantPayment(params);
139
+ async function buildEscrowPaymentHeader(params) {
140
+ const network = params.required.network ?? `vana:${params.config.chainId}`;
141
+ if (network !== `vana:${params.config.chainId}`) {
142
+ throw new Error("Payment network must match the configured chain");
143
+ }
144
+ const signed = await signEscrowPayment(params);
81
145
  const payment = {
82
146
  x402Version: 1,
83
147
  scheme: "vana-escrow-grant",
84
- network: params.required.network ?? `vana:${params.config.chainId}`,
148
+ network,
85
149
  payload: signed
86
150
  };
87
151
  return base64EncodeJson(payment);
88
152
  }
89
- function paymentReceiptFromHeader(header) {
153
+ async function buildGrantPaymentHeader(params) {
154
+ return buildEscrowPaymentHeader({
155
+ ...params,
156
+ required: {
157
+ ...params.required,
158
+ opType: GRANT_OP_TYPE,
159
+ opId: params.required.grantId
160
+ }
161
+ });
162
+ }
163
+ function asRecord(value) {
164
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
165
+ }
166
+ function stringField(value, key) {
167
+ const field = value?.[key];
168
+ return typeof field === "string" ? field : void 0;
169
+ }
170
+ function isCanonicalIsoTimestamp(value) {
171
+ try {
172
+ return new Date(value).toISOString() === value;
173
+ } catch {
174
+ return false;
175
+ }
176
+ }
177
+ function paymentResponseMetadataFromHeader(header) {
90
178
  if (!header) return void 0;
91
179
  try {
92
- return toDirectPaymentReceipt(base64DecodeJson(header));
180
+ const result = asRecord(base64DecodeJson(header));
181
+ const breakdown = asRecord(result?.breakdown);
182
+ const opType = stringField(result, "opType");
183
+ const opId = stringField(result, "opId");
184
+ const payerAddress = stringField(result, "payerAddress");
185
+ const asset = stringField(result, "asset");
186
+ const amount = stringField(result, "amount");
187
+ const paymentNonce = stringField(result, "paymentNonce");
188
+ const registrationFee = stringField(breakdown, "registrationFee");
189
+ const dataAccessFee = stringField(breakdown, "dataAccessFee");
190
+ const paidAt = stringField(result, "paidAt");
191
+ if (result?.success !== true || !opType || !opId || !BYTES32_RE.test(opId) || !payerAddress || !ADDRESS_RE.test(payerAddress) || !asset || !ADDRESS_RE.test(asset) || !amount || !isUint256Decimal(amount, true) || !paymentNonce || !isUint256Decimal(paymentNonce, false) || !registrationFee || !isUint256Decimal(registrationFee, true) || !dataAccessFee || !isUint256Decimal(dataAccessFee, true) || typeof breakdown?.registrationPaid !== "boolean" || !paidAt || !isCanonicalIsoTimestamp(paidAt)) {
192
+ return void 0;
193
+ }
194
+ return {
195
+ opType,
196
+ opId,
197
+ asset,
198
+ amount,
199
+ paymentNonce,
200
+ breakdown: {
201
+ registrationFee,
202
+ dataAccessFee,
203
+ registrationPaid: breakdown.registrationPaid
204
+ },
205
+ paidAt
206
+ };
93
207
  } catch {
94
208
  return void 0;
95
209
  }
96
210
  }
211
+ function paymentReceiptFromHeader(header) {
212
+ return paymentResponseMetadataFromHeader(header);
213
+ }
97
214
  async function authorizeGrantPayment(params) {
98
- const { payerAddress, required, config } = params;
99
- const signed = await signGrantPayment(params);
215
+ return authorizeEscrowPayment({
216
+ ...params,
217
+ required: {
218
+ ...params.required,
219
+ opType: GRANT_OP_TYPE,
220
+ opId: params.required.grantId
221
+ }
222
+ });
223
+ }
224
+ async function authorizeEscrowPayment(params) {
225
+ const { payerAddress, config } = params;
226
+ const signed = await signEscrowPayment(params);
100
227
  const result = await config.client.payForOp({
101
228
  payerAddress,
102
- opType: GRANT_OP_TYPE,
229
+ opType: signed.message.opType,
103
230
  opId: signed.message.opId,
104
231
  asset: signed.message.asset,
105
232
  amount: signed.message.amount,
106
233
  paymentNonce: signed.message.paymentNonce,
107
234
  signature: signed.signature,
108
- accessRecord: required.accessRecord
235
+ accessRecord: signed.accessRecord
109
236
  });
110
237
  return toDirectPaymentReceipt(result);
111
238
  }
112
239
  export {
240
+ DATA_ACCESS_OP_TYPE,
113
241
  GRANT_OP_TYPE,
242
+ authorizeEscrowPayment,
114
243
  authorizeGrantPayment,
244
+ buildEscrowPaymentHeader,
115
245
  buildGrantPaymentHeader,
116
246
  createDefaultNonceSource,
117
247
  paymentReceiptFromHeader,
248
+ paymentResponseMetadataFromHeader,
118
249
  toDirectFeeBreakdown,
119
250
  toDirectPaymentReceipt
120
251
  };