@solana/mpp 0.1.0 → 0.2.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 (92) hide show
  1. package/README.md +212 -0
  2. package/dist/Methods.d.ts +1 -213
  3. package/dist/Methods.d.ts.map +1 -1
  4. package/dist/Methods.js +1 -158
  5. package/dist/Methods.js.map +1 -1
  6. package/dist/client/Methods.d.ts +0 -2
  7. package/dist/client/Methods.d.ts.map +1 -1
  8. package/dist/client/Methods.js +0 -2
  9. package/dist/client/Methods.js.map +1 -1
  10. package/dist/client/index.d.ts +0 -1
  11. package/dist/client/index.d.ts.map +1 -1
  12. package/dist/client/index.js +0 -1
  13. package/dist/client/index.js.map +1 -1
  14. package/dist/index.d.ts +0 -3
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +0 -2
  17. package/dist/index.js.map +1 -1
  18. package/dist/server/Charge.d.ts +2 -2
  19. package/dist/server/Charge.d.ts.map +1 -1
  20. package/dist/server/Charge.js +4 -0
  21. package/dist/server/Charge.js.map +1 -1
  22. package/dist/server/Methods.d.ts +0 -2
  23. package/dist/server/Methods.d.ts.map +1 -1
  24. package/dist/server/Methods.js +0 -2
  25. package/dist/server/Methods.js.map +1 -1
  26. package/dist/server/index.d.ts +0 -1
  27. package/dist/server/index.d.ts.map +1 -1
  28. package/dist/server/index.js +0 -1
  29. package/dist/server/index.js.map +1 -1
  30. package/package.json +1 -9
  31. package/src/Methods.ts +1 -171
  32. package/src/client/Methods.ts +0 -3
  33. package/src/client/index.ts +0 -1
  34. package/src/index.ts +0 -29
  35. package/src/server/Charge.ts +7 -2
  36. package/src/server/Methods.ts +0 -3
  37. package/src/server/index.ts +0 -1
  38. package/dist/client/Session.d.ts +0 -195
  39. package/dist/client/Session.d.ts.map +0 -1
  40. package/dist/client/Session.js +0 -411
  41. package/dist/client/Session.js.map +0 -1
  42. package/dist/server/Session.d.ts +0 -171
  43. package/dist/server/Session.d.ts.map +0 -1
  44. package/dist/server/Session.js +0 -430
  45. package/dist/server/Session.js.map +0 -1
  46. package/dist/session/ChannelStore.d.ts +0 -12
  47. package/dist/session/ChannelStore.d.ts.map +0 -1
  48. package/dist/session/ChannelStore.js +0 -88
  49. package/dist/session/ChannelStore.js.map +0 -1
  50. package/dist/session/Types.d.ts +0 -179
  51. package/dist/session/Types.d.ts.map +0 -1
  52. package/dist/session/Types.js +0 -2
  53. package/dist/session/Types.js.map +0 -1
  54. package/dist/session/Voucher.d.ts +0 -7
  55. package/dist/session/Voucher.d.ts.map +0 -1
  56. package/dist/session/Voucher.js +0 -118
  57. package/dist/session/Voucher.js.map +0 -1
  58. package/dist/session/authorizers/BudgetAuthorizer.d.ts +0 -90
  59. package/dist/session/authorizers/BudgetAuthorizer.d.ts.map +0 -1
  60. package/dist/session/authorizers/BudgetAuthorizer.js +0 -398
  61. package/dist/session/authorizers/BudgetAuthorizer.js.map +0 -1
  62. package/dist/session/authorizers/SwigSessionAuthorizer.d.ts +0 -104
  63. package/dist/session/authorizers/SwigSessionAuthorizer.d.ts.map +0 -1
  64. package/dist/session/authorizers/SwigSessionAuthorizer.js +0 -522
  65. package/dist/session/authorizers/SwigSessionAuthorizer.js.map +0 -1
  66. package/dist/session/authorizers/UnboundedAuthorizer.d.ts +0 -36
  67. package/dist/session/authorizers/UnboundedAuthorizer.d.ts.map +0 -1
  68. package/dist/session/authorizers/UnboundedAuthorizer.js +0 -204
  69. package/dist/session/authorizers/UnboundedAuthorizer.js.map +0 -1
  70. package/dist/session/authorizers/index.d.ts +0 -5
  71. package/dist/session/authorizers/index.d.ts.map +0 -1
  72. package/dist/session/authorizers/index.js +0 -5
  73. package/dist/session/authorizers/index.js.map +0 -1
  74. package/dist/session/authorizers/makeSessionAuthorizer.d.ts +0 -19
  75. package/dist/session/authorizers/makeSessionAuthorizer.d.ts.map +0 -1
  76. package/dist/session/authorizers/makeSessionAuthorizer.js +0 -72
  77. package/dist/session/authorizers/makeSessionAuthorizer.js.map +0 -1
  78. package/dist/session/index.d.ts +0 -5
  79. package/dist/session/index.d.ts.map +0 -1
  80. package/dist/session/index.js +0 -5
  81. package/dist/session/index.js.map +0 -1
  82. package/src/client/Session.ts +0 -630
  83. package/src/server/Session.ts +0 -687
  84. package/src/session/ChannelStore.ts +0 -128
  85. package/src/session/Types.ts +0 -189
  86. package/src/session/Voucher.ts +0 -158
  87. package/src/session/authorizers/BudgetAuthorizer.ts +0 -574
  88. package/src/session/authorizers/SwigSessionAuthorizer.ts +0 -767
  89. package/src/session/authorizers/UnboundedAuthorizer.ts +0 -284
  90. package/src/session/authorizers/index.ts +0 -4
  91. package/src/session/authorizers/makeSessionAuthorizer.ts +0 -104
  92. package/src/session/index.ts +0 -4
@@ -1,284 +0,0 @@
1
- import type { MessagePartialSigner } from '@solana/kit';
2
-
3
- import {
4
- type AuthorizeCloseInput,
5
- type AuthorizedClose,
6
- type AuthorizedOpen,
7
- type AuthorizedTopup,
8
- type AuthorizedUpdate,
9
- type AuthorizeOpenInput,
10
- type AuthorizerCapabilities,
11
- type AuthorizeTopupInput,
12
- type AuthorizeUpdateInput,
13
- type SessionAuthorizer,
14
- } from '../Types.js';
15
- import { signVoucher } from '../Voucher.js';
16
-
17
- type ChannelProgress = {
18
- lastCumulative: bigint;
19
- lastSequence: number;
20
- };
21
-
22
- export interface UnboundedAuthorizerParameters {
23
- allowedPrograms?: string[];
24
- buildCloseTx?: (input: AuthorizeCloseInput) => Promise<string> | string;
25
- buildOpenTx?: (input: AuthorizeOpenInput) => Promise<string> | string;
26
- buildTopupTx?: (input: AuthorizeTopupInput) => Promise<string> | string;
27
- expiresAt?: string;
28
- requiresInteractiveApproval?: Partial<AuthorizerCapabilities['requiresInteractiveApproval']>;
29
- signer: MessagePartialSigner;
30
- }
31
-
32
- export class UnboundedAuthorizer implements SessionAuthorizer {
33
- private readonly signer: MessagePartialSigner;
34
- private readonly allowedPrograms?: Set<string>;
35
- private readonly expiresAt?: string;
36
- private readonly expiresAtUnixMs?: number;
37
- private readonly buildOpenTx?: (input: AuthorizeOpenInput) => Promise<string> | string;
38
- private readonly buildTopupTx?: (input: AuthorizeTopupInput) => Promise<string> | string;
39
- private readonly buildCloseTx?: (input: AuthorizeCloseInput) => Promise<string> | string;
40
- private readonly channels = new Map<string, ChannelProgress>();
41
- private readonly capabilities: AuthorizerCapabilities;
42
-
43
- constructor(parameters: UnboundedAuthorizerParameters) {
44
- this.signer = parameters.signer;
45
- this.allowedPrograms = parameters.allowedPrograms ? new Set(parameters.allowedPrograms) : undefined;
46
- this.expiresAt = parameters.expiresAt;
47
- this.expiresAtUnixMs =
48
- parameters.expiresAt !== undefined ? parseIsoTimestamp(parameters.expiresAt, 'expiresAt') : undefined;
49
- this.buildOpenTx = parameters.buildOpenTx;
50
- this.buildTopupTx = parameters.buildTopupTx;
51
- this.buildCloseTx = parameters.buildCloseTx;
52
-
53
- const requiresInteractiveApproval = {
54
- close: parameters.requiresInteractiveApproval?.close ?? false,
55
- open: parameters.requiresInteractiveApproval?.open ?? false,
56
- topup: parameters.requiresInteractiveApproval?.topup ?? false,
57
- update: parameters.requiresInteractiveApproval?.update ?? false,
58
- };
59
-
60
- this.capabilities = {
61
- mode: 'regular_unbounded',
62
- ...(this.expiresAt ? { expiresAt: this.expiresAt } : {}),
63
- ...(parameters.allowedPrograms ? { allowedPrograms: [...parameters.allowedPrograms] } : {}),
64
- allowedActions: ['open', 'update', 'topup', 'close'],
65
- requiresInteractiveApproval,
66
- };
67
- }
68
-
69
- getMode() {
70
- return 'regular_unbounded' as const;
71
- }
72
-
73
- getCapabilities(): AuthorizerCapabilities {
74
- return this.capabilities;
75
- }
76
-
77
- async authorizeOpen(input: AuthorizeOpenInput): Promise<AuthorizedOpen> {
78
- this.assertNotExpired();
79
- this.assertProgramAllowed(input.channelProgram);
80
-
81
- const openTx = await this.resolveOpenTx(input);
82
-
83
- const voucher = await signVoucher(this.signer, {
84
- channelId: input.channelId,
85
- cumulativeAmount: '0',
86
- meter: input.pricing?.meter ?? 'session',
87
- payer: this.signer.address,
88
- recipient: input.recipient,
89
- sequence: 0,
90
- units: '0',
91
- ...(this.expiresAt ? { expiresAt: this.expiresAt } : {}),
92
- chainId: normalizeChainId(input.network),
93
- channelProgram: input.channelProgram,
94
- serverNonce: input.serverNonce,
95
- });
96
-
97
- this.channels.set(input.channelId, {
98
- lastCumulative: 0n,
99
- lastSequence: 0,
100
- });
101
-
102
- return {
103
- capabilities: this.getCapabilities(),
104
- openTx,
105
- voucher,
106
- ...(this.expiresAt ? { expiresAt: this.expiresAt } : {}),
107
- };
108
- }
109
-
110
- async authorizeUpdate(input: AuthorizeUpdateInput): Promise<AuthorizedUpdate> {
111
- this.assertNotExpired();
112
- this.assertProgramAllowed(input.channelProgram);
113
-
114
- const cumulativeAmount = parseNonNegativeAmount(input.cumulativeAmount, 'cumulativeAmount');
115
-
116
- const progress = this.channels.get(input.channelId);
117
- this.assertMonotonic(input.channelId, input.sequence, cumulativeAmount, progress);
118
-
119
- const voucher = await signVoucher(this.signer, {
120
- channelId: input.channelId,
121
- cumulativeAmount: cumulativeAmount.toString(),
122
- meter: input.meter,
123
- payer: this.signer.address,
124
- recipient: input.recipient,
125
- sequence: input.sequence,
126
- units: input.units,
127
- ...(this.expiresAt ? { expiresAt: this.expiresAt } : {}),
128
- chainId: normalizeChainId(input.network),
129
- channelProgram: input.channelProgram,
130
- serverNonce: input.serverNonce,
131
- });
132
-
133
- this.channels.set(input.channelId, {
134
- lastCumulative: cumulativeAmount,
135
- lastSequence: input.sequence,
136
- });
137
-
138
- return { voucher };
139
- }
140
-
141
- async authorizeTopup(input: AuthorizeTopupInput): Promise<AuthorizedTopup> {
142
- this.assertNotExpired();
143
- this.assertProgramAllowed(input.channelProgram);
144
- parseNonNegativeAmount(input.additionalAmount, 'additionalAmount');
145
-
146
- return {
147
- topupTx: await this.resolveTopupTx(input),
148
- };
149
- }
150
-
151
- async authorizeClose(input: AuthorizeCloseInput): Promise<AuthorizedClose> {
152
- this.assertNotExpired();
153
- this.assertProgramAllowed(input.channelProgram);
154
-
155
- const finalCumulativeAmount = parseNonNegativeAmount(input.finalCumulativeAmount, 'finalCumulativeAmount');
156
-
157
- const progress = this.channels.get(input.channelId);
158
- this.assertMonotonic(input.channelId, input.sequence, finalCumulativeAmount, progress);
159
-
160
- const voucher = await signVoucher(this.signer, {
161
- channelId: input.channelId,
162
- cumulativeAmount: finalCumulativeAmount.toString(),
163
- meter: 'close',
164
- payer: this.signer.address,
165
- recipient: input.recipient,
166
- sequence: input.sequence,
167
- units: '0',
168
- ...(this.expiresAt ? { expiresAt: this.expiresAt } : {}),
169
- chainId: normalizeChainId(input.network),
170
- channelProgram: input.channelProgram,
171
- serverNonce: input.serverNonce,
172
- });
173
-
174
- const closeTx = await this.resolveCloseTx(input);
175
-
176
- this.channels.set(input.channelId, {
177
- lastCumulative: finalCumulativeAmount,
178
- lastSequence: input.sequence,
179
- });
180
-
181
- return {
182
- voucher,
183
- ...(closeTx ? { closeTx } : {}),
184
- };
185
- }
186
-
187
- private assertNotExpired() {
188
- if (this.expiresAtUnixMs !== undefined && Date.now() > this.expiresAtUnixMs) {
189
- throw new Error('Unbounded authorizer policy has expired');
190
- }
191
- }
192
-
193
- private assertProgramAllowed(channelProgram: string) {
194
- if (!this.allowedPrograms) {
195
- return;
196
- }
197
-
198
- if (!this.allowedPrograms.has(channelProgram)) {
199
- throw new Error(`Channel program is not allowed: ${channelProgram}`);
200
- }
201
- }
202
-
203
- private assertMonotonic(
204
- channelId: string,
205
- sequence: number,
206
- cumulativeAmount: bigint,
207
- progress: ChannelProgress | undefined,
208
- ) {
209
- if (!Number.isInteger(sequence) || sequence < 0) {
210
- throw new Error('Sequence must be a non-negative integer');
211
- }
212
-
213
- if (!progress) {
214
- return;
215
- }
216
-
217
- if (sequence <= progress.lastSequence) {
218
- throw new Error(
219
- `Sequence must increase for channel ${channelId}. Last=${progress.lastSequence}, received=${sequence}`,
220
- );
221
- }
222
-
223
- if (cumulativeAmount < progress.lastCumulative) {
224
- throw new Error(
225
- `Cumulative amount must not decrease for channel ${channelId}. Last=${progress.lastCumulative.toString()}, received=${cumulativeAmount.toString()}`,
226
- );
227
- }
228
- }
229
-
230
- private async resolveOpenTx(input: AuthorizeOpenInput): Promise<string> {
231
- if (!this.buildOpenTx) {
232
- throw new Error('UnboundedAuthorizer requires `buildOpenTx` to authorize open requests');
233
- }
234
-
235
- return await this.buildOpenTx(input);
236
- }
237
-
238
- private async resolveTopupTx(input: AuthorizeTopupInput): Promise<string> {
239
- if (!this.buildTopupTx) {
240
- throw new Error('UnboundedAuthorizer requires `buildTopupTx` to authorize topup requests');
241
- }
242
-
243
- return await this.buildTopupTx(input);
244
- }
245
-
246
- private async resolveCloseTx(input: AuthorizeCloseInput): Promise<string | undefined> {
247
- if (!this.buildCloseTx) {
248
- return undefined;
249
- }
250
-
251
- return await this.buildCloseTx(input);
252
- }
253
- }
254
-
255
- function parseNonNegativeAmount(value: string, field: string): bigint {
256
- let amount: bigint;
257
- try {
258
- amount = BigInt(value);
259
- } catch {
260
- throw new Error(`${field} must be a valid integer string`);
261
- }
262
-
263
- if (amount < 0n) {
264
- throw new Error(`${field} must be non-negative`);
265
- }
266
-
267
- return amount;
268
- }
269
-
270
- function parseIsoTimestamp(value: string, field: string): number {
271
- const unixMs = Date.parse(value);
272
- if (Number.isNaN(unixMs)) {
273
- throw new Error(`${field} must be a valid ISO timestamp`);
274
- }
275
- return unixMs;
276
- }
277
-
278
- function normalizeChainId(network: string): string {
279
- const normalized = network.trim();
280
- if (normalized.length === 0) {
281
- throw new Error('network must be a non-empty string');
282
- }
283
- return normalized.startsWith('solana:') ? normalized : `solana:${normalized}`;
284
- }
@@ -1,4 +0,0 @@
1
- export * from './BudgetAuthorizer.js';
2
- export * from './SwigSessionAuthorizer.js';
3
- export * from './UnboundedAuthorizer.js';
4
- export * from './makeSessionAuthorizer.js';
@@ -1,104 +0,0 @@
1
- import type { MessagePartialSigner } from '@solana/kit';
2
-
3
- import type { SessionAuthorizer, SessionPolicyProfile } from '../Types.js';
4
- import { BudgetAuthorizer, type BudgetAuthorizerParameters } from './BudgetAuthorizer.js';
5
- import { SwigSessionAuthorizer, type SwigWalletAdapter } from './SwigSessionAuthorizer.js';
6
- import { UnboundedAuthorizer, type UnboundedAuthorizerParameters } from './UnboundedAuthorizer.js';
7
-
8
- export interface MakeSessionAuthorizerParameters {
9
- allowedPrograms?: string[];
10
- buildCloseTx?: (
11
- input: Parameters<NonNullable<BudgetAuthorizerParameters['buildCloseTx']>>[0],
12
- ) => Promise<string> | string;
13
- buildOpenTx?: (
14
- input: Parameters<NonNullable<BudgetAuthorizerParameters['buildOpenTx']>>[0],
15
- ) => Promise<string> | string;
16
- buildTopupTx?: (
17
- input: Parameters<NonNullable<BudgetAuthorizerParameters['buildTopupTx']>>[0],
18
- ) => Promise<string> | string;
19
- profile: SessionPolicyProfile;
20
- rpcUrl?: string;
21
- signer?: MessagePartialSigner;
22
- swigWallet?: SwigWalletAdapter;
23
- }
24
-
25
- /**
26
- * Factory that maps session policy profiles to concrete authorizer classes.
27
- */
28
- export function makeSessionAuthorizer(parameters: MakeSessionAuthorizerParameters): SessionAuthorizer {
29
- const { profile } = parameters;
30
-
31
- switch (profile.profile) {
32
- case 'wallet-budget': {
33
- const signer = requireSigner(parameters.signer, profile.profile);
34
-
35
- // Budget mode is Swig-role-backed and intentionally fail-closed.
36
- if (!parameters.swigWallet?.swigAddress) {
37
- throw new Error('makeSessionAuthorizer requires `swigWallet.swigAddress` for profile "wallet-budget"');
38
- }
39
-
40
- if (parameters.swigWallet.swigRoleId === undefined) {
41
- throw new Error('makeSessionAuthorizer requires `swigWallet.swigRoleId` for profile "wallet-budget"');
42
- }
43
-
44
- return new BudgetAuthorizer({
45
- maxCumulativeAmount: profile.maxCumulativeAmount,
46
- signer,
47
- ...(profile.maxDepositAmount ? { maxDepositAmount: profile.maxDepositAmount } : {}),
48
- ...(profile.validUntil ? { validUntil: profile.validUntil } : {}),
49
- requireApprovalOnTopup: profile.requireApprovalOnTopup,
50
- ...(parameters.allowedPrograms ? { allowedPrograms: parameters.allowedPrograms } : {}),
51
- swig: {
52
- swigAddress: parameters.swigWallet.swigAddress,
53
- swigRoleId: parameters.swigWallet.swigRoleId,
54
- ...(parameters.rpcUrl ? { rpcUrl: parameters.rpcUrl } : {}),
55
- },
56
- ...(parameters.buildOpenTx ? { buildOpenTx: parameters.buildOpenTx } : {}),
57
- ...(parameters.buildTopupTx ? { buildTopupTx: parameters.buildTopupTx } : {}),
58
- ...(parameters.buildCloseTx ? { buildCloseTx: parameters.buildCloseTx } : {}),
59
- });
60
- }
61
-
62
- case 'wallet-manual': {
63
- const signer = requireSigner(parameters.signer, profile.profile);
64
- const authorizerParameters: UnboundedAuthorizerParameters = {
65
- signer,
66
- ...(parameters.allowedPrograms ? { allowedPrograms: parameters.allowedPrograms } : {}),
67
- ...(parameters.buildOpenTx ? { buildOpenTx: parameters.buildOpenTx } : {}),
68
- ...(parameters.buildTopupTx ? { buildTopupTx: parameters.buildTopupTx } : {}),
69
- ...(parameters.buildCloseTx ? { buildCloseTx: parameters.buildCloseTx } : {}),
70
- requiresInteractiveApproval: {
71
- update: profile.requireApprovalOnEveryUpdate,
72
- },
73
- };
74
-
75
- return new UnboundedAuthorizer(authorizerParameters);
76
- }
77
-
78
- case 'swig-time-bound': {
79
- if (!parameters.swigWallet) {
80
- throw new Error('makeSessionAuthorizer requires `swigWallet` for profile "swig-time-bound"');
81
- }
82
-
83
- return new SwigSessionAuthorizer({
84
- policy: profile,
85
- wallet: parameters.swigWallet,
86
- ...(parameters.rpcUrl ? { rpcUrl: parameters.rpcUrl } : {}),
87
- ...(parameters.allowedPrograms ? { allowedPrograms: parameters.allowedPrograms } : {}),
88
- ...(parameters.buildOpenTx ? { buildOpenTx: parameters.buildOpenTx } : {}),
89
- ...(parameters.buildTopupTx ? { buildTopupTx: parameters.buildTopupTx } : {}),
90
- ...(parameters.buildCloseTx ? { buildCloseTx: parameters.buildCloseTx } : {}),
91
- });
92
- }
93
- }
94
- }
95
-
96
- function requireSigner(
97
- signer: MessagePartialSigner | undefined,
98
- profile: SessionPolicyProfile['profile'],
99
- ): MessagePartialSigner {
100
- if (!signer) {
101
- throw new Error(`makeSessionAuthorizer requires \`signer\` for profile "${profile}"`);
102
- }
103
- return signer;
104
- }
@@ -1,4 +0,0 @@
1
- export * from './Types.js';
2
- export * from './Voucher.js';
3
- export * from './ChannelStore.js';
4
- export * from './authorizers/index.js';