Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
470 changes: 22 additions & 448 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 15 additions & 4 deletions packages/atxp-base/src/baseAccount.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Account, PaymentMaker, Hex } from '@atxp/client';
import type { AccountId, Source, AuthorizeParams, AuthorizeResult, Destination } from '@atxp/common';
import type { AccountId, Source, AuthorizeParams, AuthorizeResult, Destination, PaymentProtocol } from '@atxp/common';
import { BigNumber } from 'bignumber.js';
import { privateKeyToAccount, PrivateKeyAccount } from 'viem/accounts';
import { BasePaymentMaker } from './basePaymentMaker.js';
Expand Down Expand Up @@ -72,7 +72,14 @@ export class BaseAccount implements Account {
* Authorize a payment through the appropriate channel for Base accounts.
*/
async authorize(params: AuthorizeParams): Promise<AuthorizeResult> {
const { protocol } = params;
if (!params.protocols || params.protocols.length === 0) {
throw new Error('BaseAccount: protocols array must not be empty');
}
const supported: PaymentProtocol[] = ['x402', 'atxp'];
const protocol = params.protocols.find(p => supported.includes(p));
if (!protocol) {
throw new Error(`BaseAccount does not support any of: ${params.protocols.join(', ')}`);
}

switch (protocol) {
case 'x402': {
Expand All @@ -91,6 +98,12 @@ export class BaseAccount implements Account {
return { protocol, credential: paymentHeader as string };
}
case 'atxp': {
if (!params.amount) {
throw new Error('BaseAccount: amount is required for atxp authorize');
}
if (!params.destination) {
throw new Error('BaseAccount: destination is required for atxp authorize');
}
const destination: Destination = {
chain: 'base',
currency: 'USDC',
Expand All @@ -103,8 +116,6 @@ export class BaseAccount implements Account {
}
return { protocol, credential: JSON.stringify({ transactionId: result.transactionId, chain: result.chain, currency: result.currency }) };
}
case 'mpp':
throw new Error('BaseAccount does not support MPP protocol');
default:
throw new Error(`BaseAccount: unsupported protocol '${protocol}'`);
}
Expand Down
51 changes: 27 additions & 24 deletions packages/atxp-base/src/baseAppAccount.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Account, PaymentMaker, AccountId, Source, AuthorizeParams, AuthorizeResult, Destination } from '@atxp/common';
import type { Account, PaymentMaker, AccountId, Source, AuthorizeParams, AuthorizeResult, Destination, PaymentProtocol } from '@atxp/common';
import { WalletTypeEnum, ChainEnum } from '@atxp/common';
import { BigNumber } from 'bignumber.js';
import { getBaseUSDCAddress } from './baseConstants.js';
Expand Down Expand Up @@ -229,29 +229,32 @@ export class BaseAppAccount implements Account {
* Authorize a payment through the appropriate channel for Base browser accounts.
*/
async authorize(params: AuthorizeParams): Promise<AuthorizeResult> {
const { protocol } = params;
if (!params.protocols || params.protocols.length === 0) {
throw new Error('BaseAppAccount: protocols array must not be empty');
}
const supported: PaymentProtocol[] = ['atxp'];
const protocol = params.protocols.find(p => supported.includes(p));
if (!protocol) {
throw new Error(`BaseAppAccount does not support any of: ${params.protocols.join(', ')}`);
}

switch (protocol) {
case 'atxp': {
const chain = this.chainId === 84532 ? ChainEnum.BaseSepolia : ChainEnum.Base;
const destination: Destination = {
chain,
currency: 'USDC',
address: params.destination,
amount: new BigNumber(params.amount),
};
const result = await this.paymentMakers[0].makePayment([destination], params.memo || '');
if (!result) {
throw new Error('BaseAppAccount: payment execution returned no result');
}
return { protocol, credential: JSON.stringify(result) };
}
case 'x402':
throw new Error('BaseAppAccount does not support x402 protocol');
case 'mpp':
throw new Error('BaseAppAccount does not support MPP protocol');
default:
throw new Error(`BaseAppAccount: unsupported protocol '${protocol}'`);
if (!params.amount) {
throw new Error('BaseAppAccount: amount is required for atxp authorize');
}
if (!params.destination) {
throw new Error('BaseAppAccount: destination is required for atxp authorize');
}
const chain = this.chainId === 84532 ? ChainEnum.BaseSepolia : ChainEnum.Base;
const destination: Destination = {
chain,
currency: 'USDC',
address: params.destination,
amount: new BigNumber(params.amount),
};
const result = await this.paymentMakers[0].makePayment([destination], params.memo || '');
if (!result) {
throw new Error('BaseAppAccount: payment execution returned no result');
}
return { protocol, credential: JSON.stringify(result) };
}
}
}
5 changes: 2 additions & 3 deletions packages/atxp-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,9 @@ export {
MPPProtocolHandler
} from './mppProtocolHandler.js';

// Payment client for centralized authorize flow
// Payment header utilities for protocol-specific headers
export {
PaymentClient,
buildPaymentHeaders,
type AuthorizeResult
} from './paymentClient.js';
} from './paymentHeaders.js';

34 changes: 7 additions & 27 deletions packages/atxp-client/src/mppProtocolHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,7 @@ import {
hasMPPMCPError,
} from '@atxp/mpp';
import { BigNumber } from 'bignumber.js';
import { PaymentClient, buildPaymentHeaders } from './paymentClient.js';

/**
* Configuration for MPP protocol handler.
*/
export interface MPPProtocolHandlerConfig {
accountsServer?: string;
}
import { buildPaymentHeaders } from './paymentHeaders.js';

/**
* Protocol handler for MPP (Machine Payments Protocol) payment challenges.
Expand All @@ -27,16 +20,11 @@ export interface MPPProtocolHandlerConfig {
* 1. HTTP level: HTTP 402 with WWW-Authenticate: Payment header
* 2. MCP level: JSON-RPC error with code -32042 containing MPP data
*
* Handles the challenge by calling /authorize/mpp on the accounts service
* Handles the challenge by calling /authorize/auto on the accounts service
* and retrying with an Authorization: Payment header.
*/
export class MPPProtocolHandler implements ProtocolHandler {
readonly protocol = 'mpp';
private accountsServer: string;

constructor(config?: MPPProtocolHandlerConfig) {
this.accountsServer = config?.accountsServer ?? 'https://accounts.atxp.ai';
}

async canHandle(response: Response): Promise<boolean> {
if (hasMPPChallenge(response)) return true;
Expand Down Expand Up @@ -173,28 +161,20 @@ export class MPPProtocolHandler implements ProtocolHandler {
const { account, logger, fetchFn, onPayment } = config;

try {
logger.debug('MPP: calling /authorize/mpp on accounts service');
const client = new PaymentClient({
accountsServer: this.accountsServer,
logger,
fetchFn,
});

const accountId = await account.getAccountId();
logger.debug('MPP: calling /authorize/auto on accounts service');

let authorizeResult;
try {
authorizeResult = await client.authorize({
account,
userId: accountId,
authorizeResult = await account.authorize({
protocols: ['mpp'],
destination: typeof originalRequest.url === 'string' ? originalRequest.url : originalRequest.url.toString(),
protocol: 'mpp',
challenge,
});
} catch (authorizeError) {
// AuthorizationError = server rejected the request (HTTP error from accounts)
// Other errors = data validation or network failure
if (authorizeError instanceof AuthorizationError) {
logger.debug(`MPP: /authorize/mpp rejected (${authorizeError.statusCode}), returning original response`);
logger.debug(`MPP: authorize rejected (${authorizeError.statusCode}), returning original response`);
return this.reconstructResponse(bodyText, originalResponse);
}
throw authorizeError;
Expand Down
Loading
Loading