PHP WebShell
Текущая директория: /usr/lib/node_modules/bitgo/node_modules/viem/account-abstraction/accounts/implementations
Просмотр файла: toCoinbaseSmartAccount.ts
import type { Address, TypedData } from 'abitype'
import * as Signature from 'ox/Signature'
import type * as WebAuthnP256 from 'ox/WebAuthnP256'
import type { LocalAccount } from '../../../accounts/types.js'
import { readContract } from '../../../actions/public/readContract.js'
import { entryPoint06Address } from '../../../constants/address.js'
import { BaseError } from '../../../errors/base.js'
import type { Hash, Hex } from '../../../types/misc.js'
import type { TypedDataDefinition } from '../../../types/typedData.js'
import type { Assign, OneOf, Prettify } from '../../../types/utils.js'
import { decodeFunctionData } from '../../../utils/abi/decodeFunctionData.js'
import { encodeAbiParameters } from '../../../utils/abi/encodeAbiParameters.js'
import { encodeFunctionData } from '../../../utils/abi/encodeFunctionData.js'
import { encodePacked } from '../../../utils/abi/encodePacked.js'
import { pad } from '../../../utils/data/pad.js'
import { size } from '../../../utils/data/size.js'
import { stringToHex } from '../../../utils/encoding/toHex.js'
import { hashMessage } from '../../../utils/signature/hashMessage.js'
import { hashTypedData } from '../../../utils/signature/hashTypedData.js'
import { parseSignature } from '../../../utils/signature/parseSignature.js'
import { entryPoint06Abi } from '../../constants/abis.js'
import type { UserOperation } from '../../types/userOperation.js'
import { getUserOperationHash } from '../../utils/userOperation/getUserOperationHash.js'
import { toSmartAccount } from '../toSmartAccount.js'
import type {
SmartAccount,
SmartAccountImplementation,
WebAuthnAccount,
} from '../types.js'
export type ToCoinbaseSmartAccountParameters = {
address?: Address | undefined
client: CoinbaseSmartAccountImplementation['client']
ownerIndex?: number | undefined
owners: readonly (Address | OneOf<LocalAccount | WebAuthnAccount>)[]
nonce?: bigint | undefined
version: '1.1' | '1'
}
export type ToCoinbaseSmartAccountReturnType = Prettify<
SmartAccount<CoinbaseSmartAccountImplementation>
>
export type CoinbaseSmartAccountImplementation = Assign<
SmartAccountImplementation<
typeof entryPoint06Abi,
'0.6',
{ abi: typeof abi; factory: { abi: typeof factoryAbi; address: Address } }
>,
{
decodeCalls: NonNullable<SmartAccountImplementation['decodeCalls']>
sign: NonNullable<SmartAccountImplementation['sign']>
}
>
const factoryAddress = {
'1.1': '0xba5ed110efdba3d005bfc882d75358acbbb85842',
'1': '0x0ba5ed0c6aa8c49038f819e587e2633c4a9f428a',
} as const
/**
* @description Create a Coinbase Smart Account.
*
* @param parameters - {@link ToCoinbaseSmartAccountParameters}
* @returns Coinbase Smart Account. {@link ToCoinbaseSmartAccountReturnType}
*
* @example
* import { toCoinbaseSmartAccount } from 'viem/account-abstraction'
* import { privateKeyToAccount } from 'viem/accounts'
* import { client } from './client.js'
*
* const account = toCoinbaseSmartAccount({
* client,
* owners: [privateKeyToAccount('0x...')],
* version: '1.1',
* })
*/
export async function toCoinbaseSmartAccount(
parameters: ToCoinbaseSmartAccountParameters,
): Promise<ToCoinbaseSmartAccountReturnType> {
const {
client,
ownerIndex = 0,
owners,
nonce = 0n,
version = '1',
} = parameters
let address = parameters.address
const entryPoint = {
abi: entryPoint06Abi,
address: entryPoint06Address,
version: '0.6',
} as const
const factory = {
abi: factoryAbi,
address: factoryAddress[version],
} as const
const owners_bytes = owners.map((owner) => {
if (typeof owner === 'string') return pad(owner)
if (owner.type === 'webAuthn') return owner.publicKey
if (owner.type === 'local') return pad(owner.address)
throw new BaseError('invalid owner type')
})
const owner = (() => {
const owner = owners[ownerIndex] ?? owners[0]
if (typeof owner === 'string')
return { address: owner, type: 'address' } as const
return owner
})()
return toSmartAccount({
client,
entryPoint,
extend: { abi, factory },
async decodeCalls(data) {
const result = decodeFunctionData({
abi,
data,
})
if (result.functionName === 'execute')
return [
{ to: result.args[0], value: result.args[1], data: result.args[2] },
]
if (result.functionName === 'executeBatch')
return result.args[0].map((arg) => ({
to: arg.target,
value: arg.value,
data: arg.data,
}))
throw new BaseError(`unable to decode calls for "${result.functionName}"`)
},
async encodeCalls(calls) {
if (calls.length === 1)
return encodeFunctionData({
abi,
functionName: 'execute',
args: [calls[0].to, calls[0].value ?? 0n, calls[0].data ?? '0x'],
})
return encodeFunctionData({
abi,
functionName: 'executeBatch',
args: [
calls.map((call) => ({
data: call.data ?? '0x',
target: call.to,
value: call.value ?? 0n,
})),
],
})
},
async getAddress() {
address ??= await readContract(client, {
...factory,
functionName: 'getAddress',
args: [owners_bytes, nonce],
})
return address
},
async getFactoryArgs() {
const factoryData = encodeFunctionData({
abi: factory.abi,
functionName: 'createAccount',
args: [owners_bytes, nonce],
})
return { factory: factory.address, factoryData }
},
async getStubSignature() {
if (owner.type === 'webAuthn')
return '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000001949fc7c88032b9fcb5f6efc7a7b8c63668eae9871b765e23123bb473ff57aa831a7c0d9276168ebcc29f2875a0239cffdf2a9cd1c2007c5c77c071db9264df1d000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008a7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2273496a396e6164474850596759334b7156384f7a4a666c726275504b474f716d59576f4d57516869467773222c226f726967696e223a2268747470733a2f2f7369676e2e636f696e626173652e636f6d222c2263726f73734f726967696e223a66616c73657d00000000000000000000000000000000000000000000'
return wrapSignature({
ownerIndex,
signature:
'0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c',
})
},
async sign(parameters) {
const address = await this.getAddress()
const typedData = toReplaySafeTypedData({
address,
chainId: client.chain!.id,
hash: parameters.hash,
})
if (owner.type === 'address') throw new Error('owner cannot sign')
const signature = await signTypedData({ owner, typedData })
return wrapSignature({
ownerIndex,
signature,
})
},
async signMessage(parameters) {
const { message } = parameters
const address = await this.getAddress()
const typedData = toReplaySafeTypedData({
address,
chainId: client.chain!.id,
hash: hashMessage(message),
})
if (owner.type === 'address') throw new Error('owner cannot sign')
const signature = await signTypedData({ owner, typedData })
return wrapSignature({
ownerIndex,
signature,
})
},
async signTypedData(parameters) {
const { domain, types, primaryType, message } =
parameters as TypedDataDefinition<TypedData, string>
const address = await this.getAddress()
const typedData = toReplaySafeTypedData({
address,
chainId: client.chain!.id,
hash: hashTypedData({
domain,
message,
primaryType,
types,
}),
})
if (owner.type === 'address') throw new Error('owner cannot sign')
const signature = await signTypedData({ owner, typedData })
return wrapSignature({
ownerIndex,
signature,
})
},
async signUserOperation(parameters) {
const { chainId = client.chain!.id, ...userOperation } = parameters
const address = await this.getAddress()
const hash = getUserOperationHash({
chainId,
entryPointAddress: entryPoint.address,
entryPointVersion: entryPoint.version,
userOperation: {
...(userOperation as unknown as UserOperation),
sender: address,
},
})
if (owner.type === 'address') throw new Error('owner cannot sign')
const signature = await sign({ hash, owner })
return wrapSignature({
ownerIndex,
signature,
})
},
userOperation: {
async estimateGas(userOperation) {
if (owner.type !== 'webAuthn') return
// Accounts with WebAuthn owner require a minimum verification gas limit of 800,000.
return {
verificationGasLimit: BigInt(
Math.max(Number(userOperation.verificationGasLimit ?? 0n), 800_000),
),
}
},
},
})
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Utilities
/////////////////////////////////////////////////////////////////////////////////////////////
/** @internal */
export async function signTypedData({
typedData,
owner,
}: {
typedData: TypedDataDefinition
owner: OneOf<LocalAccount | WebAuthnAccount>
}) {
if (owner.type === 'local' && owner.signTypedData)
return owner.signTypedData(typedData)
const hash = hashTypedData(typedData)
return sign({ hash, owner })
}
/** @internal */
export async function sign({
hash,
owner,
}: {
hash: Hash
owner: OneOf<LocalAccount | WebAuthnAccount>
}) {
// WebAuthn Account (Passkey)
if (owner.type === 'webAuthn') {
const { signature, webauthn } = await owner.sign({
hash,
})
return toWebAuthnSignature({ signature, webauthn })
}
if (owner.sign) return owner.sign({ hash })
throw new BaseError('`owner` does not support raw sign.')
}
/** @internal */
export function toReplaySafeTypedData({
address,
chainId,
hash,
}: {
address: Address
chainId: number
hash: Hash
}) {
return {
domain: {
chainId,
name: 'Coinbase Smart Wallet',
verifyingContract: address,
version: '1',
},
types: {
CoinbaseSmartWalletMessage: [
{
name: 'hash',
type: 'bytes32',
},
],
},
primaryType: 'CoinbaseSmartWalletMessage',
message: {
hash,
},
} as const
}
/** @internal */
export function toWebAuthnSignature({
webauthn,
signature,
}: {
webauthn: WebAuthnP256.SignMetadata
signature: Hex
}) {
const { r, s } = Signature.fromHex(signature)
return encodeAbiParameters(
[
{
components: [
{
name: 'authenticatorData',
type: 'bytes',
},
{ name: 'clientDataJSON', type: 'bytes' },
{ name: 'challengeIndex', type: 'uint256' },
{ name: 'typeIndex', type: 'uint256' },
{
name: 'r',
type: 'uint256',
},
{
name: 's',
type: 'uint256',
},
],
type: 'tuple',
},
],
[
{
authenticatorData: webauthn.authenticatorData,
clientDataJSON: stringToHex(webauthn.clientDataJSON),
challengeIndex: BigInt(webauthn.challengeIndex),
typeIndex: BigInt(webauthn.typeIndex),
r,
s,
},
],
)
}
/** @internal */
export function wrapSignature(parameters: {
ownerIndex?: number | undefined
signature: Hex
}) {
const { ownerIndex = 0 } = parameters
const signatureData = (() => {
if (size(parameters.signature) !== 65) return parameters.signature
const signature = parseSignature(parameters.signature)
return encodePacked(
['bytes32', 'bytes32', 'uint8'],
[signature.r, signature.s, signature.yParity === 0 ? 27 : 28],
)
})()
return encodeAbiParameters(
[
{
components: [
{
name: 'ownerIndex',
type: 'uint8',
},
{
name: 'signatureData',
type: 'bytes',
},
],
type: 'tuple',
},
],
[
{
ownerIndex,
signatureData,
},
],
)
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Constants
/////////////////////////////////////////////////////////////////////////////////////////////
const abi = [
{ inputs: [], stateMutability: 'nonpayable', type: 'constructor' },
{
inputs: [{ name: 'owner', type: 'bytes' }],
name: 'AlreadyOwner',
type: 'error',
},
{ inputs: [], name: 'Initialized', type: 'error' },
{
inputs: [{ name: 'owner', type: 'bytes' }],
name: 'InvalidEthereumAddressOwner',
type: 'error',
},
{
inputs: [{ name: 'key', type: 'uint256' }],
name: 'InvalidNonceKey',
type: 'error',
},
{
inputs: [{ name: 'owner', type: 'bytes' }],
name: 'InvalidOwnerBytesLength',
type: 'error',
},
{ inputs: [], name: 'LastOwner', type: 'error' },
{
inputs: [{ name: 'index', type: 'uint256' }],
name: 'NoOwnerAtIndex',
type: 'error',
},
{
inputs: [{ name: 'ownersRemaining', type: 'uint256' }],
name: 'NotLastOwner',
type: 'error',
},
{
inputs: [{ name: 'selector', type: 'bytes4' }],
name: 'SelectorNotAllowed',
type: 'error',
},
{ inputs: [], name: 'Unauthorized', type: 'error' },
{ inputs: [], name: 'UnauthorizedCallContext', type: 'error' },
{ inputs: [], name: 'UpgradeFailed', type: 'error' },
{
inputs: [
{ name: 'index', type: 'uint256' },
{ name: 'expectedOwner', type: 'bytes' },
{ name: 'actualOwner', type: 'bytes' },
],
name: 'WrongOwnerAtIndex',
type: 'error',
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: 'index',
type: 'uint256',
},
{ indexed: false, name: 'owner', type: 'bytes' },
],
name: 'AddOwner',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: 'index',
type: 'uint256',
},
{ indexed: false, name: 'owner', type: 'bytes' },
],
name: 'RemoveOwner',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: 'implementation',
type: 'address',
},
],
name: 'Upgraded',
type: 'event',
},
{ stateMutability: 'payable', type: 'fallback' },
{
inputs: [],
name: 'REPLAYABLE_NONCE_KEY',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: 'owner', type: 'address' }],
name: 'addOwnerAddress',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ name: 'x', type: 'bytes32' },
{ name: 'y', type: 'bytes32' },
],
name: 'addOwnerPublicKey',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ name: 'functionSelector', type: 'bytes4' }],
name: 'canSkipChainIdValidation',
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'pure',
type: 'function',
},
{
inputs: [],
name: 'domainSeparator',
outputs: [{ name: '', type: 'bytes32' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'eip712Domain',
outputs: [
{ name: 'fields', type: 'bytes1' },
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
{ name: 'salt', type: 'bytes32' },
{ name: 'extensions', type: 'uint256[]' },
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'entryPoint',
outputs: [{ name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ name: 'target', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'data', type: 'bytes' },
],
name: 'execute',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{
components: [
{ name: 'target', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'data', type: 'bytes' },
],
name: 'calls',
type: 'tuple[]',
},
],
name: 'executeBatch',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [{ name: 'calls', type: 'bytes[]' }],
name: 'executeWithoutChainIdValidation',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{
components: [
{ name: 'sender', type: 'address' },
{ name: 'nonce', type: 'uint256' },
{ name: 'initCode', type: 'bytes' },
{ name: 'callData', type: 'bytes' },
{ name: 'callGasLimit', type: 'uint256' },
{
name: 'verificationGasLimit',
type: 'uint256',
},
{
name: 'preVerificationGas',
type: 'uint256',
},
{ name: 'maxFeePerGas', type: 'uint256' },
{
name: 'maxPriorityFeePerGas',
type: 'uint256',
},
{ name: 'paymasterAndData', type: 'bytes' },
{ name: 'signature', type: 'bytes' },
],
name: 'userOp',
type: 'tuple',
},
],
name: 'getUserOpHashWithoutChainId',
outputs: [{ name: '', type: 'bytes32' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'implementation',
outputs: [{ name: '$', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: 'owners', type: 'bytes[]' }],
name: 'initialize',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [{ name: 'account', type: 'address' }],
name: 'isOwnerAddress',
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: 'account', type: 'bytes' }],
name: 'isOwnerBytes',
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ name: 'x', type: 'bytes32' },
{ name: 'y', type: 'bytes32' },
],
name: 'isOwnerPublicKey',
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ name: 'hash', type: 'bytes32' },
{ name: 'signature', type: 'bytes' },
],
name: 'isValidSignature',
outputs: [{ name: 'result', type: 'bytes4' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'nextOwnerIndex',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: 'index', type: 'uint256' }],
name: 'ownerAtIndex',
outputs: [{ name: '', type: 'bytes' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'ownerCount',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'proxiableUUID',
outputs: [{ name: '', type: 'bytes32' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ name: 'index', type: 'uint256' },
{ name: 'owner', type: 'bytes' },
],
name: 'removeLastOwner',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ name: 'index', type: 'uint256' },
{ name: 'owner', type: 'bytes' },
],
name: 'removeOwnerAtIndex',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'removedOwnersCount',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: 'hash', type: 'bytes32' }],
name: 'replaySafeHash',
outputs: [{ name: '', type: 'bytes32' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ name: 'newImplementation', type: 'address' },
{ name: 'data', type: 'bytes' },
],
name: 'upgradeToAndCall',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{
components: [
{ name: 'sender', type: 'address' },
{ name: 'nonce', type: 'uint256' },
{ name: 'initCode', type: 'bytes' },
{ name: 'callData', type: 'bytes' },
{ name: 'callGasLimit', type: 'uint256' },
{
name: 'verificationGasLimit',
type: 'uint256',
},
{
name: 'preVerificationGas',
type: 'uint256',
},
{ name: 'maxFeePerGas', type: 'uint256' },
{
name: 'maxPriorityFeePerGas',
type: 'uint256',
},
{ name: 'paymasterAndData', type: 'bytes' },
{ name: 'signature', type: 'bytes' },
],
name: 'userOp',
type: 'tuple',
},
{ name: 'userOpHash', type: 'bytes32' },
{ name: 'missingAccountFunds', type: 'uint256' },
],
name: 'validateUserOp',
outputs: [{ name: 'validationData', type: 'uint256' }],
stateMutability: 'nonpayable',
type: 'function',
},
{ stateMutability: 'payable', type: 'receive' },
] as const
const factoryAbi = [
{
inputs: [{ name: 'implementation_', type: 'address' }],
stateMutability: 'payable',
type: 'constructor',
},
{ inputs: [], name: 'OwnerRequired', type: 'error' },
{
inputs: [
{ name: 'owners', type: 'bytes[]' },
{ name: 'nonce', type: 'uint256' },
],
name: 'createAccount',
outputs: [
{
name: 'account',
type: 'address',
},
],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{ name: 'owners', type: 'bytes[]' },
{ name: 'nonce', type: 'uint256' },
],
name: 'getAddress',
outputs: [{ name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'implementation',
outputs: [{ name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'initCodeHash',
outputs: [{ name: '', type: 'bytes32' }],
stateMutability: 'view',
type: 'function',
},
] as const
Выполнить команду
Для локальной разработки. Не используйте в интернете!