1- import { CdpClient } from '@coinbase/cdp-sdk' ;
21import { type Address } from 'viem' ;
32import { prepareCharge } from './prepareCharge.js' ;
43import type { ChargeOptions , ChargeResult } from './types.js' ;
4+ import { createCdpClientOrThrow } from './utils/createCdpClientOrThrow.js' ;
5+ import { getExistingSmartWalletOrThrow } from './utils/getExistingSmartWalletOrThrow.js' ;
6+ import { sendUserOpAndWait } from './utils/sendUserOpAndWait.js' ;
57
68/**
79 * Prepares and executes a charge for a given spend permission.
@@ -14,7 +16,7 @@ import type { ChargeOptions, ChargeResult } from './types.js';
1416 *
1517 * The function will:
1618 * - Use the provided CDP credentials or fall back to environment variables
17- * - Create or retrieve a smart wallet to act as the subscription owner
19+ * - Get the existing smart wallet that acts as the subscription owner
1820 * - Prepare the charge call data using the subscription ID
1921 * - Execute the charge transaction using the smart wallet
2022 * - Optionally use a paymaster for transaction sponsorship
@@ -86,53 +88,15 @@ export async function charge(options: ChargeOptions): Promise<ChargeResult> {
8688 } = options ;
8789
8890 // Step 1: Initialize CDP client with provided credentials or environment variables
89- let cdpClient : CdpClient ;
90-
91- try {
92- cdpClient = new CdpClient ( {
93- apiKeyId : cdpApiKeyId ,
94- apiKeySecret : cdpApiKeySecret ,
95- walletSecret : cdpWalletSecret ,
96- } ) ;
97- } catch ( error ) {
98- // Re-throw with more context about what credentials are missing
99- const errorMessage = error instanceof Error ? error . message : String ( error ) ;
100- throw new Error (
101- `Failed to initialize CDP client for subscription charge. ${ errorMessage } \n\nPlease ensure you have set the required CDP credentials either:\n1. As environment variables: CDP_API_KEY_ID, CDP_API_KEY_SECRET, CDP_WALLET_SECRET\n2. As function parameters: cdpApiKeyId, cdpApiKeySecret, cdpWalletSecret\n\nYou can get these credentials from https://portal.cdp.coinbase.com/`
102- ) ;
103- }
91+ const cdpClient = createCdpClientOrThrow (
92+ { cdpApiKeyId, cdpApiKeySecret, cdpWalletSecret } ,
93+ 'subscription charge'
94+ ) ;
10495
10596 // Step 2: Get the existing EVM account and smart wallet
10697 // NOTE: We use get() instead of getOrCreate() to ensure the wallet already exists.
10798 // The wallet should have been created prior to executing a charge on it.
108- let smartWallet ;
109- try {
110- // First get the existing EOA that owns the smart wallet
111- const eoaAccount = await cdpClient . evm . getAccount ( { name : walletName } ) ;
112-
113- if ( ! eoaAccount ) {
114- throw new Error (
115- `EOA wallet "${ walletName } " not found. The wallet must be created before executing a charge. Use getOrCreateSubscriptionOwnerWallet() to create the wallet first.`
116- ) ;
117- }
118-
119- // Get the existing smart wallet with the EOA as owner
120- // NOTE: Both the EOA wallet and smart wallet are given the same name intentionally.
121- // This simplifies wallet management and ensures consistency across the system.
122- smartWallet = await cdpClient . evm . getSmartAccount ( {
123- name : walletName , // Same name as the EOA wallet
124- owner : eoaAccount ,
125- } ) ;
126-
127- if ( ! smartWallet ) {
128- throw new Error (
129- `Smart wallet "${ walletName } " not found. The wallet must be created before executing a charge. Use getOrCreateSubscriptionOwnerWallet() to create the wallet first.`
130- ) ;
131- }
132- } catch ( error ) {
133- const errorMessage = error instanceof Error ? error . message : String ( error ) ;
134- throw new Error ( `Failed to get charge smart wallet "${ walletName } ": ${ errorMessage } ` ) ;
135- }
99+ const smartWallet = await getExistingSmartWalletOrThrow ( cdpClient , walletName , 'charge' ) ;
136100
137101 // Step 3: Prepare the charge call data (including optional recipient transfer)
138102 const chargeCalls = await prepareCharge ( { id, amount, testnet, recipient } ) ;
@@ -143,49 +107,15 @@ export async function charge(options: ChargeOptions): Promise<ChargeResult> {
143107
144108 // Step 5: Execute the charge transaction(s) using the smart wallet
145109 // Smart wallets can batch multiple calls and use paymasters for gas sponsorship
146- let transactionHash : string | undefined ;
147-
148- try {
149- // Build the calls array for the smart wallet
150- const calls = chargeCalls . map ( ( call ) => ( {
151- to : call . to ,
152- data : call . data ,
153- value : call . value ,
154- } ) ) ;
155-
156- // For smart wallets, we can send all calls in a single user operation
157- // This is more efficient and allows for better paymaster integration
158-
159- // Send the user operation
160- const userOpResult = await networkSmartWallet . sendUserOperation ( {
161- calls,
162- ...( paymasterUrl && { paymasterUrl } ) ,
163- } ) ;
164-
165- // The sendUserOperation returns { smartAccountAddress, status: "broadcast", userOpHash }
166- // We need to wait for the operation to complete to get the transaction hash
167- const completedOp = await networkSmartWallet . waitForUserOperation ( {
168- userOpHash : userOpResult . userOpHash ,
169- waitOptions : {
170- timeoutSeconds : 60 , // Wait up to 60 seconds for the operation to complete
171- } ,
172- } ) ;
173-
174- // Check if the operation was successful
175- if ( completedOp . status === 'failed' ) {
176- throw new Error ( `User operation failed: ${ userOpResult . userOpHash } ` ) ;
177- }
178-
179- // For completed operations, we have the transaction hash
180- transactionHash = completedOp . transactionHash ;
181- } catch ( error ) {
182- const errorMessage = error instanceof Error ? error . message : String ( error ) ;
183- throw new Error ( `Failed to execute charge transaction with smart wallet: ${ errorMessage } ` ) ;
184- }
185-
186- if ( ! transactionHash ) {
187- throw new Error ( 'No transaction hash received from charge execution' ) ;
188- }
110+ // For smart wallets, we can send all calls in a single user operation
111+ // This is more efficient and allows for better paymaster integration
112+ const transactionHash = await sendUserOpAndWait (
113+ networkSmartWallet ,
114+ chargeCalls ,
115+ paymasterUrl ,
116+ 60 , // Wait up to 60 seconds for the operation to complete
117+ 'charge'
118+ ) ;
189119
190120 // Return success result
191121 return {
0 commit comments