@circle-fin/bridge-kit 1.1.2 → 1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @circle-fin/bridge-kit
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add computeFee function to CustomFeePolicy that receives human-readable amounts (e.g., '100' for 100 USDC), deprecate calculateFee which receives smallest-unit amounts
8
+ - Enhanced decimal format validation to standardize on strict dot (.) decimal separators. Amount, maxFee, and customFee fields now use a unified dot-decimal format. Supported examples include "1000.50" and "1.5"
9
+ - Introduce the `BridgeChain` enum and `BridgeChainIdentifier` type to restrict chain selection to only CCTPv2-supported chains. This provides IDE autocomplete limited to valid bridge chains and ensures that passing an unsupported chain triggers a compile-time error.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated BridgeKit to return `BridgeResults` in human readable units for `bridge()` and `retry()`. For example, when printing out the results of a `kit.bridge()` call with 1 USDC, the result's `amount` attribute will now show `'1.0'` instead of `'1000000'`.
14
+ - Validation errors now use standardized error codes for consistent error handling. All validation failures return the same `KitError` structure as other SDK errors.
15
+
16
+ **Migration:** If you're catching validation errors, update your error handling:
17
+ - Check `error.code === 1098` instead of `instanceof ValidationError`
18
+ - Access details via `error.cause?.trace?.validationErrors` instead of `error.errors`
19
+
20
+ The legacy `ValidationError` class remains available for backward compatibility.
21
+
22
+ - Documented the exact custom-fee flow across Bridge Kit’s README and JSDoc.
23
+ Clarified that kit-level and per-transfer custom fees are added to the transfer debit before CCTPv2 runs.
24
+
3
25
  ## 1.1.2
4
26
 
5
27
  ### Patch Changes
package/QUICKSTART.md CHANGED
@@ -56,7 +56,7 @@ Before diving into the examples, it's important to understand the two address co
56
56
 
57
57
  ```typescript
58
58
  // User-controlled example - address resolved automatically
59
- const adapter = createAdapterFromPrivateKey({
59
+ const adapter = createViemAdapterFromPrivateKey({
60
60
  privateKey: process.env.PRIVATE_KEY,
61
61
  // addressContext: 'user-controlled' is the default
62
62
  })
@@ -84,7 +84,7 @@ await kit.bridge({
84
84
 
85
85
  ```typescript
86
86
  // Developer-controlled example - explicit address control
87
- const adapter = createAdapterFromPrivateKey({
87
+ const adapter = createViemAdapterFromPrivateKey({
88
88
  privateKey: process.env.PRIVATE_KEY,
89
89
  capabilities: {
90
90
  addressContext: 'developer-controlled',
@@ -190,14 +190,14 @@ The factory methods make getting started incredibly simple. Plus, you can create
190
190
 
191
191
  ```typescript
192
192
  import { BridgeKit } from '@circle-fin/bridge-kit'
193
- import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
193
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
194
194
 
195
195
  // Initialize the kit (CCTPv2 provider included by default)
196
196
  const kit = new BridgeKit()
197
197
 
198
198
  // Create ONE adapter that can work across chains!
199
199
  // Uses 'user-controlled' by default - addresses resolved automatically
200
- const adapter = createAdapterFromPrivateKey({
200
+ const adapter = createViemAdapterFromPrivateKey({
201
201
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
202
202
  })
203
203
 
@@ -222,12 +222,12 @@ const result = await kit.bridge({
222
222
 
223
223
  ```typescript
224
224
  import { BridgeKit } from '@circle-fin/bridge-kit'
225
- import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
225
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
226
226
 
227
227
  const kit = new BridgeKit()
228
228
 
229
229
  // Create adapter with explicit address control
230
- const adapter = createAdapterFromPrivateKey({
230
+ const adapter = createViemAdapterFromPrivateKey({
231
231
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
232
232
  capabilities: {
233
233
  addressContext: 'developer-controlled', // Explicit address control
@@ -257,13 +257,13 @@ const result = await kit.bridge({
257
257
 
258
258
  ```typescript
259
259
  import { BridgeKit } from '@circle-fin/bridge-kit'
260
- import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
260
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
261
261
  import { createPublicClient, http } from 'viem'
262
262
 
263
263
  const kit = new BridgeKit()
264
264
 
265
265
  // Production-ready setup with custom RPC endpoints
266
- const adapter = createAdapterFromPrivateKey({
266
+ const adapter = createViemAdapterFromPrivateKey({
267
267
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
268
268
  capabilities: {
269
269
  addressContext: 'user-controlled', // Explicit for clarity in production
@@ -296,13 +296,13 @@ For browser environments with wallet providers like MetaMask:
296
296
 
297
297
  ```typescript
298
298
  import { BridgeKit } from '@circle-fin/bridge-kit'
299
- import { createAdapterFromProvider } from '@circle-fin/adapter-viem-v2'
299
+ import { createViemAdapterFromProvider } from '@circle-fin/adapter-viem-v2'
300
300
 
301
301
  const kit = new BridgeKit()
302
302
 
303
303
  // Create adapters from browser wallet providers
304
304
  // Browser wallets are typically user-controlled (one connected address)
305
- const adapter = await createAdapterFromProvider({
305
+ const adapter = await createViemAdapterFromProvider({
306
306
  provider: window.ethereum,
307
307
  capabilities: {
308
308
  addressContext: 'user-controlled', // Browser wallets = user-controlled
@@ -412,7 +412,7 @@ type AdapterContext = { adapter: Adapter; chain: ChainIdentifier }
412
412
  **Chain-agnostic adapter creation**:
413
413
 
414
414
  ```typescript
415
- const adapter = createAdapterFromPrivateKey({
415
+ const adapter = createViemAdapterFromPrivateKey({
416
416
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
417
417
  chain: 'Ethereum',
418
418
  })
@@ -527,6 +527,122 @@ interface BridgeConfig {
527
527
  - **FAST**: Optimized for speed with potentially higher fees
528
528
  - **SLOW**: Optimized for cost with zero transfer fees but longer confirmation times
529
529
 
530
+ ## Custom Fees
531
+
532
+ ### Understanding How Custom Fees Work
533
+
534
+ Custom fees allow you to charge developer fees on cross-chain USDC transfers. **The custom fee is added on top of the transfer amount.** The wallet signs for `amount + customFee`, the custom fee is split (10% to Circle, 90% to your `recipientAddress`) on the source chain, and the entire transfer amount continues through CCTPv2 unchanged.
535
+
536
+ ### Step-by-Step Example: 1,000 USDC Transfer with 10 USDC Custom Fee
537
+
538
+ ```typescript
539
+ import { BridgeKit } from '@circle-fin/bridge-kit'
540
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
541
+
542
+ const kit = new BridgeKit()
543
+ const adapter = createViemAdapterFromPrivateKey({
544
+ privateKey: process.env.PRIVATE_KEY as `0x${string}`,
545
+ })
546
+
547
+ // User wants to transfer 1,000 USDC
548
+ // You want to charge a 10 USDC custom fee
549
+ await kit.bridge({
550
+ from: { adapter, chain: 'Ethereum' },
551
+ to: { adapter, chain: 'Base' },
552
+ amount: '1000', // ← Amount forwarded to CCTPv2
553
+ config: {
554
+ customFee: {
555
+ value: '10', // ← Additional debit on top of the transfer amount
556
+ recipientAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0',
557
+ },
558
+ },
559
+ })
560
+ ```
561
+
562
+ #### Breakdown of What Happens:
563
+
564
+ | Stage | Amount | Description |
565
+ | ------------------------------------- | ---------- | -------------------------------------------------- |
566
+ | **Wallet debit** | 1,010 USDC | User signs for transfer (1,000) + custom fee (10) |
567
+ | **Custom fee → Circle (10%)** | 1 USDC | Automatically routed to Circle on the source chain |
568
+ | **Custom fee → Your recipient (90%)** | 9 USDC | Sent to `0x742d35Cc...bEb0` |
569
+ | **Forwarded to CCTPv2** | 1,000 USDC | Transfer amount proceeds unchanged |
570
+ | **CCTPv2 fee (FAST 1 bps example)** | -0.1 USDC | Protocol fee taken from the transfer amount |
571
+ | **Destination receives** | 999.9 USDC | Amount minted on Base |
572
+
573
+ **Summary:**
574
+
575
+ - **Circle receives:** 10% of the custom fee (1 USDC in this example).
576
+ - **You receive:** 90% of the custom fee (9 USDC).
577
+ - **User receives:** Transfer amount minus the CCTPv2 protocol fee (999.9 USDC).
578
+ - **Important:** Circle only participates in the 10% share when a custom fee is present. If you do not configure a custom fee for a transfer, Circle does not collect any additional amount.
579
+ - **Total user debit:** 1,010.1 USDC (1,000 transfer + 10 custom fee + 0.1 protocol fee).
580
+
581
+ ### Per-Transfer Custom Fee
582
+
583
+ Add a one-off fee directly inside `bridge` parameters:
584
+
585
+ ```typescript
586
+ await kit.bridge({
587
+ from: { adapter, chain: 'Ethereum' },
588
+ to: { adapter, chain: 'Base' },
589
+ amount: '100',
590
+ config: {
591
+ customFee: {
592
+ value: '1', // 1 USDC fee
593
+ recipientAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0',
594
+ },
595
+ },
596
+ })
597
+
598
+ // Result (Fast Transfer):
599
+ // - Wallet signs for 101 USDC (100 transfer + 1 custom fee)
600
+ // - Custom fee split: 0.1 USDC to Circle, 0.9 USDC to your recipientAddress wallet
601
+ // - 100 USDC forwarded to CCTPv2
602
+ // - CCTPv2 fee: 0.01 USDC (1 bps)
603
+ // - User receives: 99.99 USDC
604
+ ```
605
+
606
+ ### Kit-Level Fee Policy
607
+
608
+ Set a global fee policy for all transfers:
609
+
610
+ ```typescript
611
+ import { BridgeKit } from '@circle-fin/bridge-kit'
612
+
613
+ const kit = new BridgeKit()
614
+
615
+ kit.setCustomFeePolicy({
616
+ computeFee: (params) => {
617
+ const amount = parseFloat(params.amount)
618
+
619
+ // Example: Charge 1% fee
620
+ const feePercentage = 0.01
621
+ const fee = amount * feePercentage
622
+
623
+ // Return human-readable fee (e.g., '10' for 10 USDC)
624
+ return fee.toFixed(6)
625
+ },
626
+ resolveFeeRecipientAddress: (feePayoutChain) => {
627
+ // Return address for the source chain
628
+ if (feePayoutChain.type === 'solana') {
629
+ return 'YourSolanaAddressBase58Encoded...'
630
+ }
631
+ return '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0'
632
+ },
633
+ })
634
+
635
+ // Now all transfers automatically include the custom fee
636
+ await kit.bridge({
637
+ from: { adapter, chain: 'Ethereum' },
638
+ to: { adapter, chain: 'Base' },
639
+ amount: '1000',
640
+ })
641
+ // Custom fee (10 USDC) calculated and charged automatically
642
+ ```
643
+
644
+ > **Note**: The `calculateFee` function is deprecated. Use `computeFee` instead, which receives human-readable amounts (e.g., `'100'` for 100 USDC) rather than smallest-unit amounts.
645
+
530
646
  ## Examples
531
647
 
532
648
  ### Example 1: EVM to EVM Transfer (Ethereum → Base)
@@ -535,13 +651,13 @@ interface BridgeConfig {
535
651
 
536
652
  ```typescript
537
653
  import { BridgeKit } from '@circle-fin/bridge-kit'
538
- import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
654
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
539
655
 
540
656
  const kit = new BridgeKit()
541
657
 
542
658
  // Create ONE adapter that can work across chains
543
659
  // Uses user-controlled by default - addresses resolved automatically
544
- const adapter = createAdapterFromPrivateKey({
660
+ const adapter = createViemAdapterFromPrivateKey({
545
661
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
546
662
  })
547
663
 
@@ -588,12 +704,12 @@ async function evmToEvmTransfer() {
588
704
 
589
705
  ```typescript
590
706
  import { BridgeKit } from '@circle-fin/bridge-kit'
591
- import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
707
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
592
708
 
593
709
  const kit = new BridgeKit()
594
710
 
595
711
  // Create adapter with explicit address control for enterprise use
596
- const adapter = createAdapterFromPrivateKey({
712
+ const adapter = createViemAdapterFromPrivateKey({
597
713
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
598
714
  capabilities: {
599
715
  addressContext: 'developer-controlled', // Enterprise/multi-address mode
@@ -632,7 +748,7 @@ async function enterpriseEvmToEvmTransfer() {
632
748
 
633
749
  ```typescript
634
750
  import { BridgeKit } from '@circle-fin/bridge-kit'
635
- import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
751
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
636
752
  import { SolanaAdapter } from '@circle-fin/adapter-solana'
637
753
  import { Connection, Keypair } from '@solana/web3.js'
638
754
  import bs58 from 'bs58'
@@ -640,7 +756,7 @@ import bs58 from 'bs58'
640
756
  const kit = new BridgeKit()
641
757
 
642
758
  // Create EVM adapter (Ethereum) using factory method
643
- const ethereumAdapter = createAdapterFromPrivateKey({
759
+ const ethereumAdapter = createViemAdapterFromPrivateKey({
644
760
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
645
761
  chain: 'Ethereum',
646
762
  })
@@ -686,12 +802,12 @@ async function evmToSolanaTransfer() {
686
802
 
687
803
  ```typescript
688
804
  import { BridgeKit } from '@circle-fin/bridge-kit'
689
- import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
805
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
690
806
 
691
807
  const kit = new BridgeKit()
692
808
 
693
809
  // Create ONE adapter that can work across chains
694
- const adapter = createAdapterFromPrivateKey({
810
+ const adapter = createViemAdapterFromPrivateKey({
695
811
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
696
812
  chain: 'Ethereum',
697
813
  })
@@ -821,10 +937,10 @@ retry<
821
937
 
822
938
  ```typescript
823
939
  import { BridgeKit } from '@circle-fin/bridge-kit'
824
- import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
940
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
825
941
 
826
942
  const kit = new BridgeKit()
827
- const adapter = createAdapterFromPrivateKey({
943
+ const adapter = createViemAdapterFromPrivateKey({
828
944
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
829
945
  })
830
946
 
@@ -850,14 +966,14 @@ if (result.state === 'error') {
850
966
 
851
967
  ```typescript
852
968
  import { BridgeKit } from '@circle-fin/bridge-kit'
853
- import { createAdapterFromPrivateKey as createEvmAdapter } from '@circle-fin/adapter-viem-v2'
854
- import { createAdapterFromPrivateKey as createSolAdapter } from '@circle-fin/adapter-solana'
969
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
970
+ import { createSolanaAdapterFromPrivateKey } from '@circle-fin/adapter-solana'
855
971
 
856
972
  const kit = new BridgeKit()
857
- const evm = createEvmAdapter({
973
+ const evm = createViemAdapterFromPrivateKey({
858
974
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
859
975
  })
860
- const sol = createSolAdapter({
976
+ const sol = createSolanaAdapterFromPrivateKey({
861
977
  privateKey: process.env.SOL_PRIVATE_KEY as string,
862
978
  })
863
979
 
@@ -889,10 +1005,10 @@ if (result.state === 'error') {
889
1005
 
890
1006
  ```typescript
891
1007
  import { BridgeKit, BridgeResult } from '@circle-fin/bridge-kit'
892
- import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
1008
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
893
1009
 
894
1010
  const kit = new BridgeKit()
895
- const adapter = createAdapterFromPrivateKey({
1011
+ const adapter = createViemAdapterFromPrivateKey({
896
1012
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
897
1013
  })
898
1014
 
@@ -941,7 +1057,7 @@ if (result.state === 'error') {
941
1057
  const mintStep = result.steps.find((s) => s.name === 'mint')
942
1058
  if (result.state === 'error' && mintStep?.state === 'error') {
943
1059
  // Recreate destination adapter with updated gas settings as needed
944
- const dest = createAdapterFromPrivateKey({
1060
+ const dest = createViemAdapterFromPrivateKey({
945
1061
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
946
1062
  // Provide a higher-priority RPC or adjust client gas policies here if supported by your setup
947
1063
  })
package/README.md CHANGED
@@ -36,6 +36,10 @@ _Making cross-chain stablecoin (USDC, and soon more tokens) transfers as simple
36
36
  - [3. **BridgeConfig** - Transfer Settings](#3-bridgeconfig---transfer-settings)
37
37
  - [Complete Example with All Options](#complete-example-with-all-options)
38
38
  - [Bridge Speed Configuration](#bridge-speed-configuration)
39
+ - [Custom Fees](#custom-fees)
40
+ - [How Custom Fees Work](#how-custom-fees-work)
41
+ - [1000 USDC Transfer Example](#1000-usdc-transfer-example)
42
+ - [Kit-Level Fee Policies](#kit-level-fee-policies)
39
43
  - [Error Handling](#error-handling)
40
44
  - [Retrying Failed Transfers](#retrying-failed-transfers)
41
45
  - [Examples](#examples)
@@ -133,13 +137,13 @@ The factory methods make it incredibly easy to get started with **built-in relia
133
137
 
134
138
  ```typescript
135
139
  import { BridgeKit } from '@circle-fin/bridge-kit'
136
- import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
140
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
137
141
 
138
142
  // Initialize the kit
139
143
  const kit = new BridgeKit()
140
144
 
141
145
  // Create ONE adapter that works across all chains!
142
- const adapter = createAdapterFromPrivateKey({
146
+ const adapter = createViemAdapterFromPrivateKey({
143
147
  privateKey: process.env.PRIVATE_KEY as string,
144
148
  })
145
149
 
@@ -171,7 +175,7 @@ const result = await kit.bridge({
171
175
  })
172
176
 
173
177
  // Or use a different adapter for the destination chain
174
- const baseAdapter = createAdapterFromPrivateKey({
178
+ const baseAdapter = createViemAdapterFromPrivateKey({
175
179
  privateKey: process.env.PRIVATE_KEY as string,
176
180
  })
177
181
 
@@ -194,7 +198,7 @@ const resultWithDifferentAdapter = await kit.bridge({
194
198
  import { createPublicClient, http } from 'viem'
195
199
 
196
200
  // Production-ready setup with custom RPC endpoints
197
- const adapter = createAdapterFromPrivateKey({
201
+ const adapter = createViemAdapterFromPrivateKey({
198
202
  privateKey: process.env.PRIVATE_KEY as string,
199
203
  getPublicClient: ({ chain }) =>
200
204
  createPublicClient({
@@ -222,10 +226,10 @@ const result = await kit.bridge({
222
226
  **Best for**: Browser applications, wallet integrations, user-controlled transactions
223
227
 
224
228
  ```typescript
225
- import { createAdapterFromProvider } from '@circle-fin/adapter-viem-v2'
229
+ import { createViemAdapterFromProvider } from '@circle-fin/adapter-viem-v2'
226
230
 
227
231
  // Create adapters from browser wallet providers
228
- const adapter = await createAdapterFromProvider({
232
+ const adapter = await createViemAdapterFromProvider({
229
233
  provider: window.ethereum,
230
234
  })
231
235
 
@@ -312,7 +316,7 @@ The Bridge Kit supports different configuration patterns to match your use case:
312
316
 
313
317
  ```typescript
314
318
  // Create chain-agnostic adapter
315
- const adapter = createAdapterFromPrivateKey({...})
319
+ const adapter = createViemAdapterFromPrivateKey({...})
316
320
 
317
321
  // Always specify chain explicitly for clarity
318
322
  const adapterContext = { adapter, chain: 'Ethereum' }
@@ -360,6 +364,118 @@ const result = await kit.bridge({
360
364
  })
361
365
  ```
362
366
 
367
+ ## Custom Fees
368
+
369
+ Bridge Kit allows you to charge custom developer fees on cross-chain USDC transfers. Understanding how these fees interact with wallet debits and CCTPv2 protocol fees is crucial for correct implementation.
370
+
371
+ ### How Custom Fees Work
372
+
373
+ Custom fees are **added on top of the transfer amount**, not taken out of it. The wallet signs for `transfer amount + custom fee`, so the user must have enough balance for both values. The entire transfer amount continues through CCTPv2 unchanged, while the custom fee is split on the source chain:
374
+
375
+ - **10%** of the custom fee is automatically routed to Circle.
376
+ - **90%** is sent to your `recipientAddress`.
377
+ - **Important:** Circle only takes the 10% share when a custom fee is actually charged. If you omit a custom fee, Circle does not collect anything beyond the protocol fee.
378
+
379
+ After the custom fee is collected, the transfer amount (e.g., 1,000 USDC) proceeds through CCTPv2, where the protocol applies its own fee (1–14 bps in FAST mode, 0% in STANDARD).
380
+
381
+ **Fee Flow (1,000 USDC transfer + 10 USDC custom fee):**
382
+
383
+ ```
384
+ ┌───────────────────────────────────────────────────────────────┐
385
+ │ User signs: 1,000 USDC transfer + 10 USDC custom fee = 1,010 │
386
+ └──────────────────────┬────────────────────────────────────────┘
387
+
388
+
389
+ ┌───────────────────────────────────────────────────────────────┐
390
+ │ Custom fee distribution (source chain) │
391
+ │ - 1 USDC (10%) → Circle │
392
+ │ - 9 USDC (90%) → Your fee recipient │
393
+ └──────────────────────┬────────────────────────────────────────┘
394
+
395
+
396
+ ┌───────────────────────────────────────────────────────────────┐
397
+ │ CCTPv2 processes full transfer amount (1,000 USDC) │
398
+ │ - Protocol fee example (FAST 1 bps): 0.1 USDC │
399
+ │ - Destination receives: 999.9 USDC │
400
+ └───────────────────────────────────────────────────────────────┘
401
+ ```
402
+
403
+ ### 1,000 USDC Transfer Example
404
+
405
+ **Scenario:** Transfer 1,000 USDC from Ethereum to Base with a 10 USDC custom fee.
406
+
407
+ ```typescript
408
+ import { BridgeKit } from '@circle-fin/bridge-kit'
409
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
410
+
411
+ const kit = new BridgeKit()
412
+ const adapter = createViemAdapterFromPrivateKey({
413
+ privateKey: process.env.PRIVATE_KEY as `0x${string}`,
414
+ })
415
+
416
+ await kit.bridge({
417
+ from: { adapter, chain: 'Ethereum' },
418
+ to: { adapter, chain: 'Base' },
419
+ amount: '1000', // Transfer amount forwarded to CCTPv2
420
+ config: {
421
+ customFee: {
422
+ value: '10', // Additional debit charged on top of the transfer amount
423
+ recipientAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0',
424
+ },
425
+ },
426
+ })
427
+ ```
428
+
429
+ **What happens on-chain:**
430
+
431
+ | Stage | Amount | Description |
432
+ | ----------------------------- | ---------- | ------------------------------------------------------ |
433
+ | **Transfer amount** | 1,000 USDC | Forwarded to CCTPv2 without reduction |
434
+ | **Custom fee debit** | +10 USDC | Wallet signs for 1,010 USDC total |
435
+ | **Custom fee → Circle (10%)** | 1 USDC | Automatically routed to Circle |
436
+ | **Custom fee → You (90%)** | 9 USDC | Sent to `0x742d35Cc...bEb0` (your fee recipient) |
437
+ | **CCTPv2 fee (FAST 1 bps)\*** | -0.1 USDC | Protocol fee taken from the 1,000 USDC transfer amount |
438
+ | **Destination receives** | 999.9 USDC | Amount minted on Base after protocol fee |
439
+
440
+ \* \_CCTPv2 FAST transfers charge 1–14 bps depending on the route; STANDARD transfers charge 0 bps.
441
+
442
+ ### Kit-Level Fee Policies
443
+
444
+ For dynamic fee calculation across all transfers, use kit-level policies:
445
+
446
+ ```typescript
447
+ import { BridgeKit } from '@circle-fin/bridge-kit'
448
+
449
+ const kit = new BridgeKit()
450
+
451
+ kit.setCustomFeePolicy({
452
+ computeFee: (params) => {
453
+ const amount = parseFloat(params.amount)
454
+
455
+ const feePercentage = 0.01 // 1%
456
+ const calculatedFee = amount * feePercentage
457
+
458
+ // Return human-readable fee (e.g., '10' for 10 USDC)
459
+ return calculatedFee.toFixed(6)
460
+ },
461
+ resolveFeeRecipientAddress: (feePayoutChain) => {
462
+ // Return appropriate address for source chain
463
+ return feePayoutChain.type === 'solana'
464
+ ? 'SolanaAddressBase58...'
465
+ : '0xEvmAddress...'
466
+ },
467
+ })
468
+
469
+ // All subsequent bridges will use this policy
470
+ await kit.bridge({
471
+ from: { adapter, chain: 'Ethereum' },
472
+ to: { adapter, chain: 'Base' },
473
+ amount: '1000', // Custom fee calculated automatically
474
+ })
475
+ ```
476
+
477
+ > **Note**: The `calculateFee` function is deprecated. Use `computeFee` instead, which receives human-readable amounts (e.g., `'100'` for 100 USDC) rather than smallest-unit amounts.
478
+
363
479
  ## Error Handling
364
480
 
365
481
  The kit uses a thoughtful error handling approach:
@@ -369,10 +485,10 @@ The kit uses a thoughtful error handling approach:
369
485
 
370
486
  ```typescript
371
487
  import { BridgeKit } from '@circle-fin/bridge-kit'
372
- import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
488
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
373
489
 
374
490
  const kit = new BridgeKit()
375
- const adapter = createAdapterFromPrivateKey({
491
+ const adapter = createViemAdapterFromPrivateKey({
376
492
  privateKey: process.env.PRIVATE_KEY as string,
377
493
  })
378
494
 
@@ -415,10 +531,10 @@ retry<
415
531
 
416
532
  ```typescript
417
533
  import { BridgeKit } from '@circle-fin/bridge-kit'
418
- import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
534
+ import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
419
535
 
420
536
  const kit = new BridgeKit()
421
- const adapter = createAdapterFromPrivateKey({
537
+ const adapter = createViemAdapterFromPrivateKey({
422
538
  privateKey: process.env.PRIVATE_KEY as `0x${string}`,
423
539
  })
424
540