@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,128 +0,0 @@
1
- import { Store } from 'mppx';
2
-
3
- import type { ChannelState } from './Types.js';
4
-
5
- const CHANNEL_KEY_PREFIX = 'solana-session:channel:';
6
- const storeCache = new WeakMap<Store.Store, ChannelStore>();
7
-
8
- export interface ChannelStore {
9
- getChannel(channelId: string): Promise<ChannelState | null>;
10
- updateChannel(
11
- channelId: string,
12
- updater: (current: ChannelState | null) => ChannelState | null,
13
- ): Promise<ChannelState | null>;
14
- }
15
-
16
- export function fromStore(store: Store.Store): ChannelStore {
17
- const cached = storeCache.get(store);
18
- if (cached) {
19
- return cached;
20
- }
21
-
22
- const locks = new Map<string, Promise<void>>();
23
-
24
- async function update(
25
- channelId: string,
26
- updater: (current: ChannelState | null) => ChannelState | null,
27
- ): Promise<ChannelState | null> {
28
- const key = toStoreKey(channelId);
29
-
30
- while (locks.has(key)) {
31
- await locks.get(key);
32
- }
33
-
34
- let release!: () => void;
35
- locks.set(
36
- key,
37
- new Promise<void>(resolve => {
38
- release = resolve;
39
- }),
40
- );
41
-
42
- try {
43
- const current = await store.get<ChannelState | null>(key);
44
- const next = updater(current);
45
-
46
- if (next) {
47
- await store.put(key, next);
48
- } else {
49
- await store.delete(key);
50
- }
51
-
52
- return next;
53
- } finally {
54
- locks.delete(key);
55
- release();
56
- }
57
- }
58
-
59
- const channelStore: ChannelStore = {
60
- async getChannel(channelId) {
61
- return await store.get<ChannelState | null>(toStoreKey(channelId));
62
- },
63
- async updateChannel(channelId, updater) {
64
- return await update(channelId, updater);
65
- },
66
- };
67
-
68
- storeCache.set(store, channelStore);
69
- return channelStore;
70
- }
71
-
72
- export async function deductFromChannel(
73
- store: ChannelStore,
74
- channelId: string,
75
- amount: bigint,
76
- ): Promise<{ channel: ChannelState; ok: boolean }> {
77
- if (amount < 0n) {
78
- throw new Error('Deduction amount must be non-negative');
79
- }
80
-
81
- let deducted = false;
82
-
83
- const channel = await store.updateChannel(channelId, current => {
84
- deducted = false;
85
-
86
- if (!current) {
87
- return null;
88
- }
89
-
90
- const settledAmount = parseAtomicAmount(current.settledAmount, 'settledAmount');
91
- const authorizedAmount = parseAtomicAmount(current.lastAuthorizedAmount, 'lastAuthorizedAmount');
92
- const escrowedAmount = parseAtomicAmount(current.escrowedAmount, 'escrowedAmount');
93
-
94
- const spendCeiling = authorizedAmount < escrowedAmount ? authorizedAmount : escrowedAmount;
95
- const nextSettledAmount = settledAmount + amount;
96
-
97
- if (nextSettledAmount > spendCeiling) {
98
- return current;
99
- }
100
-
101
- deducted = true;
102
- return {
103
- ...current,
104
- settledAmount: nextSettledAmount.toString(),
105
- };
106
- });
107
-
108
- if (!channel) {
109
- throw new Error('channel not found');
110
- }
111
-
112
- return {
113
- channel,
114
- ok: deducted,
115
- };
116
- }
117
-
118
- function toStoreKey(channelId: string): string {
119
- return `${CHANNEL_KEY_PREFIX}${channelId}`;
120
- }
121
-
122
- function parseAtomicAmount(value: string, field: string): bigint {
123
- try {
124
- return BigInt(value);
125
- } catch {
126
- throw new Error(`Invalid atomic amount in ${field}: ${value}`);
127
- }
128
- }
@@ -1,189 +0,0 @@
1
- export type AuthorizationMode = 'regular_budget' | 'regular_unbounded' | 'swig_session';
2
-
3
- export interface SessionVoucher {
4
- chainId: string;
5
- channelId: string;
6
- channelProgram: string;
7
- cumulativeAmount: string;
8
- expiresAt?: string;
9
- meter: string;
10
- payer: string;
11
- recipient: string;
12
- sequence: number;
13
- serverNonce: string;
14
- units: string;
15
- }
16
-
17
- export interface SignedSessionVoucher {
18
- signature: string;
19
- signatureType: 'ed25519' | 'swig-session';
20
- signer: string;
21
- voucher: SessionVoucher;
22
- }
23
-
24
- export interface ChannelState {
25
- asset: { decimals: number; kind: 'sol' | 'spl'; mint?: string };
26
- authority: {
27
- delegatedSessionKey?: string;
28
- swigRoleId?: number;
29
- wallet: string;
30
- };
31
- authorizationMode: AuthorizationMode;
32
- channelId: string;
33
- createdAt: string;
34
- escrowedAmount: string;
35
- expiresAtUnix: number | null;
36
- lastAuthorizedAmount: string;
37
- lastSequence: number;
38
- openSlot: number;
39
- payer: string;
40
- recipient: string;
41
- serverNonce: string;
42
- settledAmount: string;
43
- status: 'closed' | 'closing' | 'expired' | 'open';
44
- }
45
-
46
- export type SessionCredentialPayload =
47
- | {
48
- action: 'close';
49
- channelId: string;
50
- closeTx?: string;
51
- voucher: SignedSessionVoucher;
52
- }
53
- | {
54
- action: 'open';
55
- authorizationMode: AuthorizationMode;
56
- capabilities?: {
57
- allowedActions?: string[];
58
- maxCumulativeAmount?: string;
59
- };
60
- channelId: string;
61
- depositAmount: string;
62
- expiresAt?: string;
63
- openTx: string;
64
- payer: string;
65
- voucher: SignedSessionVoucher;
66
- }
67
- | {
68
- action: 'topup';
69
- additionalAmount: string;
70
- channelId: string;
71
- topupTx: string;
72
- }
73
- | {
74
- action: 'update';
75
- channelId: string;
76
- voucher: SignedSessionVoucher;
77
- };
78
-
79
- export interface VoucherVerifier {
80
- verify(voucher: SignedSessionVoucher, channel: ChannelState): Promise<boolean>;
81
- }
82
-
83
- export interface SessionAuthorizer {
84
- authorizeClose(input: AuthorizeCloseInput): Promise<AuthorizedClose>;
85
- authorizeOpen(input: AuthorizeOpenInput): Promise<AuthorizedOpen>;
86
- authorizeTopup(input: AuthorizeTopupInput): Promise<AuthorizedTopup>;
87
- authorizeUpdate(input: AuthorizeUpdateInput): Promise<AuthorizedUpdate>;
88
- getCapabilities(): AuthorizerCapabilities;
89
- getMode(): AuthorizationMode;
90
- }
91
-
92
- export interface AuthorizeOpenInput {
93
- asset: { decimals: number; kind: 'sol' | 'spl'; mint?: string };
94
- channelId: string;
95
- channelProgram: string;
96
- depositAmount: string;
97
- network: string;
98
- pricing?: { amountPerUnit: string; meter: string; unit: string };
99
- recipient: string;
100
- serverNonce: string;
101
- }
102
-
103
- export interface AuthorizedOpen {
104
- capabilities: AuthorizerCapabilities;
105
- expiresAt?: string;
106
- openTx: string;
107
- voucher: SignedSessionVoucher;
108
- }
109
-
110
- export interface AuthorizeUpdateInput {
111
- channelId: string;
112
- channelProgram: string;
113
- cumulativeAmount: string;
114
- meter: string;
115
- network: string;
116
- recipient: string;
117
- sequence: number;
118
- serverNonce: string;
119
- units: string;
120
- }
121
-
122
- export interface AuthorizedUpdate {
123
- voucher: SignedSessionVoucher;
124
- }
125
-
126
- export interface AuthorizeTopupInput {
127
- additionalAmount: string;
128
- channelId: string;
129
- channelProgram: string;
130
- network: string;
131
- }
132
-
133
- export interface AuthorizedTopup {
134
- topupTx: string;
135
- }
136
-
137
- export interface AuthorizeCloseInput {
138
- channelId: string;
139
- channelProgram: string;
140
- finalCumulativeAmount: string;
141
- network: string;
142
- recipient: string;
143
- sequence: number;
144
- serverNonce: string;
145
- }
146
-
147
- export interface AuthorizedClose {
148
- closeTx?: string;
149
- voucher: SignedSessionVoucher;
150
- }
151
-
152
- export interface AuthorizerCapabilities {
153
- allowedActions?: Array<'close' | 'open' | 'topup' | 'update'>;
154
- allowedPrograms?: string[];
155
- expiresAt?: string;
156
- maxCumulativeAmount?: string;
157
- maxDepositAmount?: string;
158
- mode: AuthorizationMode;
159
- requiresInteractiveApproval: {
160
- close: boolean;
161
- open: boolean;
162
- topup: boolean;
163
- update: boolean;
164
- };
165
- }
166
-
167
- export type SessionPolicyProfile =
168
- | {
169
- autoTopup?: {
170
- amount: string;
171
- enabled: boolean;
172
- triggerBelow: string;
173
- };
174
- depositLimit?: string;
175
- profile: 'swig-time-bound';
176
- spendLimit?: string;
177
- ttlSeconds: number;
178
- }
179
- | {
180
- maxCumulativeAmount: string;
181
- maxDepositAmount?: string;
182
- profile: 'wallet-budget';
183
- requireApprovalOnTopup?: boolean;
184
- validUntil?: string;
185
- }
186
- | {
187
- profile: 'wallet-manual';
188
- requireApprovalOnEveryUpdate: boolean;
189
- };
@@ -1,158 +0,0 @@
1
- import {
2
- address,
3
- createSignableMessage,
4
- getBase58Decoder,
5
- getBase58Encoder,
6
- getPublicKeyFromAddress,
7
- type MessagePartialSigner,
8
- signatureBytes,
9
- verifySignature,
10
- } from '@solana/kit';
11
-
12
- import type { SessionVoucher, SignedSessionVoucher } from './Types.js';
13
-
14
- const DOMAIN_SEPARATOR = 'solana-mpp-session-voucher-v1:';
15
- const textEncoder = new TextEncoder();
16
- const base58Encoder = getBase58Encoder();
17
- const base58Decoder = getBase58Decoder();
18
-
19
- export function serializeVoucher(voucher: SessionVoucher): Uint8Array {
20
- const canonical = JSON.stringify(canonicalize(voucher));
21
- return textEncoder.encode(`${DOMAIN_SEPARATOR}${canonical}`);
22
- }
23
-
24
- export async function signVoucher(
25
- signer: MessagePartialSigner,
26
- voucher: SessionVoucher,
27
- ): Promise<SignedSessionVoucher> {
28
- const signable = createSignableMessage(serializeVoucher(voucher));
29
- const [signatureDictionary] = await signer.signMessages([signable]);
30
- const signatureValue = signatureDictionary[signer.address];
31
-
32
- if (!signatureValue) {
33
- throw new Error('Signer did not produce a voucher signature');
34
- }
35
-
36
- return {
37
- signature: base58Decoder.decode(signatureValue),
38
- signatureType: 'ed25519',
39
- signer: signer.address,
40
- voucher,
41
- };
42
- }
43
-
44
- export async function verifyVoucherSignature(signed: SignedSessionVoucher): Promise<boolean> {
45
- if (signed.signatureType !== 'ed25519' && signed.signatureType !== 'swig-session') {
46
- return false;
47
- }
48
-
49
- try {
50
- const publicKey = await getPublicKeyFromAddress(address(signed.signer));
51
- const signature = signatureBytes(base58Encoder.encode(signed.signature));
52
- const serialized = serializeVoucher(signed.voucher);
53
- return await verifySignature(publicKey, signature, serialized);
54
- } catch {
55
- return false;
56
- }
57
- }
58
-
59
- export function parseVoucherFromPayload(payload: unknown): SignedSessionVoucher {
60
- const maybeRoot = asRecord(payload, 'Session payload must be an object');
61
- const source = hasSignedVoucherEnvelope(maybeRoot)
62
- ? maybeRoot
63
- : asRecord(maybeRoot.voucher, 'Payload must include a signed voucher in `voucher`');
64
-
65
- const rawVoucher = asRecord(source.voucher, 'Signed voucher must include `voucher` object');
66
-
67
- const signatureTypeRaw = readString(source, 'signatureType');
68
- if (signatureTypeRaw !== 'ed25519' && signatureTypeRaw !== 'swig-session') {
69
- throw new Error('Signed voucher `signatureType` must be "ed25519" or "swig-session"');
70
- }
71
-
72
- const expiresAt = readOptionalString(rawVoucher, 'expiresAt');
73
-
74
- return {
75
- signature: readString(source, 'signature'),
76
- signatureType: signatureTypeRaw,
77
- signer: readString(source, 'signer'),
78
- voucher: {
79
- channelId: readString(rawVoucher, 'channelId'),
80
- cumulativeAmount: readString(rawVoucher, 'cumulativeAmount'),
81
- meter: readString(rawVoucher, 'meter'),
82
- payer: readString(rawVoucher, 'payer'),
83
- recipient: readString(rawVoucher, 'recipient'),
84
- sequence: readInteger(rawVoucher, 'sequence'),
85
- units: readString(rawVoucher, 'units'),
86
- ...(expiresAt !== undefined ? { expiresAt } : {}),
87
- chainId: readString(rawVoucher, 'chainId'),
88
- channelProgram: readString(rawVoucher, 'channelProgram'),
89
- serverNonce: readString(rawVoucher, 'serverNonce'),
90
- },
91
- };
92
- }
93
-
94
- function canonicalize(value: unknown): unknown {
95
- if (Array.isArray(value)) {
96
- return value.map(item => canonicalize(item));
97
- }
98
-
99
- if (value && typeof value === 'object') {
100
- const record = value as Record<string, unknown>;
101
- const normalized: Record<string, unknown> = {};
102
-
103
- for (const key of Object.keys(record).sort()) {
104
- const item = record[key];
105
- if (item !== undefined) {
106
- normalized[key] = canonicalize(item);
107
- }
108
- }
109
-
110
- return normalized;
111
- }
112
-
113
- return value;
114
- }
115
-
116
- function hasSignedVoucherEnvelope(value: Record<string, unknown>): value is {
117
- signature: unknown;
118
- signatureType: unknown;
119
- signer: unknown;
120
- voucher: unknown;
121
- } {
122
- return 'voucher' in value && 'signer' in value && 'signature' in value && 'signatureType' in value;
123
- }
124
-
125
- function asRecord(value: unknown, message: string): Record<string, unknown> {
126
- if (!value || typeof value !== 'object') {
127
- throw new Error(message);
128
- }
129
-
130
- return value as Record<string, unknown>;
131
- }
132
-
133
- function readString(record: Record<string, unknown>, key: string): string {
134
- const value = record[key];
135
- if (typeof value !== 'string') {
136
- throw new Error(`Expected string field: ${key}`);
137
- }
138
- return value;
139
- }
140
-
141
- function readOptionalString(record: Record<string, unknown>, key: string): string | undefined {
142
- const value = record[key];
143
- if (value === undefined) {
144
- return undefined;
145
- }
146
- if (typeof value !== 'string') {
147
- throw new Error(`Expected optional string field: ${key}`);
148
- }
149
- return value;
150
- }
151
-
152
- function readInteger(record: Record<string, unknown>, key: string): number {
153
- const value = record[key];
154
- if (!Number.isInteger(value)) {
155
- throw new Error(`Expected integer field: ${key}`);
156
- }
157
- return value as number;
158
- }