@velas/account-agent
Version:
sdk
384 lines (333 loc) • 11.1 kB
JavaScript
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,
};