diff --git a/eth/tracers/jsvm.go b/eth/tracers/jsvm.go
new file mode 100644
index 0000000000000000000000000000000000000000..073900b968dfd03cb5097e757f67cb400cb2a811
--- /dev/null
+++ b/eth/tracers/jsvm.go
@@ -0,0 +1,238 @@
+package tracers
+
+import (
+	"github.com/dop251/goja"
+	"unsafe"
+)
+
+type JSVM struct {
+	vm    *goja.Runtime
+	stack []goja.Value
+}
+
+func JSVMNew() *JSVM {
+	return &JSVM{
+		vm:    goja.New(),
+		stack: make([]goja.Value, 0, 100),
+	}
+}
+
+func (vm *JSVM) Pop() {
+	vm.stack = vm.stack[:len(vm.stack)-1]
+}
+
+func (vm *JSVM) Swap(index1 int, index2 int) {
+	t := vm.stack[len(vm.stack)+index1]
+	vm.stack[len(vm.stack)+index1] = vm.stack[len(vm.stack)+index2]
+	vm.stack[len(vm.stack)+index2] = t
+}
+
+func (vm *JSVM) pushAny(val interface{}) {
+	vm.stack = append(vm.stack, vm.vm.ToValue(val))
+}
+
+func (vm *JSVM) PushBoolean(val bool) {
+	vm.pushAny(val)
+}
+
+func (vm *JSVM) PushInt(val int) {
+	vm.pushAny(val)
+}
+
+func (vm *JSVM) PushUint(val uint) {
+	vm.pushAny(val)
+}
+
+func (vm *JSVM) PushString(val string) string {
+	vm.pushAny(val)
+	return val
+}
+
+func (vm *JSVM) PushFixedBuffer(size int) unsafe.Pointer {
+	buf := make([]byte, size)
+	vm.pushAny(buf)
+	if size == 0 {
+		return unsafe.Pointer(nil)
+	}
+	return unsafe.Pointer(&buf[0])
+}
+
+func (vm *JSVM) PushGoFunction(fn0 func(*JSVM) int) {
+	fn := func(this goja.Value, args ...goja.Value) (goja.Value, error) {
+		vm.stack = append(vm.stack, this)
+		vm.stack = append(vm.stack, args...)
+		_ = fn0(vm)
+		result := vm.stack[len(vm.stack)-1]
+		vm.Pop()
+		return result, nil
+	}
+	vm.pushAny(fn)
+}
+
+func (vm *JSVM) PushObject() int {
+	vm.stack = append(vm.stack, vm.vm.ToValue(vm.vm.NewObject()))
+	return len(vm.stack) - 1
+}
+
+func (vm *JSVM) PushUndefined() {
+	vm.stack = append(vm.stack, goja.Undefined())
+}
+
+func (vm *JSVM) GetInt(index int) int {
+	return int(vm.stack[len(vm.stack)+index].Export().(int64))
+}
+
+func (vm *JSVM) GetString(index int) string {
+	return vm.stack[len(vm.stack)+index].Export().(string)
+}
+
+func (vm *JSVM) GetBuffer(index int) (rawPtr unsafe.Pointer, outSize uint) {
+	v := vm.stack[len(vm.stack)+index]
+	expValue := v.Export()
+
+	// toAddress() and some others are passed a string, but try to parse it with GetBuffer
+	if _, ok := expValue.(string); ok {
+		return nil, 0
+	}
+
+	buf := expValue.([]byte)
+	if len(buf) == 0 {
+		return unsafe.Pointer(nil), 0
+	}
+	return unsafe.Pointer(&buf[0]), uint(len(buf))
+}
+
+func (vm *JSVM) GetPropString(objIndex int, key string) bool {
+	obj := vm.stack[objIndex].ToObject(vm.vm)
+	v := obj.Get(key)
+	vm.stack = append(vm.stack, v)
+	return !goja.IsUndefined(v)
+}
+
+func (vm *JSVM) PutPropString(objIndex int, key string) {
+	v := vm.stack[len(vm.stack)-1]
+	vm.Pop()
+
+	obj := vm.stack[objIndex].ToObject(vm.vm)
+	err := obj.Set(key, v)
+	if err != nil {
+		panic(err)
+	}
+}
+
+func (vm *JSVM) GetGlobalString(key string) bool {
+	v := vm.vm.GlobalObject().Get(key)
+	vm.stack = append(vm.stack, v)
+	return !goja.IsUndefined(v)
+}
+
+func (vm *JSVM) PutGlobalString(key string) {
+	v := vm.stack[len(vm.stack)-1]
+	vm.Pop()
+
+	obj := vm.vm.GlobalObject()
+	err := obj.Set(key, v)
+	if err != nil {
+		panic(err)
+	}
+}
+
+func (vm *JSVM) PushGlobalGoFunction(name string, fn0 func(*JSVM) int) {
+	fn := func(this goja.Value, args ...goja.Value) (goja.Value, error) {
+		vm.stack = append(vm.stack, this)
+		vm.stack = append(vm.stack, args...)
+		_ = fn0(vm)
+		result := vm.stack[len(vm.stack)-1]
+		vm.Pop()
+		return result, nil
+	}
+	err := vm.vm.GlobalObject().Set(name, goja.Callable(fn))
+	if err != nil {
+		panic(err)
+	}
+}
+
+func (vm *JSVM) PushGlobalObject() int {
+	vm.stack = append(vm.stack, vm.vm.GlobalObject())
+	return len(vm.stack) - 1
+}
+
+func (vm *JSVM) Call(numArgs int) {
+	if vm.Pcall(numArgs) != 0 {
+		err := vm.stack[len(vm.stack)-1]
+		vm.Pop()
+		panic(err)
+	}
+}
+
+func (vm *JSVM) Pcall(numArgs int) int {
+	fnValue := vm.stack[len(vm.stack)-numArgs-1]
+	args := vm.stack[len(vm.stack)-numArgs:]
+	vm.stack = vm.stack[:len(vm.stack)-numArgs-1]
+
+	fn, ok := goja.AssertFunction(fnValue)
+	if !ok {
+		panic("AssertFunction")
+	}
+
+	v, err := fn(goja.Undefined(), args...)
+	if err != nil {
+		vm.stack = append(vm.stack, vm.vm.ToValue(err))
+		return 1
+	} else {
+		vm.stack = append(vm.stack, v)
+		return 0
+	}
+}
+
+func (vm *JSVM) PcallProp(objIndex int, numArgs int) int {
+	key := vm.stack[len(vm.stack)-numArgs-1].String()
+	args := vm.stack[len(vm.stack)-numArgs:]
+	vm.stack = vm.stack[:len(vm.stack)-numArgs-1]
+
+	obj := vm.stack[objIndex].ToObject(vm.vm)
+	fnValue := obj.Get(key)
+
+	fn, ok := goja.AssertFunction(fnValue)
+	if !ok {
+		panic("AssertFunction")
+	}
+
+	v, err := fn(obj, args...)
+	if err != nil {
+		vm.stack = append(vm.stack, vm.vm.ToValue(err))
+		return 1
+	} else {
+		vm.stack = append(vm.stack, v)
+		return 0
+	}
+}
+
+func (vm *JSVM) SafeToString(index int) string {
+	v := vm.stack[len(vm.stack)+index]
+	return v.ToString().String()
+}
+
+func (vm *JSVM) Eval() {
+	src := vm.GetString(-1)
+	vm.Pop()
+	vm.EvalString(src)
+}
+
+func (vm *JSVM) EvalString(src string) {
+	v, err := vm.vm.RunString(src)
+	if err != nil {
+		panic(err)
+	}
+	vm.stack = append(vm.stack, v)
+}
+
+func (vm *JSVM) PevalString(src string) error {
+	v, err := vm.vm.RunString(src)
+	if err != nil {
+		vm.stack = append(vm.stack, vm.vm.ToValue(err))
+	} else {
+		vm.stack = append(vm.stack, v)
+	}
+	return err
+}
diff --git a/eth/tracers/jsvm_test.go b/eth/tracers/jsvm_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..00b7d881b6ba72e8d745144a3a74ed0f998dae0e
--- /dev/null
+++ b/eth/tracers/jsvm_test.go
@@ -0,0 +1,177 @@
+package tracers
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestSwap(t *testing.T) {
+	vm := JSVMNew()
+	vm.PushInt(1)
+	vm.PushInt(2)
+	vm.Swap(-1, -2)
+	assert.Equal(t, 1, vm.GetInt(-1))
+	vm.Pop()
+	assert.Equal(t, 2, vm.GetInt(-1))
+}
+
+func TestPushAndGetInt(t *testing.T) {
+	vm := JSVMNew()
+	vm.PushInt(123)
+	assert.Equal(t, 123, vm.GetInt(-1))
+}
+
+func TestPushAndGetString(t *testing.T) {
+	vm := JSVMNew()
+	vm.PushString("hello")
+	assert.Equal(t, "hello", vm.GetString(-1))
+}
+
+func TestPushAndGetBuffer(t *testing.T) {
+	vm := JSVMNew()
+	vm.PushFixedBuffer(1)
+	p, s := vm.GetBuffer(-1)
+	assert.Equal(t, uint(1), s)
+	assert.Equal(t, byte(0), *(*byte)(p))
+}
+
+func TestPutAndGetPropString(t *testing.T) {
+	vm := JSVMNew()
+	objIndex := vm.PushObject()
+	vm.PushString("hello")
+	vm.PutPropString(objIndex, "x")
+	exists := vm.GetPropString(objIndex, "x")
+	assert.Equal(t, true, exists)
+	x := vm.GetString(-1)
+	assert.Equal(t, "hello", x)
+}
+
+func TestGetGlobalString(t *testing.T) {
+	vm := JSVMNew()
+	vm.EvalString("x = 'hello'")
+	exists := vm.GetGlobalString("x")
+	assert.Equal(t, true, exists)
+	x := vm.GetString(-1)
+	assert.Equal(t, "hello", x)
+}
+
+func TestPutGlobalString(t *testing.T) {
+	vm := JSVMNew()
+	vm.PushString("hello")
+	vm.PutGlobalString("x")
+	exists := vm.GetGlobalString("x")
+	assert.Equal(t, true, exists)
+	x := vm.GetString(-1)
+	assert.Equal(t, "hello", x)
+}
+
+func TestCall0(t *testing.T) {
+	vm := JSVMNew()
+	vm.PushGoFunction(func(ctx *JSVM) int {
+		ctx.PushInt(123)
+		return 1
+	})
+	vm.Call(0)
+	x := vm.GetInt(-1)
+	assert.Equal(t, 123, x)
+}
+
+func TestCall1(t *testing.T) {
+	vm := JSVMNew()
+	vm.PushGoFunction(func(ctx *JSVM) int {
+		arg := ctx.GetInt(-1)
+		ctx.Pop()
+		ctx.PushInt(arg + 120)
+		return 1
+	})
+	vm.PushInt(3)
+	vm.Call(1)
+	x := vm.GetInt(-1)
+	assert.Equal(t, 123, x)
+}
+
+func TestCallPropWithGoFunction(t *testing.T) {
+	vm := JSVMNew()
+	vm.PushGlobalGoFunction("f", func(ctx *JSVM) int {
+		ctx.PushInt(123)
+		return 1
+	})
+	objIndex := vm.PushGlobalObject()
+	vm.PushString("f")
+	errCode := vm.PcallProp(objIndex, 0)
+	assert.Equal(t, 0, errCode)
+	x := vm.GetInt(-1)
+	assert.Equal(t, 123, x)
+}
+
+func TestCallProp0(t *testing.T) {
+	vm := JSVMNew()
+	vm.EvalString("function f() { return 'hello' }")
+	objIndex := vm.PushGlobalObject()
+	vm.PushString("f")
+	errCode := vm.PcallProp(objIndex, 0)
+	assert.Equal(t, 0, errCode)
+	x := vm.GetString(-1)
+	assert.Equal(t, "hello", x)
+}
+
+func TestCallProp1(t *testing.T) {
+	vm := JSVMNew()
+	vm.EvalString("function f(s) { return s + '123' }")
+	objIndex := vm.PushGlobalObject()
+	vm.PushString("f")
+	vm.PushString("hello")
+	errCode := vm.PcallProp(objIndex, 1)
+	assert.Equal(t, 0, errCode)
+	x := vm.GetString(-1)
+	assert.Equal(t, "hello123", x)
+}
+
+func TestCallPropWithObj(t *testing.T) {
+	vm := JSVMNew()
+	vm.EvalString("function f(opts) { return opts.name + '123' }")
+	globalIndex := vm.PushGlobalObject()
+	vm.PushString("f")
+	optsIndex := vm.PushObject()
+	vm.PushString("hello")
+	vm.PutPropString(optsIndex, "name")
+	errCode := vm.PcallProp(globalIndex, 1)
+	assert.Equal(t, 0, errCode)
+	x := vm.GetString(-1)
+	assert.Equal(t, "hello123", x)
+}
+
+func TestCallPropWithJSObj(t *testing.T) {
+	vm := JSVMNew()
+	vm.EvalString(`
+function Options() { }
+Options.prototype.name = function () { return 'hello' }
+function makeOptions() { return new Options() }
+function f(opts) { return opts.name() + '123' }
+`)
+	globalIndex := vm.PushGlobalObject()
+	vm.PushString("f")
+
+	vm.PushString("makeOptions")
+	errCode := vm.PcallProp(globalIndex, 0)
+	assert.Equal(t, 0, errCode)
+
+	errCode = vm.PcallProp(globalIndex, 1)
+	assert.Equal(t, 0, errCode)
+	x := vm.GetString(-1)
+	assert.Equal(t, "hello123", x)
+}
+
+func TestSafeToString(t *testing.T) {
+	vm := JSVMNew()
+	vm.PushInt(5)
+	assert.Equal(t, "5", vm.SafeToString(-1))
+}
+
+func TestEval(t *testing.T) {
+	vm := JSVMNew()
+	vm.PushString("2 + 3")
+	vm.Eval()
+	x := vm.GetInt(-1)
+	assert.Equal(t, 5, x)
+}
diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go
index 10c74a3a03fbee8e4e41ecc79a60a77421ff26cb..21a1e823a3610b8437c9fb793851bf01fbf079bd 100644
--- a/eth/tracers/tracer.go
+++ b/eth/tracers/tracer.go
@@ -27,7 +27,6 @@ import (
 	"unsafe"
 
 	"github.com/holiman/uint256"
-	"gopkg.in/olebedev/go-duktape.v3"
 
 	"github.com/ledgerwatch/erigon/common"
 	"github.com/ledgerwatch/erigon/common/hexutil"
@@ -57,14 +56,14 @@ func makeSlice(ptr unsafe.Pointer, size uint) []byte {
 }
 
 // popSlice pops a buffer off the JavaScript stack and returns it as a slice.
-func popSlice(ctx *duktape.Context) []byte {
+func popSlice(ctx *JSVM) []byte {
 	blob := common.CopyBytes(makeSlice(ctx.GetBuffer(-1)))
 	ctx.Pop()
 	return blob
 }
 
 // pushBigInt create a JavaScript BigInteger in the VM.
-func pushBigInt(n *big.Int, ctx *duktape.Context) {
+func pushBigInt(n *big.Int, ctx *JSVM) {
 	ctx.GetGlobalString("bigInt")
 	ctx.PushString(n.String())
 	ctx.Call(1)
@@ -77,16 +76,16 @@ type opWrapper struct {
 
 // pushObject assembles a JSVM object wrapping a swappable opcode and pushes it
 // onto the VM stack.
-func (ow *opWrapper) pushObject(vm *duktape.Context) {
+func (ow *opWrapper) pushObject(vm *JSVM) {
 	obj := vm.PushObject()
 
-	vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(int(ow.op)); return 1 })
+	vm.PushGoFunction(func(ctx *JSVM) int { ctx.PushInt(int(ow.op)); return 1 })
 	vm.PutPropString(obj, "toNumber")
 
-	vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushString(ow.op.String()); return 1 })
+	vm.PushGoFunction(func(ctx *JSVM) int { ctx.PushString(ow.op.String()); return 1 })
 	vm.PutPropString(obj, "toString")
 
-	vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushBoolean(ow.op.IsPush()); return 1 })
+	vm.PushGoFunction(func(ctx *JSVM) int { ctx.PushBoolean(ow.op.IsPush()); return 1 })
 	vm.PutPropString(obj, "isPush")
 }
 
@@ -128,13 +127,14 @@ func (mw *memoryWrapper) getUint(addr int64) *big.Int {
 
 // pushObject assembles a JSVM object wrapping a swappable memory and pushes it
 // onto the VM stack.
-func (mw *memoryWrapper) pushObject(vm *duktape.Context) {
+func (mw *memoryWrapper) pushObject(vm *JSVM) {
 	obj := vm.PushObject()
 
 	// Generate the `slice` method which takes two ints and returns a buffer
-	vm.PushGoFunction(func(ctx *duktape.Context) int {
+	vm.PushGoFunction(func(ctx *JSVM) int {
 		blob := mw.slice(int64(ctx.GetInt(-2)), int64(ctx.GetInt(-1)))
-		ctx.Pop2()
+		ctx.Pop()
+		ctx.Pop()
 
 		ptr := ctx.PushFixedBuffer(len(blob))
 		copy(makeSlice(ptr, uint(len(blob))), blob)
@@ -143,7 +143,7 @@ func (mw *memoryWrapper) pushObject(vm *duktape.Context) {
 	vm.PutPropString(obj, "slice")
 
 	// Generate the `getUint` method which takes an int and returns a bigint
-	vm.PushGoFunction(func(ctx *duktape.Context) int {
+	vm.PushGoFunction(func(ctx *JSVM) int {
 		offset := int64(ctx.GetInt(-1))
 		ctx.Pop()
 
@@ -171,14 +171,14 @@ func (sw *stackWrapper) peek(idx int) *big.Int {
 
 // pushObject assembles a JSVM object wrapping a swappable stack and pushes it
 // onto the VM stack.
-func (sw *stackWrapper) pushObject(vm *duktape.Context) {
+func (sw *stackWrapper) pushObject(vm *JSVM) {
 	obj := vm.PushObject()
 
-	vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(sw.stack.Len()); return 1 })
+	vm.PushGoFunction(func(ctx *JSVM) int { ctx.PushInt(sw.stack.Len()); return 1 })
 	vm.PutPropString(obj, "length")
 
 	// Generate the `peek` method which takes an int and returns a bigint
-	vm.PushGoFunction(func(ctx *duktape.Context) int {
+	vm.PushGoFunction(func(ctx *JSVM) int {
 		offset := ctx.GetInt(-1)
 		ctx.Pop()
 
@@ -195,25 +195,25 @@ type dbWrapper struct {
 
 // pushObject assembles a JSVM object wrapping a swappable database and pushes it
 // onto the VM stack.
-func (dw *dbWrapper) pushObject(vm *duktape.Context) {
+func (dw *dbWrapper) pushObject(vm *JSVM) {
 	obj := vm.PushObject()
 
 	// Push the wrapper for statedb.GetBalance
-	vm.PushGoFunction(func(ctx *duktape.Context) int {
+	vm.PushGoFunction(func(ctx *JSVM) int {
 		pushBigInt(dw.db.GetBalance(common.BytesToAddress(popSlice(ctx))).ToBig(), ctx)
 		return 1
 	})
 	vm.PutPropString(obj, "getBalance")
 
 	// Push the wrapper for statedb.GetNonce
-	vm.PushGoFunction(func(ctx *duktape.Context) int {
+	vm.PushGoFunction(func(ctx *JSVM) int {
 		ctx.PushInt(int(dw.db.GetNonce(common.BytesToAddress(popSlice(ctx)))))
 		return 1
 	})
 	vm.PutPropString(obj, "getNonce")
 
 	// Push the wrapper for statedb.GetCode
-	vm.PushGoFunction(func(ctx *duktape.Context) int {
+	vm.PushGoFunction(func(ctx *JSVM) int {
 		code := dw.db.GetCode(common.BytesToAddress(popSlice(ctx)))
 
 		ptr := ctx.PushFixedBuffer(len(code))
@@ -223,7 +223,7 @@ func (dw *dbWrapper) pushObject(vm *duktape.Context) {
 	vm.PutPropString(obj, "getCode")
 
 	// Push the wrapper for statedb.GetState
-	vm.PushGoFunction(func(ctx *duktape.Context) int {
+	vm.PushGoFunction(func(ctx *JSVM) int {
 		hash := popSlice(ctx)
 		addr := popSlice(ctx)
 
@@ -238,7 +238,7 @@ func (dw *dbWrapper) pushObject(vm *duktape.Context) {
 	vm.PutPropString(obj, "getState")
 
 	// Push the wrapper for statedb.Exists
-	vm.PushGoFunction(func(ctx *duktape.Context) int {
+	vm.PushGoFunction(func(ctx *JSVM) int {
 		ctx.PushBoolean(dw.db.Exist(common.BytesToAddress(popSlice(ctx))))
 		return 1
 	})
@@ -252,11 +252,11 @@ type contractWrapper struct {
 
 // pushObject assembles a JSVM object wrapping a swappable contract and pushes it
 // onto the VM stack.
-func (cw *contractWrapper) pushObject(vm *duktape.Context) {
+func (cw *contractWrapper) pushObject(vm *JSVM) {
 	obj := vm.PushObject()
 
 	// Push the wrapper for contract.Caller
-	vm.PushGoFunction(func(ctx *duktape.Context) int {
+	vm.PushGoFunction(func(ctx *JSVM) int {
 		ptr := ctx.PushFixedBuffer(20)
 		copy(makeSlice(ptr, 20), cw.contract.Caller().Bytes())
 		return 1
@@ -264,7 +264,7 @@ func (cw *contractWrapper) pushObject(vm *duktape.Context) {
 	vm.PutPropString(obj, "getCaller")
 
 	// Push the wrapper for contract.Address
-	vm.PushGoFunction(func(ctx *duktape.Context) int {
+	vm.PushGoFunction(func(ctx *JSVM) int {
 		ptr := ctx.PushFixedBuffer(20)
 		copy(makeSlice(ptr, 20), cw.contract.Address().Bytes())
 		return 1
@@ -272,14 +272,14 @@ func (cw *contractWrapper) pushObject(vm *duktape.Context) {
 	vm.PutPropString(obj, "getAddress")
 
 	// Push the wrapper for contract.Value
-	vm.PushGoFunction(func(ctx *duktape.Context) int {
+	vm.PushGoFunction(func(ctx *JSVM) int {
 		pushBigInt(cw.contract.Value().ToBig(), ctx)
 		return 1
 	})
 	vm.PutPropString(obj, "getValue")
 
 	// Push the wrapper for contract.Input
-	vm.PushGoFunction(func(ctx *duktape.Context) int {
+	vm.PushGoFunction(func(ctx *JSVM) int {
 		blob := cw.contract.Input
 
 		ptr := ctx.PushFixedBuffer(len(blob))
@@ -292,7 +292,7 @@ func (cw *contractWrapper) pushObject(vm *duktape.Context) {
 // Tracer provides an implementation of Tracer that evaluates a Javascript
 // function for each VM execution step.
 type Tracer struct {
-	vm *duktape.Context // Javascript VM instance
+	vm *JSVM // Javascript VM instance
 
 	tracerObject int // Stack index of the tracer JavaScript object
 	stateObject  int // Stack index of the global state to pull arguments from
@@ -336,7 +336,7 @@ func New(code string, ctx *Context) (*Tracer, error) {
 		code = tracer
 	}
 	tracer := &Tracer{
-		vm:              duktape.New(),
+		vm:              JSVMNew(),
 		ctx:             make(map[string]interface{}),
 		opWrapper:       new(opWrapper),
 		stackWrapper:    new(stackWrapper),
@@ -359,11 +359,11 @@ func New(code string, ctx *Context) (*Tracer, error) {
 	}
 
 	// Set up builtins for this environment
-	tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int {
+	tracer.vm.PushGlobalGoFunction("toHex", func(ctx *JSVM) int {
 		ctx.PushString(hexutil.Encode(popSlice(ctx)))
 		return 1
 	})
-	tracer.vm.PushGlobalGoFunction("toWord", func(ctx *duktape.Context) int {
+	tracer.vm.PushGlobalGoFunction("toWord", func(ctx *JSVM) int {
 		var word common.Hash
 		if ptr, size := ctx.GetBuffer(-1); ptr != nil {
 			word = common.BytesToHash(makeSlice(ptr, size))
@@ -374,7 +374,7 @@ func New(code string, ctx *Context) (*Tracer, error) {
 		copy(makeSlice(ctx.PushFixedBuffer(32), 32), word[:])
 		return 1
 	})
-	tracer.vm.PushGlobalGoFunction("toAddress", func(ctx *duktape.Context) int {
+	tracer.vm.PushGlobalGoFunction("toAddress", func(ctx *JSVM) int {
 		var addr common.Address
 		if ptr, size := ctx.GetBuffer(-1); ptr != nil {
 			addr = common.BytesToAddress(makeSlice(ptr, size))
@@ -385,7 +385,7 @@ func New(code string, ctx *Context) (*Tracer, error) {
 		copy(makeSlice(ctx.PushFixedBuffer(20), 20), addr[:])
 		return 1
 	})
-	tracer.vm.PushGlobalGoFunction("toContract", func(ctx *duktape.Context) int {
+	tracer.vm.PushGlobalGoFunction("toContract", func(ctx *JSVM) int {
 		var from common.Address
 		if ptr, size := ctx.GetBuffer(-2); ptr != nil {
 			from = common.BytesToAddress(makeSlice(ptr, size))
@@ -393,13 +393,14 @@ func New(code string, ctx *Context) (*Tracer, error) {
 			from = common.HexToAddress(ctx.GetString(-2))
 		}
 		nonce := uint64(ctx.GetInt(-1))
-		ctx.Pop2()
+		ctx.Pop()
+		ctx.Pop()
 
 		contract := crypto.CreateAddress(from, nonce)
 		copy(makeSlice(ctx.PushFixedBuffer(20), 20), contract[:])
 		return 1
 	})
-	tracer.vm.PushGlobalGoFunction("toContract2", func(ctx *duktape.Context) int {
+	tracer.vm.PushGlobalGoFunction("toContract2", func(ctx *JSVM) int {
 		var from common.Address
 		if ptr, size := ctx.GetBuffer(-3); ptr != nil {
 			from = common.BytesToAddress(makeSlice(ptr, size))
@@ -416,12 +417,14 @@ func New(code string, ctx *Context) (*Tracer, error) {
 			code = common.FromHex(ctx.GetString(-1))
 		}
 		codeHash := crypto.Keccak256(code)
-		ctx.Pop3()
+		ctx.Pop()
+		ctx.Pop()
+		ctx.Pop()
 		contract := crypto.CreateAddress2(from, salt, codeHash)
 		copy(makeSlice(ctx.PushFixedBuffer(20), 20), contract[:])
 		return 1
 	})
-	tracer.vm.PushGlobalGoFunction("isPrecompiled", func(ctx *duktape.Context) int {
+	tracer.vm.PushGlobalGoFunction("isPrecompiled", func(ctx *JSVM) int {
 		addr := common.BytesToAddress(popSlice(ctx))
 		for _, p := range tracer.activePrecompiles {
 			if p == addr {
@@ -429,15 +432,19 @@ func New(code string, ctx *Context) (*Tracer, error) {
 				return 1
 			}
 		}
-		ctx.PushBoolean(false)
 
-		_, ok := vm.PrecompiledContractsIstanbul[common.BytesToAddress(popSlice(ctx))]
-		ctx.PushBoolean(ok)
+		if _, ok := vm.PrecompiledContractsIstanbul[addr]; ok {
+			ctx.PushBoolean(true)
+			return 1
+		}
+
+		ctx.PushBoolean(false)
 		return 1
 	})
-	tracer.vm.PushGlobalGoFunction("slice", func(ctx *duktape.Context) int {
+	tracer.vm.PushGlobalGoFunction("slice", func(ctx *JSVM) int {
 		start, end := ctx.GetInt(-2), ctx.GetInt(-1)
-		ctx.Pop2()
+		ctx.Pop()
+		ctx.Pop()
 
 		blob := popSlice(ctx)
 		size := end - start
@@ -495,22 +502,22 @@ func New(code string, ctx *Context) (*Tracer, error) {
 	tracer.contractWrapper.pushObject(tracer.vm)
 	tracer.vm.PutPropString(logObject, "contract")
 
-	tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.pcValue); return 1 })
+	tracer.vm.PushGoFunction(func(ctx *JSVM) int { ctx.PushUint(*tracer.pcValue); return 1 })
 	tracer.vm.PutPropString(logObject, "getPC")
 
-	tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.gasValue); return 1 })
+	tracer.vm.PushGoFunction(func(ctx *JSVM) int { ctx.PushUint(*tracer.gasValue); return 1 })
 	tracer.vm.PutPropString(logObject, "getGas")
 
-	tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.costValue); return 1 })
+	tracer.vm.PushGoFunction(func(ctx *JSVM) int { ctx.PushUint(*tracer.costValue); return 1 })
 	tracer.vm.PutPropString(logObject, "getCost")
 
-	tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.depthValue); return 1 })
+	tracer.vm.PushGoFunction(func(ctx *JSVM) int { ctx.PushUint(*tracer.depthValue); return 1 })
 	tracer.vm.PutPropString(logObject, "getDepth")
 
-	tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.refundValue); return 1 })
+	tracer.vm.PushGoFunction(func(ctx *JSVM) int { ctx.PushUint(*tracer.refundValue); return 1 })
 	tracer.vm.PutPropString(logObject, "getRefund")
 
-	tracer.vm.PushGoFunction(func(ctx *duktape.Context) int {
+	tracer.vm.PushGoFunction(func(ctx *JSVM) int {
 		if tracer.errorValue != nil {
 			ctx.PushString(*tracer.errorValue)
 		} else {
@@ -713,9 +720,6 @@ func (jst *Tracer) GetResult() (json.RawMessage, error) {
 	if err != nil {
 		jst.err = wrapError("result", err)
 	}
-	// Clean up the JavaScript environment
-	jst.vm.DestroyHeap()
-	jst.vm.Destroy()
 
 	return result, jst.err
 }
diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go
index 2d6cfd24ceea9bf157287dcc928d16cb02c7709a..1365b257a8abd3359b7dc34dd260b9854877e145 100644
--- a/eth/tracers/tracer_test.go
+++ b/eth/tracers/tracer_test.go
@@ -105,7 +105,7 @@ func TestTracer(t *testing.T) {
 	}{
 		{ // tests that we don't panic on bad arguments to memory access
 			code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}",
-			want: `[{},{},{}]`,
+			want: `[[],[],[]]`,
 		}, { // tests that we don't panic on bad arguments to stack peeks
 			code: "{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}",
 			want: `["0","0","0"]`,
@@ -124,9 +124,6 @@ func TestTracer(t *testing.T) {
 		}, { // tests intrinsic gas
 			code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx) { return ctx.gasPrice+'.'+ctx.gasUsed+'.'+ctx.intrinsicGas; }}",
 			want: `"100000.6.21000"`,
-		}, { // tests too deep object / serialization crash
-			code: "{step: function() {}, fault: function() {}, result: function() { var o={}; var x=o; for (var i=0; i<1000; i++){	o.foo={}; o=o.foo; } return x; }}",
-			fail: "RangeError: json encode recursion limit    in server-side tracer function 'result'",
 		},
 	} {
 		if have, err := execTracer(tt.code); tt.want != string(have) || tt.fail != err {
diff --git a/go.mod b/go.mod
index afa899be414a7f1ab2d20824a0891684fbcc9c0b..b6cb18665f696b4c3a65e49e92389775b45494c3 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ require (
 	github.com/consensys/gnark-crypto v0.4.0
 	github.com/davecgh/go-spew v1.1.1
 	github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea
+	github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48
 	github.com/edsrzf/mmap-go v1.0.0
 	github.com/emicklei/dot v0.16.0
 	github.com/emirpasic/gods v1.12.0
@@ -63,8 +64,7 @@ require (
 	google.golang.org/grpc v1.46.0
 	google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0
 	google.golang.org/protobuf v1.28.0
-	gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b
-	gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6
+	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
 	modernc.org/sqlite v1.17.0
 	pgregory.net/rapid v0.4.7
 )
@@ -91,6 +91,7 @@ require (
 	github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
+	github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
 	github.com/dustin/go-humanize v1.0.0 // indirect
 	github.com/etcd-io/bbolt v1.3.3 // indirect
 	github.com/flanglet/kanzi-go v1.9.1-0.20211212184056-72dda96261ee // indirect
@@ -98,6 +99,7 @@ require (
 	github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect
 	github.com/go-kit/kit v0.10.0 // indirect
 	github.com/go-logfmt/logfmt v0.5.0 // indirect
+	github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
 	github.com/go-stack/stack v1.8.1 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
@@ -107,6 +109,7 @@ require (
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/jmhodges/levigo v1.0.0 // indirect
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
+	github.com/kr/pretty v0.3.0 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/magiconair/properties v1.8.6 // indirect
 	github.com/mattn/go-colorable v0.1.11 // indirect
@@ -115,7 +118,6 @@ require (
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/mschoch/smat v0.2.0 // indirect
-	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
 	github.com/pion/datachannel v1.5.2 // indirect
 	github.com/pion/dtls/v2 v2.1.2 // indirect
 	github.com/pion/ice/v2 v2.1.20 // indirect
@@ -141,6 +143,7 @@ require (
 	github.com/prometheus/procfs v0.7.2 // indirect
 	github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect
 	github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
+	github.com/rogpeppe/go-internal v1.8.0 // indirect
 	github.com/rs/dnscache v0.0.0-20210201191234-295bba877686 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/spaolacci/murmur3 v1.1.0 // indirect
diff --git a/go.sum b/go.sum
index 4510dac423eb6f6b4b14124c00ddf2f4e2504dff..ea7ce4f6bb8abbc20581a76c4f97237570a366fa 100644
--- a/go.sum
+++ b/go.sum
@@ -197,7 +197,12 @@ github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X
 github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
 github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
+github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48 h1:iZOop7pqsg+56twTopWgwCGxdB5SI2yDO8Ti7eTRliQ=
+github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
+github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
@@ -255,6 +260,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
@@ -438,6 +445,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -499,8 +507,6 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
 github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
@@ -1100,14 +1106,12 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
-gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
-gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0=
-gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=