@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.
- package/.turbo/turbo-build.log +4 -0
- package/LICENSE +203 -0
- package/README.md +5 -0
- package/dist/actor-client.d.ts +13 -0
- package/dist/actor-client.d.ts.map +1 -0
- package/dist/actor-client.js +44 -0
- package/dist/actor-client.js.map +1 -0
- package/dist/bundler/client.d.ts +20 -0
- package/dist/bundler/client.d.ts.map +1 -0
- package/dist/bundler/client.js +228 -0
- package/dist/bundler/client.js.map +1 -0
- package/dist/bundler/providers/alchemy.d.ts +3 -0
- package/dist/bundler/providers/alchemy.d.ts.map +1 -0
- package/dist/bundler/providers/alchemy.js +15 -0
- package/dist/bundler/providers/alchemy.js.map +1 -0
- package/dist/bundler/providers/pimlico.d.ts +3 -0
- package/dist/bundler/providers/pimlico.d.ts.map +1 -0
- package/dist/bundler/providers/pimlico.js +12 -0
- package/dist/bundler/providers/pimlico.js.map +1 -0
- package/dist/client.d.ts +24 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +352 -0
- package/dist/client.js.map +1 -0
- package/dist/conversion.d.ts +3 -0
- package/dist/conversion.d.ts.map +1 -0
- package/dist/conversion.js +27 -0
- package/dist/conversion.js.map +1 -0
- package/dist/erc4337/builder.d.ts +15 -0
- package/dist/erc4337/builder.d.ts.map +1 -0
- package/dist/erc4337/builder.js +144 -0
- package/dist/erc4337/builder.js.map +1 -0
- package/dist/erc4337/providers/kernel.d.ts +23 -0
- package/dist/erc4337/providers/kernel.d.ts.map +1 -0
- package/dist/erc4337/providers/kernel.js +120 -0
- package/dist/erc4337/providers/kernel.js.map +1 -0
- package/dist/erc4337/providers/safe.d.ts +23 -0
- package/dist/erc4337/providers/safe.d.ts.map +1 -0
- package/dist/erc4337/providers/safe.js +117 -0
- package/dist/erc4337/providers/safe.js.map +1 -0
- package/dist/erc4337/providers/types.d.ts +88 -0
- package/dist/erc4337/providers/types.d.ts.map +1 -0
- package/dist/erc4337/providers/types.js +35 -0
- package/dist/erc4337/providers/types.js.map +1 -0
- package/dist/erc4337/smart-wallet.d.ts +16 -0
- package/dist/erc4337/smart-wallet.d.ts.map +1 -0
- package/dist/erc4337/smart-wallet.js +86 -0
- package/dist/erc4337/smart-wallet.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/payment-intents.d.ts +99 -0
- package/dist/modules/payment-intents.d.ts.map +1 -0
- package/dist/modules/payment-intents.js +118 -0
- package/dist/modules/payment-intents.js.map +1 -0
- package/dist/modules/policies.d.ts +87 -0
- package/dist/modules/policies.d.ts.map +1 -0
- package/dist/modules/policies.js +61 -0
- package/dist/modules/policies.js.map +1 -0
- package/dist/paymaster/client.d.ts +11 -0
- package/dist/paymaster/client.d.ts.map +1 -0
- package/dist/paymaster/client.js +119 -0
- package/dist/paymaster/client.js.map +1 -0
- package/dist/runtimee.d.ts +4 -0
- package/dist/runtimee.d.ts.map +1 -0
- package/dist/runtimee.js +4 -0
- package/dist/runtimee.js.map +1 -0
- package/dist/types.d.ts +389 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +29 -0
- package/src/actor-client.ts +57 -0
- package/src/bundler/client.ts +306 -0
- package/src/bundler/providers/alchemy.ts +21 -0
- package/src/bundler/providers/pimlico.ts +17 -0
- package/src/client.ts +472 -0
- package/src/conversion.ts +25 -0
- package/src/erc4337/builder.ts +181 -0
- package/src/erc4337/providers/kernel.ts +136 -0
- package/src/erc4337/providers/safe.ts +133 -0
- package/src/erc4337/providers/types.ts +73 -0
- package/src/erc4337/smart-wallet.ts +115 -0
- package/src/index.ts +60 -0
- package/src/modules/payment-intents.ts +156 -0
- package/src/modules/policies.ts +79 -0
- package/src/paymaster/client.ts +155 -0
- package/src/runtimee.ts +3 -0
- package/src/types.ts +469 -0
- package/test/architecture/architecture-invariants.test.ts +61 -0
- package/test/client.test.ts +180 -0
- package/test/conversion.test.ts +42 -0
- package/test/runtimee.test.ts +30 -0
- package/tsconfig.json +9 -0
- 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
|
+
}
|