UNPKG

@velas/account-agent

Version:

sdk

384 lines (333 loc) 11.1 kB
import * as web3 from '@velas/web3' import * as rlp from 'rlp' import { Keccak } from 'sha3' import assert from './assert' const hash = new Keccak(256) function bufferFrom (value) { const data = value ? Buffer.from(value) : Buffer.from('0x0') const len_buffer = Buffer.alloc(8) len_buffer.writeInt32LE(data.length, 0) return Buffer.concat([len_buffer, data]) } function bufferFromHex (value) { const data = value ? new Buffer.from(value.slice(2), 'hex') : Buffer.alloc(0) const len_buffer = Buffer.alloc(8) len_buffer.writeInt32LE(data.length, 0) return Buffer.concat([len_buffer, data]) } function invalidResponseError (result, host) { let message = !!result && !!result.error && !!result.error.message ? `[provider] ${result.error.message}` : `[provider] Invalid JSON RPC response from host provider ${host}: ${JSON.stringify( result, null, 2 )}` message = !!result && !!result.error && !!result.error.description ? result.error.description : message return new Error(message) } function isHexStrict (hex) { return ( (typeof hex === 'string' || typeof hex === 'number') && /^(-)?0x[0-9a-f]*$/i.test(hex) ) } function hexToBytes (hex) { hex = hex.toString(16) hex = hex.replace(/^0x/i, '') hex = hex.length % 2 ? `0${hex}` : hex for (var bytes = [], c = 0; c < hex.length; c += 2) bytes.push(parseInt(hex.substr(c, 2), 16)) return bytes } function serialize_authorized_evm_tx ({ from, nonce, value, data, to, gasPrice, gas }) { return Buffer.concat([ Buffer.from([4, 0, 0, 0]), bufferFrom(from), bufferFrom(nonce), bufferFrom(gasPrice), bufferFrom(gas), to ? Buffer.from([0, 0, 0, 0]) : Buffer.from([1, 0, 0, 0]), to ? bufferFrom(to) : Buffer.alloc(0), bufferFrom(value || null), bufferFromHex(data || null) ]) } function evmTransaction ( { id, transaction, chainId, csrf_token = null, broadcast = null, host = null, gas_sponsoring = null }, cb ) { const self = !this.client ? { client: this } : this; try { const data = serialize_authorized_evm_tx(transaction) transaction.nonce = transaction.nonce === '0x0' ? '0x' : transaction.nonce; transaction.gasPrice = transaction.gasPrice === '0x0' ? '0x' : transaction.gasPrice; transaction.gas = transaction.gas === '0x0' ? '0x' : transaction.gas; transaction.value = !transaction.value ? '0x' : transaction.value; transaction.data = !transaction.data ? '0x' : transaction.data; const raw = [ Uint8Array.from(hexToBytes(transaction.nonce)), Uint8Array.from(hexToBytes(transaction.gasPrice)), Uint8Array.from(hexToBytes(transaction.gas)), Uint8Array.from(hexToBytes(transaction.to)), Uint8Array.from(hexToBytes(transaction.value)), Uint8Array.from(hexToBytes(transaction.data)), Uint8Array.from(hexToBytes(chainId)), Uint8Array.from(hexToBytes(transaction.from)), Uint8Array.from(hexToBytes('0x1')), ]; const transactionRpl = rlp.encode(raw).toString('hex'); const transactionHash = hash.update(Buffer.from(transactionRpl, 'hex')).digest('hex'); hash.reset(); const sessionKey = new web3.PublicKey(transaction.op_key); const sponsorPubKey = new web3.PublicKey(self.client.transactions_sponsor_pub_key); if (gas_sponsoring) { const estimatedGas = 100006000; const transactionParams = { account: transaction.account, fromAccountPubKey: transaction.account, ownerOrOprationalPubKey: transaction.op_key, estimatedGas, data }; self.client.sponsorAndExecuteTransaction(transactionParams).then(tr => { self.client.connection.getRecentBlockhash().then(({blockhash})=>{ tr.recentBlockhash = blockhash; tr.feePayer = broadcast ? sponsorPubKey : sessionKey; if (!host && !csrf_token) { cb(null, tr); return; }; self.client.signAndBroadcastWithKey({ account: transaction.account, op_key: transaction.op_key, message: tr.serializeMessage(), csrf_token, host, }).then(()=> { cb(null, { id, jsonrpc: '2.0', result: '0x' + transactionHash }) }).catch((error) => { cb(error, { id, jsonrpc: '2.0', result: '0x' + transactionHash }) }); }); }); } else { const evmInstruction = new web3.TransactionInstruction({ programId: new web3.PublicKey('EVM1111111111111111111111111111111111111111'), keys: [ { pubkey: new web3.PublicKey('EvmState11111111111111111111111111111111111'), isSigner: false, isWritable: true }, { pubkey: new web3.PublicKey(transaction.account), isSigner: false, isWritable: false } ], data }) const keys = [ { pubkey: evmInstruction.programId, isSigner: false, isWritable: false }, ...evmInstruction.keys ] const transactionParams = { account: transaction.account, fromAccountPubKey: transaction.account, ownerOrOprationalPubKey: transaction.op_key, keys, data } self.client.executeTransaction(transactionParams).then( async tr => { self.client.connection.getRecentBlockhash().then(({ blockhash }) => { tr.recentBlockhash = blockhash tr.feePayer = broadcast ? sponsorPubKey : sessionKey if (!host && !csrf_token) { cb(null, tr); return; }; self.client.signAndBroadcastWithKey({ account: transaction.account, op_key: transaction.op_key, message: tr.serializeMessage(), csrf_token, host, }) .then(()=> { cb(null, { id, jsonrpc: '2.0', result: '0x' + transactionHash }) }).catch((error) => { cb(error, { id, jsonrpc: '2.0', result: '0x' + transactionHash }) }) }) } ) } } catch (error) { cb(error, null) } } const httpProvider = function (payload, cb) { const self = this assert.check(self.client.connection._rpcEndpoint, { type: 'string', message: `[provider] networkApiHost not set` }) if (payload.method === 'eth_sendTransaction') { // if (typeof self.authResult !== 'object') { // cb(`[provider] default account not set`, null) // return // } if (!self.client.transactions_sponsor_pub_key) { cb(`[provider] agent initialized without transactions_sponsor_pub_key param. Please set.`, null) return }; if (!payload.params || !payload.params[0]) { cb(`[provider] incorrect payload`, null) return } if (!payload.params[0].account) { cb(`[provider] account param is required`, null) return } if (!payload.params[0].op_key) { cb(`[provider] op_key param is required`, null) return } if (!payload.params[0].nonce) { cb(`[provider] nonce is required`, null) return } if (!isHexStrict(payload.params[0].nonce)) { cb(`[provider] nonce should be hex string`, null) return } if (!payload.params[0].gasPrice) { cb(`[provider] gasPrice is required`, null) return } if (!isHexStrict(payload.params[0].gasPrice)) { cb(`[provider] gasPrice should be hex string`, null) return } if (!payload.params[0].gas) { cb(`[provider] gas is required`, null) return } if (!isHexStrict(payload.params[0].gas)) { cb(`[provider] gas should be hex string`, null) return } if (payload.params[0].value && !isHexStrict(payload.params[0].value)) { cb(`[provider] value should be hex string`, null) return } if (payload.params[0].data && !isHexStrict(payload.params[0].data)) { cb(`[provider] data should be hex string`, null) return } if ( !payload.params[0].broadcast && (!payload.params[0].csrf_token || typeof payload.params[0].csrf_token !== 'string') ) { cb( `[provider] csrf_token option is required to broadcast transaction`, null ) return } self.evmTransactionParams = payload.params[0] payload.method = 'eth_chainId' payload.params = [] } let request; if (typeof XMLHttpRequest !== 'undefined') { request = new XMLHttpRequest(); } else { throw new Error("your environment isn't supported for current version"); }; request.timeout = self.timeout || 0 request.open('POST', self.client.connection._rpcEndpoint, true) request.setRequestHeader('Content-Type', 'application/json') request.onreadystatechange = () => { if (request.readyState === 4 && request.timeout !== 1) { var result = request.responseText var error = null try { result = JSON.parse(result) } catch (_) { error = invalidResponseError( request.responseText, self.client.connection._rpcEndpoint ) } if (self.evmTransactionParams) { const gas_sponsoring = self.evmTransactionParams.gasSponsoring; const broadcast = self.evmTransactionParams.broadcast const csrf_token = self.evmTransactionParams.csrf_token const host = self.evmTransactionParams.host delete self.evmTransactionParams.broadcast delete self.evmTransactionParams.csrf_token evmTransaction.call( self, { id: result.id, transaction: self.evmTransactionParams, chainId: result.result, broadcast, csrf_token, host, gas_sponsoring, }, cb ) self.evmTransactionParams = null } else { cb(error, result) } } } request.ontimeout = () => { cb( `[provider] CONNECTION TIMEOUT: http request timeout after ${self.timeout || 0} ms. (i.e. your connect has timed out for whatever reason, check your provider).`, null ) } try { request.send(JSON.stringify(payload)) } catch (error) { cb( `[provider] CONNECTION ERROR: Couldn't connect to node '${ self.client.connection._rpcEndpoint }': ${JSON.stringify(error, null, 2)}`, null ) } } export default { provider: httpProvider, evmTransaction: evmTransaction, };