@brninpay/sdk 0.1.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 (95) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/LICENSE +203 -0
  3. package/README.md +5 -0
  4. package/dist/actor-client.d.ts +13 -0
  5. package/dist/actor-client.d.ts.map +1 -0
  6. package/dist/actor-client.js +44 -0
  7. package/dist/actor-client.js.map +1 -0
  8. package/dist/bundler/client.d.ts +20 -0
  9. package/dist/bundler/client.d.ts.map +1 -0
  10. package/dist/bundler/client.js +228 -0
  11. package/dist/bundler/client.js.map +1 -0
  12. package/dist/bundler/providers/alchemy.d.ts +3 -0
  13. package/dist/bundler/providers/alchemy.d.ts.map +1 -0
  14. package/dist/bundler/providers/alchemy.js +15 -0
  15. package/dist/bundler/providers/alchemy.js.map +1 -0
  16. package/dist/bundler/providers/pimlico.d.ts +3 -0
  17. package/dist/bundler/providers/pimlico.d.ts.map +1 -0
  18. package/dist/bundler/providers/pimlico.js +12 -0
  19. package/dist/bundler/providers/pimlico.js.map +1 -0
  20. package/dist/client.d.ts +24 -0
  21. package/dist/client.d.ts.map +1 -0
  22. package/dist/client.js +352 -0
  23. package/dist/client.js.map +1 -0
  24. package/dist/conversion.d.ts +3 -0
  25. package/dist/conversion.d.ts.map +1 -0
  26. package/dist/conversion.js +27 -0
  27. package/dist/conversion.js.map +1 -0
  28. package/dist/erc4337/builder.d.ts +15 -0
  29. package/dist/erc4337/builder.d.ts.map +1 -0
  30. package/dist/erc4337/builder.js +144 -0
  31. package/dist/erc4337/builder.js.map +1 -0
  32. package/dist/erc4337/providers/kernel.d.ts +23 -0
  33. package/dist/erc4337/providers/kernel.d.ts.map +1 -0
  34. package/dist/erc4337/providers/kernel.js +120 -0
  35. package/dist/erc4337/providers/kernel.js.map +1 -0
  36. package/dist/erc4337/providers/safe.d.ts +23 -0
  37. package/dist/erc4337/providers/safe.d.ts.map +1 -0
  38. package/dist/erc4337/providers/safe.js +117 -0
  39. package/dist/erc4337/providers/safe.js.map +1 -0
  40. package/dist/erc4337/providers/types.d.ts +88 -0
  41. package/dist/erc4337/providers/types.d.ts.map +1 -0
  42. package/dist/erc4337/providers/types.js +35 -0
  43. package/dist/erc4337/providers/types.js.map +1 -0
  44. package/dist/erc4337/smart-wallet.d.ts +16 -0
  45. package/dist/erc4337/smart-wallet.d.ts.map +1 -0
  46. package/dist/erc4337/smart-wallet.js +86 -0
  47. package/dist/erc4337/smart-wallet.js.map +1 -0
  48. package/dist/index.d.ts +16 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +15 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/modules/payment-intents.d.ts +99 -0
  53. package/dist/modules/payment-intents.d.ts.map +1 -0
  54. package/dist/modules/payment-intents.js +118 -0
  55. package/dist/modules/payment-intents.js.map +1 -0
  56. package/dist/modules/policies.d.ts +87 -0
  57. package/dist/modules/policies.d.ts.map +1 -0
  58. package/dist/modules/policies.js +61 -0
  59. package/dist/modules/policies.js.map +1 -0
  60. package/dist/paymaster/client.d.ts +11 -0
  61. package/dist/paymaster/client.d.ts.map +1 -0
  62. package/dist/paymaster/client.js +119 -0
  63. package/dist/paymaster/client.js.map +1 -0
  64. package/dist/runtimee.d.ts +4 -0
  65. package/dist/runtimee.d.ts.map +1 -0
  66. package/dist/runtimee.js +4 -0
  67. package/dist/runtimee.js.map +1 -0
  68. package/dist/types.d.ts +389 -0
  69. package/dist/types.d.ts.map +1 -0
  70. package/dist/types.js +2 -0
  71. package/dist/types.js.map +1 -0
  72. package/package.json +29 -0
  73. package/src/actor-client.ts +57 -0
  74. package/src/bundler/client.ts +306 -0
  75. package/src/bundler/providers/alchemy.ts +21 -0
  76. package/src/bundler/providers/pimlico.ts +17 -0
  77. package/src/client.ts +472 -0
  78. package/src/conversion.ts +25 -0
  79. package/src/erc4337/builder.ts +181 -0
  80. package/src/erc4337/providers/kernel.ts +136 -0
  81. package/src/erc4337/providers/safe.ts +133 -0
  82. package/src/erc4337/providers/types.ts +73 -0
  83. package/src/erc4337/smart-wallet.ts +115 -0
  84. package/src/index.ts +60 -0
  85. package/src/modules/payment-intents.ts +156 -0
  86. package/src/modules/policies.ts +79 -0
  87. package/src/paymaster/client.ts +155 -0
  88. package/src/runtimee.ts +3 -0
  89. package/src/types.ts +469 -0
  90. package/test/architecture/architecture-invariants.test.ts +61 -0
  91. package/test/client.test.ts +180 -0
  92. package/test/conversion.test.ts +42 -0
  93. package/test/runtimee.test.ts +30 -0
  94. package/tsconfig.json +9 -0
  95. package/vitest.config.ts +8 -0
@@ -0,0 +1,136 @@
1
+ import { ValidationError } from '@brninpay/core'
2
+ import { encodeFunctionData } from 'viem'
3
+ import { getUserOperationHash } from 'viem/account-abstraction'
4
+ import type { ProviderBaseConfig, SmartAccountProvider } from './types.js'
5
+ import { ACCOUNT_FACTORY_ABI, ENTRY_POINT_ABI } from './types.js'
6
+
7
+ const KERNEL_ACCOUNT_ABI = [
8
+ {
9
+ type: 'function',
10
+ name: 'execute',
11
+ stateMutability: 'nonpayable',
12
+ inputs: [
13
+ { name: 'to', type: 'address' },
14
+ { name: 'value', type: 'uint256' },
15
+ { name: 'data', type: 'bytes' },
16
+ { name: 'operation', type: 'uint8' },
17
+ ],
18
+ outputs: [],
19
+ },
20
+ {
21
+ type: 'function',
22
+ name: 'executeBatch',
23
+ stateMutability: 'nonpayable',
24
+ inputs: [
25
+ {
26
+ name: 'calls',
27
+ type: 'tuple[]',
28
+ components: [
29
+ { name: 'to', type: 'address' },
30
+ { name: 'value', type: 'uint256' },
31
+ { name: 'data', type: 'bytes' },
32
+ ],
33
+ },
34
+ ],
35
+ outputs: [],
36
+ },
37
+ ] as const
38
+
39
+ export class KernelAccountProvider implements SmartAccountProvider {
40
+ readonly kind = 'kernel' as const
41
+ readonly saltNonce: bigint
42
+
43
+ constructor(private readonly config: ProviderBaseConfig) {
44
+ this.saltNonce = config.saltNonce ?? 0n
45
+ }
46
+
47
+ get owner() {
48
+ return this.config.owner
49
+ }
50
+
51
+ get chainId() {
52
+ return this.config.chainId
53
+ }
54
+
55
+ get entryPointAddress() {
56
+ return this.config.entryPointAddress
57
+ }
58
+
59
+ get entryPointVersion() {
60
+ return this.config.entryPointVersion
61
+ }
62
+
63
+ get factoryAddress() {
64
+ return this.config.factoryAddress
65
+ }
66
+
67
+ async getCounterfactualAddress() {
68
+ return this.config.publicClient.readContract<`0x${string}`>({
69
+ address: this.config.factoryAddress,
70
+ abi: ACCOUNT_FACTORY_ABI,
71
+ functionName: 'getAddress',
72
+ args: [this.config.owner.address, this.saltNonce],
73
+ })
74
+ }
75
+
76
+ async getFactoryArgs() {
77
+ return {
78
+ factory: this.config.factoryAddress,
79
+ factoryData: encodeFunctionData({
80
+ abi: ACCOUNT_FACTORY_ABI,
81
+ functionName: 'createAccount',
82
+ args: [this.config.owner.address, this.saltNonce],
83
+ }),
84
+ }
85
+ }
86
+
87
+ async encodeCalls(calls: Parameters<SmartAccountProvider['encodeCalls']>[0]) {
88
+ if (calls.length === 0) {
89
+ throw new ValidationError('KernelAccountProvider requires at least one call')
90
+ }
91
+
92
+ if (calls.length === 1) {
93
+ const [call] = calls
94
+ return encodeFunctionData({
95
+ abi: KERNEL_ACCOUNT_ABI,
96
+ functionName: 'execute',
97
+ args: [call.to, call.value, call.data, 0],
98
+ })
99
+ }
100
+
101
+ return encodeFunctionData({
102
+ abi: KERNEL_ACCOUNT_ABI,
103
+ functionName: 'executeBatch',
104
+ args: [calls.map((call) => ({ to: call.to, value: call.value, data: call.data }))],
105
+ })
106
+ }
107
+
108
+ async getNonce(address: `0x${string}`, key = 0n) {
109
+ return this.config.publicClient.readContract<bigint>({
110
+ address: this.config.entryPointAddress,
111
+ abi: ENTRY_POINT_ABI,
112
+ functionName: 'getNonce',
113
+ args: [address, key],
114
+ })
115
+ }
116
+
117
+ async getStubSignature() {
118
+ return `0x${'00'.repeat(65)}` as `0x${string}`
119
+ }
120
+
121
+ async signUserOperation(userOperation: Parameters<SmartAccountProvider['signUserOperation']>[0]) {
122
+ const hash = getUserOperationHash({
123
+ chainId: this.config.chainId,
124
+ entryPointAddress: this.config.entryPointAddress,
125
+ entryPointVersion: this.config.entryPointVersion === 'v0.6' ? '0.6' : '0.7',
126
+ userOperation: userOperation as never,
127
+ })
128
+
129
+ return this.config.owner.signMessage({ message: { raw: hash } })
130
+ }
131
+
132
+ async isDeployed(address: `0x${string}`) {
133
+ const code = await this.config.publicClient.getCode({ address })
134
+ return Boolean(code && code !== '0x')
135
+ }
136
+ }
@@ -0,0 +1,133 @@
1
+ import { ValidationError } from '@brninpay/core'
2
+ import { encodeFunctionData } from 'viem'
3
+ import { getUserOperationHash } from 'viem/account-abstraction'
4
+ import type { SmartAccountProvider, ProviderBaseConfig } from './types.js'
5
+ import { ACCOUNT_FACTORY_ABI, ENTRY_POINT_ABI } from './types.js'
6
+
7
+ const SAFE_ACCOUNT_ABI = [
8
+ {
9
+ type: 'function',
10
+ name: 'execute',
11
+ stateMutability: 'nonpayable',
12
+ inputs: [
13
+ { name: 'to', type: 'address' },
14
+ { name: 'value', type: 'uint256' },
15
+ { name: 'data', type: 'bytes' },
16
+ ],
17
+ outputs: [],
18
+ },
19
+ {
20
+ type: 'function',
21
+ name: 'executeBatch',
22
+ stateMutability: 'nonpayable',
23
+ inputs: [
24
+ { name: 'to', type: 'address[]' },
25
+ { name: 'value', type: 'uint256[]' },
26
+ { name: 'data', type: 'bytes[]' },
27
+ ],
28
+ outputs: [],
29
+ },
30
+ ] as const
31
+
32
+ export class SafeAccountProvider implements SmartAccountProvider {
33
+ readonly kind = 'safe' as const
34
+ readonly saltNonce: bigint
35
+
36
+ constructor(private readonly config: ProviderBaseConfig) {
37
+ this.saltNonce = config.saltNonce ?? 0n
38
+ }
39
+
40
+ get owner() {
41
+ return this.config.owner
42
+ }
43
+
44
+ get chainId() {
45
+ return this.config.chainId
46
+ }
47
+
48
+ get entryPointAddress() {
49
+ return this.config.entryPointAddress
50
+ }
51
+
52
+ get entryPointVersion() {
53
+ return this.config.entryPointVersion
54
+ }
55
+
56
+ get factoryAddress() {
57
+ return this.config.factoryAddress
58
+ }
59
+
60
+ async getCounterfactualAddress() {
61
+ return this.config.publicClient.readContract<`0x${string}`>({
62
+ address: this.config.factoryAddress,
63
+ abi: ACCOUNT_FACTORY_ABI,
64
+ functionName: 'getAddress',
65
+ args: [this.config.owner.address, this.saltNonce],
66
+ })
67
+ }
68
+
69
+ async getFactoryArgs() {
70
+ return {
71
+ factory: this.config.factoryAddress,
72
+ factoryData: encodeFunctionData({
73
+ abi: ACCOUNT_FACTORY_ABI,
74
+ functionName: 'createAccount',
75
+ args: [this.config.owner.address, this.saltNonce],
76
+ }),
77
+ }
78
+ }
79
+
80
+ async encodeCalls(calls: Parameters<SmartAccountProvider['encodeCalls']>[0]) {
81
+ if (calls.length === 0) {
82
+ throw new ValidationError('SafeAccountProvider requires at least one call')
83
+ }
84
+
85
+ if (calls.length === 1) {
86
+ const [call] = calls
87
+ return encodeFunctionData({
88
+ abi: SAFE_ACCOUNT_ABI,
89
+ functionName: 'execute',
90
+ args: [call.to, call.value, call.data],
91
+ })
92
+ }
93
+
94
+ return encodeFunctionData({
95
+ abi: SAFE_ACCOUNT_ABI,
96
+ functionName: 'executeBatch',
97
+ args: [
98
+ calls.map((call) => call.to),
99
+ calls.map((call) => call.value),
100
+ calls.map((call) => call.data),
101
+ ],
102
+ })
103
+ }
104
+
105
+ async getNonce(address: `0x${string}`, key = 0n) {
106
+ return this.config.publicClient.readContract<bigint>({
107
+ address: this.config.entryPointAddress,
108
+ abi: ENTRY_POINT_ABI,
109
+ functionName: 'getNonce',
110
+ args: [address, key],
111
+ })
112
+ }
113
+
114
+ async getStubSignature() {
115
+ return `0x${'00'.repeat(65)}` as `0x${string}`
116
+ }
117
+
118
+ async signUserOperation(userOperation: Parameters<SmartAccountProvider['signUserOperation']>[0]) {
119
+ const hash = getUserOperationHash({
120
+ chainId: this.config.chainId,
121
+ entryPointAddress: this.config.entryPointAddress,
122
+ entryPointVersion: this.config.entryPointVersion === 'v0.6' ? '0.6' : '0.7',
123
+ userOperation: userOperation as never,
124
+ })
125
+
126
+ return this.config.owner.signMessage({ message: { raw: hash } })
127
+ }
128
+
129
+ async isDeployed(address: `0x${string}`) {
130
+ const code = await this.config.publicClient.getCode({ address })
131
+ return Boolean(code && code !== '0x')
132
+ }
133
+ }
@@ -0,0 +1,73 @@
1
+ import type { Address, EntryPointVersion, Hex, UserOperation, UserOperationCall } from '@brninpay/core'
2
+ import type { SmartAccountOwner } from '../../types.js'
3
+
4
+ export const ENTRY_POINT_ABI = [
5
+ {
6
+ type: 'function',
7
+ name: 'getNonce',
8
+ stateMutability: 'view',
9
+ inputs: [
10
+ { name: 'sender', type: 'address' },
11
+ { name: 'key', type: 'uint192' },
12
+ ],
13
+ outputs: [{ name: 'nonce', type: 'uint256' }],
14
+ },
15
+ ] as const
16
+
17
+ export const ACCOUNT_FACTORY_ABI = [
18
+ {
19
+ type: 'function',
20
+ name: 'createAccount',
21
+ stateMutability: 'nonpayable',
22
+ inputs: [
23
+ { name: 'owner', type: 'address' },
24
+ { name: 'saltNonce', type: 'uint256' },
25
+ ],
26
+ outputs: [{ name: 'account', type: 'address' }],
27
+ },
28
+ {
29
+ type: 'function',
30
+ name: 'getAddress',
31
+ stateMutability: 'view',
32
+ inputs: [
33
+ { name: 'owner', type: 'address' },
34
+ { name: 'saltNonce', type: 'uint256' },
35
+ ],
36
+ outputs: [{ name: 'account', type: 'address' }],
37
+ },
38
+ ] as const
39
+
40
+ export interface ProviderBaseConfig {
41
+ publicClient: {
42
+ readContract<T = unknown>(parameters: {
43
+ address: Address
44
+ abi: readonly unknown[]
45
+ functionName: string
46
+ args?: readonly unknown[]
47
+ }): Promise<T>
48
+ getCode(parameters: { address: Address }): Promise<Hex | undefined>
49
+ }
50
+ owner: SmartAccountOwner
51
+ entryPointAddress: Address
52
+ entryPointVersion: EntryPointVersion
53
+ factoryAddress: Address
54
+ chainId: number
55
+ saltNonce?: bigint
56
+ }
57
+
58
+ export interface SmartAccountProvider {
59
+ readonly kind: 'safe' | 'kernel' | 'circle'
60
+ readonly owner: SmartAccountOwner
61
+ readonly chainId: number
62
+ readonly entryPointAddress: Address
63
+ readonly entryPointVersion: EntryPointVersion
64
+ readonly factoryAddress: Address
65
+ readonly saltNonce: bigint
66
+ getCounterfactualAddress(): Promise<Address>
67
+ getFactoryArgs(): Promise<{ factory?: Address; factoryData?: Hex }>
68
+ encodeCalls(calls: readonly UserOperationCall[]): Promise<Hex>
69
+ getNonce(address: Address, key?: bigint): Promise<bigint>
70
+ getStubSignature(): Promise<Hex>
71
+ signUserOperation(userOperation: UserOperation): Promise<Hex>
72
+ isDeployed(address: Address): Promise<boolean>
73
+ }
@@ -0,0 +1,115 @@
1
+ import type { SmartWallet as CoreSmartWallet, UserOperationCall } from '@brninpay/core'
2
+ import { UserOperationBuilder } from './builder.js'
3
+ import type {
4
+ BatchOperationOptions,
5
+ SmartWalletConfig,
6
+ SmartWalletSendResult,
7
+ } from '../types.js'
8
+
9
+ export class SmartWallet {
10
+ private wallet?: CoreSmartWallet
11
+
12
+ constructor(private readonly config: SmartWalletConfig) {}
13
+
14
+ async createWallet(): Promise<CoreSmartWallet> {
15
+ const address = await this.config.provider.getCounterfactualAddress()
16
+ const deployed = await this.config.provider.isDeployed(address)
17
+
18
+ this.wallet = {
19
+ address,
20
+ ownerAddress: this.config.provider.owner.address,
21
+ chainId: this.config.chainId,
22
+ provider: this.config.provider.kind,
23
+ entryPointAddress: this.config.provider.entryPointAddress,
24
+ entryPointVersion: this.config.provider.entryPointVersion,
25
+ factoryAddress: this.config.provider.factoryAddress,
26
+ deployed,
27
+ }
28
+
29
+ return this.wallet
30
+ }
31
+
32
+ async sendUSDC(
33
+ parameters: { recipient: `0x${string}`; amount: bigint },
34
+ options?: BatchOperationOptions
35
+ ): Promise<SmartWalletSendResult> {
36
+ const call = this.config.builder.buildUSDCTransfer(parameters)
37
+ return this.batchOperations([call], options)
38
+ }
39
+
40
+ async batchOperations(
41
+ calls: readonly UserOperationCall[],
42
+ options: BatchOperationOptions = {}
43
+ ): Promise<SmartWalletSendResult> {
44
+ const wallet = this.wallet ?? (await this.createWallet())
45
+ const builder = this.config.builder instanceof UserOperationBuilder ? this.config.builder : this.config.builder
46
+
47
+ let userOperation = await builder.prepareUserOperation(this.config.provider, {
48
+ walletAddress: wallet.address,
49
+ walletDeployed: wallet.deployed,
50
+ calls,
51
+ })
52
+
53
+ if (this.config.paymasterClient) {
54
+ const sponsorship = await this.config.paymasterClient.requestSponsorship(
55
+ userOperation,
56
+ options.sponsorshipContext
57
+ )
58
+
59
+ userOperation = await builder.prepareUserOperation(this.config.provider, {
60
+ walletAddress: wallet.address,
61
+ walletDeployed: wallet.deployed,
62
+ calls,
63
+ sponsorship,
64
+ })
65
+ }
66
+
67
+ const signature = await this.config.provider.signUserOperation(userOperation)
68
+ const signedUserOperation = {
69
+ ...userOperation,
70
+ signature,
71
+ }
72
+
73
+ const userOperationHash = await this.config.bundlerClient.sendUserOperation(signedUserOperation)
74
+
75
+ if (!options.waitForReceipt) {
76
+ return {
77
+ wallet,
78
+ userOperation: signedUserOperation,
79
+ userOperationHash,
80
+ }
81
+ }
82
+
83
+ const receipt = await this.config.bundlerClient.getUserOperationReceipt(userOperationHash, {
84
+ pollingIntervalMs: options.pollingIntervalMs,
85
+ timeoutMs: options.timeoutMs,
86
+ })
87
+
88
+ this.wallet = {
89
+ ...wallet,
90
+ deployed: true,
91
+ }
92
+
93
+ return {
94
+ wallet: this.wallet,
95
+ userOperation: signedUserOperation,
96
+ userOperationHash,
97
+ receipt,
98
+ }
99
+ }
100
+
101
+ async isDeployed(): Promise<boolean> {
102
+ const wallet = this.wallet ?? (await this.createWallet())
103
+ const deployed = await this.config.provider.isDeployed(wallet.address)
104
+ this.wallet = {
105
+ ...wallet,
106
+ deployed,
107
+ }
108
+ return deployed
109
+ }
110
+
111
+ async getNonce(key = 0n): Promise<bigint> {
112
+ const wallet = this.wallet ?? (await this.createWallet())
113
+ return this.config.provider.getNonce(wallet.address, key)
114
+ }
115
+ }
package/src/index.ts ADDED
@@ -0,0 +1,60 @@
1
+ export { BrninpayClient } from './client.js'
2
+ export { Runtimee } from './runtimee.js'
3
+ export { ActorClient } from './actor-client.js'
4
+ export { PaymentIntentClient } from './modules/payment-intents.js'
5
+ export { PolicyClient } from './modules/policies.js'
6
+ export { BundlerClient } from './bundler/client.js'
7
+ export { createPimlicoBundlerProvider } from './bundler/providers/pimlico.js'
8
+ export { createAlchemyBundlerProvider } from './bundler/providers/alchemy.js'
9
+ export { PaymasterClient } from './paymaster/client.js'
10
+ export { UserOperationBuilder } from './erc4337/builder.js'
11
+ export { SmartWallet } from './erc4337/smart-wallet.js'
12
+ export { SafeAccountProvider } from './erc4337/providers/safe.js'
13
+ export { KernelAccountProvider } from './erc4337/providers/kernel.js'
14
+ export { parseUsdc, formatUsdc } from './conversion.js'
15
+ export type {
16
+ Address,
17
+ ApiErrorPayload,
18
+ BatchOperationOptions,
19
+ BundlerClientConfig,
20
+ BundlerProviderConfig,
21
+ CancelPaymentIntentParams,
22
+ ClientRequestOptions,
23
+ ClientTransport,
24
+ CreateActorParams,
25
+ CreatePaymentIntentParams,
26
+ CreatePolicyParams,
27
+ PayParams,
28
+ PaginatedResult,
29
+ PaymentIntentPreview,
30
+ PaymentIntentPreviewParams,
31
+ PimlicoBundlerProviderParams,
32
+ PreviewPayParams,
33
+ PublicClientLike,
34
+ RequestContext,
35
+ ResponseContext,
36
+ RetryPolicy,
37
+ SmartAccountOwner,
38
+ SmartWalletConfig,
39
+ SmartWalletInstance,
40
+ SmartWalletSendResult,
41
+ SponsorshipContext,
42
+ SponsorshipData,
43
+ ActorStatus,
44
+ ActorSummary,
45
+ ExecutionReceipt,
46
+ AlchemyBundlerProviderParams,
47
+ GetPaymentIntentOptions,
48
+ ListPaymentIntentsParams,
49
+ ListPoliciesParams,
50
+ PaymasterClientConfig,
51
+ PolicyDecisionExplanation,
52
+ PreparedUserOperationContext,
53
+ SmartWalletAccountProvider,
54
+ SdkLogger,
55
+ UpdatePolicyParams,
56
+ UserOperationBuilderConfig,
57
+ UserOperationByHash,
58
+ UserOperationReceipt,
59
+ RuntimeeConfig,
60
+ } from './types.js'
@@ -0,0 +1,156 @@
1
+ import {
2
+ deserializeBigInts,
3
+ parsePaymentIntent,
4
+ ValidationError,
5
+ } from '@brninpay/core'
6
+ import type {
7
+ CancelPaymentIntentParams,
8
+ ClientTransport,
9
+ CreatePaymentIntentParams,
10
+ GetPaymentIntentOptions,
11
+ ListPaymentIntentsParams,
12
+ PaginatedResult,
13
+ PaymentIntentPreview,
14
+ PaymentIntentPreviewParams,
15
+ } from '../types.js'
16
+
17
+ function sleep(ms: number): Promise<void> {
18
+ return new Promise((resolve) => setTimeout(resolve, ms))
19
+ }
20
+
21
+ function normalizeAmount(amount: CreatePaymentIntentParams['amount']) {
22
+ return {
23
+ currency: amount.currency,
24
+ value: BigInt(amount.value).toString(),
25
+ decimals: amount.decimals ?? 6,
26
+ }
27
+ }
28
+
29
+ export class PaymentIntentClient {
30
+ constructor(private readonly transport: ClientTransport) {}
31
+
32
+ async createPaymentIntent(params: CreatePaymentIntentParams) {
33
+ if (!params.idempotencyKey?.trim()) {
34
+ throw new ValidationError('createPaymentIntent requires a non-empty idempotencyKey', {
35
+ field: 'idempotencyKey',
36
+ })
37
+ }
38
+
39
+ const response = await this.transport.request<unknown>({
40
+ path: '/v1/payment-intents',
41
+ method: 'POST',
42
+ headers: {
43
+ 'Idempotency-Key': params.idempotencyKey,
44
+ },
45
+ body: {
46
+ ...params,
47
+ amount: normalizeAmount(params.amount),
48
+ },
49
+ })
50
+
51
+ return parsePaymentIntent(deserializeBigInts(response))
52
+ }
53
+
54
+ async getPaymentIntent(id: string, options: GetPaymentIntentOptions = {}) {
55
+ if (!id.trim()) {
56
+ throw new ValidationError('getPaymentIntent requires a payment intent id')
57
+ }
58
+
59
+ const loadOnce = async () => {
60
+ const response = await this.transport.request<unknown>({
61
+ path: `/v1/payment-intents/${encodeURIComponent(id)}`,
62
+ method: 'GET',
63
+ })
64
+
65
+ return parsePaymentIntent(deserializeBigInts(response))
66
+ }
67
+
68
+ if (!options.waitForTerminalState) {
69
+ return loadOnce()
70
+ }
71
+
72
+ const startedAt = Date.now()
73
+ const pollIntervalMs = options.pollIntervalMs ?? 2_000
74
+ const timeoutMs = options.timeoutMs ?? 60_000
75
+
76
+ while (true) {
77
+ const paymentIntent = await loadOnce()
78
+ if (['rejected', 'succeeded', 'failed', 'cancelled', 'expired'].includes(paymentIntent.status)) {
79
+ return paymentIntent
80
+ }
81
+
82
+ if (Date.now() - startedAt >= timeoutMs) {
83
+ throw new ValidationError('Timed out while waiting for payment intent to reach a terminal state', {
84
+ id,
85
+ timeoutMs,
86
+ currentStatus: paymentIntent.status,
87
+ })
88
+ }
89
+
90
+ await sleep(pollIntervalMs)
91
+ }
92
+ }
93
+
94
+ async cancelPaymentIntent(id: string, params: CancelPaymentIntentParams = {}) {
95
+ const current = await this.getPaymentIntent(id)
96
+ if (['submitted', 'confirming', 'succeeded', 'failed'].includes(current.status)) {
97
+ throw new ValidationError(`Payment intent ${id} can no longer be cancelled`, {
98
+ id,
99
+ status: current.status,
100
+ })
101
+ }
102
+
103
+ const response = await this.transport.request<unknown>({
104
+ path: `/v1/payment-intents/${encodeURIComponent(id)}/cancel`,
105
+ method: 'POST',
106
+ body: params,
107
+ })
108
+
109
+ return parsePaymentIntent(deserializeBigInts(response))
110
+ }
111
+
112
+ async previewPayment(params: PaymentIntentPreviewParams): Promise<PaymentIntentPreview> {
113
+ const response = await this.transport.request<PaymentIntentPreview>({
114
+ path: '/v1/payment-intents/preview',
115
+ method: 'POST',
116
+ body: {
117
+ ...params,
118
+ amount: normalizeAmount(params.amount),
119
+ },
120
+ })
121
+
122
+ return {
123
+ ...response,
124
+ estimatedGas:
125
+ response.estimatedGas !== undefined ? BigInt(response.estimatedGas) : undefined,
126
+ estimatedTotalCost:
127
+ response.estimatedTotalCost !== undefined
128
+ ? BigInt(response.estimatedTotalCost)
129
+ : undefined,
130
+ }
131
+ }
132
+
133
+ async listPaymentIntents(
134
+ params: ListPaymentIntentsParams = {}
135
+ ): Promise<PaginatedResult<ReturnType<typeof parsePaymentIntent>>> {
136
+ const response = await this.transport.request<{
137
+ items: unknown[]
138
+ nextCursor?: string | null
139
+ }>({
140
+ path: '/v1/payment-intents',
141
+ method: 'GET',
142
+ query: {
143
+ cursor: params.cursor,
144
+ limit: params.limit,
145
+ workspaceId: params.workspaceId,
146
+ actorId: params.actorId,
147
+ status: params.status,
148
+ },
149
+ })
150
+
151
+ return {
152
+ items: response.items.map((item) => parsePaymentIntent(deserializeBigInts(item))),
153
+ nextCursor: response.nextCursor ?? null,
154
+ }
155
+ }
156
+ }