ox 0.14.11 → 0.14.12
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/CHANGELOG.md +8 -0
- package/_cjs/erc8021/Attribution.js +7 -1
- package/_cjs/erc8021/Attribution.js.map +1 -1
- package/_cjs/tempo/KeyAuthorization.js +141 -21
- package/_cjs/tempo/KeyAuthorization.js.map +1 -1
- package/_cjs/tempo/Period.js +31 -0
- package/_cjs/tempo/Period.js.map +1 -0
- package/_cjs/tempo/index.js +2 -1
- package/_cjs/tempo/index.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/erc8021/Attribution.js +7 -1
- package/_esm/erc8021/Attribution.js.map +1 -1
- package/_esm/tempo/KeyAuthorization.js +150 -22
- package/_esm/tempo/KeyAuthorization.js.map +1 -1
- package/_esm/tempo/Period.js +92 -0
- package/_esm/tempo/Period.js.map +1 -0
- package/_esm/tempo/index.js +29 -0
- package/_esm/tempo/index.js.map +1 -1
- package/_esm/version.js +1 -1
- package/_types/erc8021/Attribution.d.ts +2 -0
- package/_types/erc8021/Attribution.d.ts.map +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +91 -19
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/Period.d.ts +78 -0
- package/_types/tempo/Period.d.ts.map +1 -0
- package/_types/tempo/index.d.ts +29 -0
- package/_types/tempo/index.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/erc8021/Attribution.ts +12 -1
- package/package.json +6 -1
- package/tempo/KeyAuthorization.test.ts +312 -3
- package/tempo/KeyAuthorization.ts +277 -51
- package/tempo/Period/package.json +6 -0
- package/tempo/Period.test.ts +44 -0
- package/tempo/Period.ts +97 -0
- package/tempo/e2e.test.ts +890 -1
- package/tempo/index.ts +30 -0
- package/version.ts +1 -1
package/tempo/e2e.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AbiFunction,
|
|
2
3
|
Address,
|
|
3
4
|
Hex,
|
|
4
5
|
P256,
|
|
@@ -9,10 +10,11 @@ import {
|
|
|
9
10
|
} from 'ox'
|
|
10
11
|
import { getTransactionCount } from 'viem/actions'
|
|
11
12
|
import { beforeEach, describe, expect, test } from 'vitest'
|
|
12
|
-
import { chain, client, fundAddress } from '../../test/tempo/config.js'
|
|
13
|
+
import { chain, client, fundAddress, nodeEnv } from '../../test/tempo/config.js'
|
|
13
14
|
import {
|
|
14
15
|
AuthorizationTempo,
|
|
15
16
|
KeyAuthorization,
|
|
17
|
+
Period,
|
|
16
18
|
SignatureEnvelope,
|
|
17
19
|
} from './index.js'
|
|
18
20
|
import * as Transaction from './Transaction.js'
|
|
@@ -1680,4 +1682,891 @@ describe('behavior: keyAuthorization', () => {
|
|
|
1680
1682
|
expect(response.keyAuthorization?.limits).toBeUndefined()
|
|
1681
1683
|
}
|
|
1682
1684
|
})
|
|
1685
|
+
|
|
1686
|
+
// TODO: remove skipIf when testnet has T3
|
|
1687
|
+
test.skipIf(nodeEnv === 'testnet')(
|
|
1688
|
+
'behavior: access key with periodic spending limit',
|
|
1689
|
+
async () => {
|
|
1690
|
+
const privateKey = Secp256k1.randomPrivateKey()
|
|
1691
|
+
const publicKey = Secp256k1.getPublicKey({ privateKey })
|
|
1692
|
+
const address = Address.fromPublicKey(publicKey)
|
|
1693
|
+
const access = {
|
|
1694
|
+
address,
|
|
1695
|
+
publicKey,
|
|
1696
|
+
privateKey,
|
|
1697
|
+
} as const
|
|
1698
|
+
|
|
1699
|
+
const keyAuth = KeyAuthorization.from({
|
|
1700
|
+
address: access.address,
|
|
1701
|
+
chainId: BigInt(chainId),
|
|
1702
|
+
type: 'secp256k1',
|
|
1703
|
+
limits: [
|
|
1704
|
+
{
|
|
1705
|
+
token: '0x20c0000000000000000000000000000000000001',
|
|
1706
|
+
limit: Value.from('1000', 6),
|
|
1707
|
+
period: Period.months(1),
|
|
1708
|
+
},
|
|
1709
|
+
],
|
|
1710
|
+
})
|
|
1711
|
+
|
|
1712
|
+
const keyAuth_signature = Secp256k1.sign({
|
|
1713
|
+
payload: KeyAuthorization.getSignPayload(keyAuth),
|
|
1714
|
+
privateKey: root.privateKey,
|
|
1715
|
+
})
|
|
1716
|
+
|
|
1717
|
+
const keyAuth_signed = KeyAuthorization.from(keyAuth, {
|
|
1718
|
+
signature: SignatureEnvelope.from(keyAuth_signature),
|
|
1719
|
+
})
|
|
1720
|
+
|
|
1721
|
+
const nonce = await getTransactionCount(client, {
|
|
1722
|
+
address: root.address,
|
|
1723
|
+
blockTag: 'pending',
|
|
1724
|
+
})
|
|
1725
|
+
|
|
1726
|
+
const transaction = TxEnvelopeTempo.from({
|
|
1727
|
+
calls: [
|
|
1728
|
+
{
|
|
1729
|
+
to: '0x0000000000000000000000000000000000000000',
|
|
1730
|
+
},
|
|
1731
|
+
],
|
|
1732
|
+
chainId,
|
|
1733
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
1734
|
+
keyAuthorization: keyAuth_signed,
|
|
1735
|
+
nonce: BigInt(nonce),
|
|
1736
|
+
gas: 1_000_000n,
|
|
1737
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
1738
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
1739
|
+
})
|
|
1740
|
+
|
|
1741
|
+
const signature = Secp256k1.sign({
|
|
1742
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction, {
|
|
1743
|
+
from: root.address,
|
|
1744
|
+
}),
|
|
1745
|
+
privateKey: access.privateKey,
|
|
1746
|
+
})
|
|
1747
|
+
|
|
1748
|
+
const serialized_signed = TxEnvelopeTempo.serialize(transaction, {
|
|
1749
|
+
signature: SignatureEnvelope.from({
|
|
1750
|
+
userAddress: root.address,
|
|
1751
|
+
inner: SignatureEnvelope.from(signature),
|
|
1752
|
+
type: 'keychain',
|
|
1753
|
+
}),
|
|
1754
|
+
})
|
|
1755
|
+
|
|
1756
|
+
const receipt = (await client
|
|
1757
|
+
.request({
|
|
1758
|
+
method: 'eth_sendRawTransactionSync',
|
|
1759
|
+
params: [serialized_signed],
|
|
1760
|
+
})
|
|
1761
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
1762
|
+
expect(receipt).toBeDefined()
|
|
1763
|
+
expect(receipt.status).toBe('success')
|
|
1764
|
+
|
|
1765
|
+
{
|
|
1766
|
+
const response = await client
|
|
1767
|
+
.request({
|
|
1768
|
+
method: 'eth_getTransactionByHash',
|
|
1769
|
+
params: [receipt.transactionHash],
|
|
1770
|
+
})
|
|
1771
|
+
.then((tx) => Transaction.fromRpc(tx as any))
|
|
1772
|
+
if (!response) throw new Error()
|
|
1773
|
+
|
|
1774
|
+
expect(response.from).toBe(root.address)
|
|
1775
|
+
expect(response.keyAuthorization).toBeDefined()
|
|
1776
|
+
expect(response.keyAuthorization?.limits?.[0]?.limit).toBe(
|
|
1777
|
+
Value.from('1000', 6),
|
|
1778
|
+
)
|
|
1779
|
+
expect(response.keyAuthorization?.limits?.[0]?.period).toBe(2592000)
|
|
1780
|
+
}
|
|
1781
|
+
},
|
|
1782
|
+
)
|
|
1783
|
+
|
|
1784
|
+
// TODO: remove skipIf when testnet has T3
|
|
1785
|
+
test.skipIf(nodeEnv === 'testnet')(
|
|
1786
|
+
'behavior: rejects transfer exceeding periodic spending limit',
|
|
1787
|
+
async () => {
|
|
1788
|
+
const privateKey = Secp256k1.randomPrivateKey()
|
|
1789
|
+
const publicKey = Secp256k1.getPublicKey({ privateKey })
|
|
1790
|
+
const address = Address.fromPublicKey(publicKey)
|
|
1791
|
+
const token = '0x20c0000000000000000000000000000000000001'
|
|
1792
|
+
const transfer = AbiFunction.from(
|
|
1793
|
+
'function transfer(address to, uint256 amount)',
|
|
1794
|
+
)
|
|
1795
|
+
|
|
1796
|
+
// Key with a 5 USDC periodic limit
|
|
1797
|
+
const keyAuth = KeyAuthorization.from({
|
|
1798
|
+
address,
|
|
1799
|
+
chainId: BigInt(chainId),
|
|
1800
|
+
type: 'secp256k1',
|
|
1801
|
+
limits: [
|
|
1802
|
+
{
|
|
1803
|
+
token,
|
|
1804
|
+
limit: Value.from('5', 6),
|
|
1805
|
+
period: Period.months(1),
|
|
1806
|
+
},
|
|
1807
|
+
],
|
|
1808
|
+
})
|
|
1809
|
+
|
|
1810
|
+
const keyAuth_signed = KeyAuthorization.from(keyAuth, {
|
|
1811
|
+
signature: SignatureEnvelope.from(
|
|
1812
|
+
Secp256k1.sign({
|
|
1813
|
+
payload: KeyAuthorization.getSignPayload(keyAuth),
|
|
1814
|
+
privateKey: root.privateKey,
|
|
1815
|
+
}),
|
|
1816
|
+
),
|
|
1817
|
+
})
|
|
1818
|
+
|
|
1819
|
+
const nonce = await getTransactionCount(client, {
|
|
1820
|
+
address: root.address,
|
|
1821
|
+
blockTag: 'pending',
|
|
1822
|
+
})
|
|
1823
|
+
|
|
1824
|
+
// Try to transfer 10 USDC (exceeds 5 USDC limit)
|
|
1825
|
+
const transferData = AbiFunction.encodeData(transfer, [
|
|
1826
|
+
'0x0000000000000000000000000000000000000001',
|
|
1827
|
+
Value.from('10', 6),
|
|
1828
|
+
])
|
|
1829
|
+
|
|
1830
|
+
const transaction = TxEnvelopeTempo.from({
|
|
1831
|
+
calls: [{ to: token, data: transferData }],
|
|
1832
|
+
chainId,
|
|
1833
|
+
feeToken: token,
|
|
1834
|
+
keyAuthorization: keyAuth_signed,
|
|
1835
|
+
nonce: BigInt(nonce),
|
|
1836
|
+
gas: 5_000_000n,
|
|
1837
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
1838
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
1839
|
+
})
|
|
1840
|
+
|
|
1841
|
+
const signature = Secp256k1.sign({
|
|
1842
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction, {
|
|
1843
|
+
from: root.address,
|
|
1844
|
+
}),
|
|
1845
|
+
privateKey,
|
|
1846
|
+
})
|
|
1847
|
+
|
|
1848
|
+
const serialized_signed = TxEnvelopeTempo.serialize(transaction, {
|
|
1849
|
+
signature: SignatureEnvelope.from({
|
|
1850
|
+
userAddress: root.address,
|
|
1851
|
+
inner: SignatureEnvelope.from(signature),
|
|
1852
|
+
type: 'keychain',
|
|
1853
|
+
}),
|
|
1854
|
+
})
|
|
1855
|
+
|
|
1856
|
+
const receipt = (await client
|
|
1857
|
+
.request({
|
|
1858
|
+
method: 'eth_sendRawTransactionSync',
|
|
1859
|
+
params: [serialized_signed],
|
|
1860
|
+
})
|
|
1861
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
1862
|
+
expect(receipt).toBeDefined()
|
|
1863
|
+
expect(receipt.status).toBe('reverted')
|
|
1864
|
+
},
|
|
1865
|
+
)
|
|
1866
|
+
|
|
1867
|
+
test.runIf(nodeEnv === 'localnet')(
|
|
1868
|
+
'behavior: periodic spending limit resets after period',
|
|
1869
|
+
async () => {
|
|
1870
|
+
const accessPrivateKey = Secp256k1.randomPrivateKey()
|
|
1871
|
+
const accessAddress = Address.fromPublicKey(
|
|
1872
|
+
Secp256k1.getPublicKey({ privateKey: accessPrivateKey }),
|
|
1873
|
+
)
|
|
1874
|
+
const token = '0x20c0000000000000000000000000000000000001'
|
|
1875
|
+
const transfer = AbiFunction.from(
|
|
1876
|
+
'function transfer(address to, uint256 amount)',
|
|
1877
|
+
)
|
|
1878
|
+
const recipient = '0x0000000000000000000000000000000000000001'
|
|
1879
|
+
|
|
1880
|
+
// Key with a 5 USDC limit that resets every 5 seconds
|
|
1881
|
+
const keyAuth = KeyAuthorization.from({
|
|
1882
|
+
address: accessAddress,
|
|
1883
|
+
chainId: BigInt(chainId),
|
|
1884
|
+
type: 'secp256k1',
|
|
1885
|
+
limits: [
|
|
1886
|
+
{
|
|
1887
|
+
token,
|
|
1888
|
+
limit: Value.from('5', 6),
|
|
1889
|
+
period: Period.seconds(5),
|
|
1890
|
+
},
|
|
1891
|
+
],
|
|
1892
|
+
})
|
|
1893
|
+
|
|
1894
|
+
const keyAuth_signed = KeyAuthorization.from(keyAuth, {
|
|
1895
|
+
signature: SignatureEnvelope.from(
|
|
1896
|
+
Secp256k1.sign({
|
|
1897
|
+
payload: KeyAuthorization.getSignPayload(keyAuth),
|
|
1898
|
+
privateKey: root.privateKey,
|
|
1899
|
+
}),
|
|
1900
|
+
),
|
|
1901
|
+
})
|
|
1902
|
+
|
|
1903
|
+
// 1. Provision key + transfer 4 USDC
|
|
1904
|
+
{
|
|
1905
|
+
const nonce = await getTransactionCount(client, {
|
|
1906
|
+
address: root.address,
|
|
1907
|
+
blockTag: 'pending',
|
|
1908
|
+
})
|
|
1909
|
+
|
|
1910
|
+
const transferData = AbiFunction.encodeData(transfer, [
|
|
1911
|
+
recipient,
|
|
1912
|
+
Value.from('4', 6),
|
|
1913
|
+
])
|
|
1914
|
+
|
|
1915
|
+
const transaction = TxEnvelopeTempo.from({
|
|
1916
|
+
calls: [{ to: token, data: transferData }],
|
|
1917
|
+
chainId,
|
|
1918
|
+
feeToken: token,
|
|
1919
|
+
keyAuthorization: keyAuth_signed,
|
|
1920
|
+
nonce: BigInt(nonce),
|
|
1921
|
+
gas: 5_000_000n,
|
|
1922
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
1923
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
1924
|
+
})
|
|
1925
|
+
|
|
1926
|
+
const signature = Secp256k1.sign({
|
|
1927
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction, {
|
|
1928
|
+
from: root.address,
|
|
1929
|
+
}),
|
|
1930
|
+
privateKey: accessPrivateKey,
|
|
1931
|
+
})
|
|
1932
|
+
|
|
1933
|
+
const serialized = TxEnvelopeTempo.serialize(transaction, {
|
|
1934
|
+
signature: SignatureEnvelope.from({
|
|
1935
|
+
userAddress: root.address,
|
|
1936
|
+
inner: SignatureEnvelope.from(signature),
|
|
1937
|
+
type: 'keychain',
|
|
1938
|
+
}),
|
|
1939
|
+
})
|
|
1940
|
+
|
|
1941
|
+
const receipt = (await client
|
|
1942
|
+
.request({
|
|
1943
|
+
method: 'eth_sendRawTransactionSync',
|
|
1944
|
+
params: [serialized],
|
|
1945
|
+
})
|
|
1946
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
1947
|
+
expect(receipt.status).toBe('success')
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// 2. Immediately try another 4 USDC transfer (should revert — limit exhausted)
|
|
1951
|
+
{
|
|
1952
|
+
const nonce = await getTransactionCount(client, {
|
|
1953
|
+
address: root.address,
|
|
1954
|
+
blockTag: 'pending',
|
|
1955
|
+
})
|
|
1956
|
+
|
|
1957
|
+
const transferData = AbiFunction.encodeData(transfer, [
|
|
1958
|
+
recipient,
|
|
1959
|
+
Value.from('4', 6),
|
|
1960
|
+
])
|
|
1961
|
+
|
|
1962
|
+
const transaction = TxEnvelopeTempo.from({
|
|
1963
|
+
calls: [{ to: token, data: transferData }],
|
|
1964
|
+
chainId,
|
|
1965
|
+
feeToken: token,
|
|
1966
|
+
nonce: BigInt(nonce),
|
|
1967
|
+
gas: 5_000_000n,
|
|
1968
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
1969
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
1970
|
+
})
|
|
1971
|
+
|
|
1972
|
+
const signature = Secp256k1.sign({
|
|
1973
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction, {
|
|
1974
|
+
from: root.address,
|
|
1975
|
+
}),
|
|
1976
|
+
privateKey: accessPrivateKey,
|
|
1977
|
+
})
|
|
1978
|
+
|
|
1979
|
+
const serialized = TxEnvelopeTempo.serialize(transaction, {
|
|
1980
|
+
signature: SignatureEnvelope.from({
|
|
1981
|
+
userAddress: root.address,
|
|
1982
|
+
inner: SignatureEnvelope.from(signature),
|
|
1983
|
+
type: 'keychain',
|
|
1984
|
+
}),
|
|
1985
|
+
})
|
|
1986
|
+
|
|
1987
|
+
const receipt = (await client
|
|
1988
|
+
.request({
|
|
1989
|
+
method: 'eth_sendRawTransactionSync',
|
|
1990
|
+
params: [serialized],
|
|
1991
|
+
})
|
|
1992
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
1993
|
+
expect(receipt.status).toBe('reverted')
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
// 3. Wait for period to reset
|
|
1997
|
+
await new Promise((resolve) => setTimeout(resolve, 6000))
|
|
1998
|
+
|
|
1999
|
+
// 4. Transfer 4 USDC again (should succeed — period reset)
|
|
2000
|
+
{
|
|
2001
|
+
const nonce = await getTransactionCount(client, {
|
|
2002
|
+
address: root.address,
|
|
2003
|
+
blockTag: 'pending',
|
|
2004
|
+
})
|
|
2005
|
+
|
|
2006
|
+
const transferData = AbiFunction.encodeData(transfer, [
|
|
2007
|
+
recipient,
|
|
2008
|
+
Value.from('4', 6),
|
|
2009
|
+
])
|
|
2010
|
+
|
|
2011
|
+
const transaction = TxEnvelopeTempo.from({
|
|
2012
|
+
calls: [{ to: token, data: transferData }],
|
|
2013
|
+
chainId,
|
|
2014
|
+
feeToken: token,
|
|
2015
|
+
nonce: BigInt(nonce),
|
|
2016
|
+
gas: 5_000_000n,
|
|
2017
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
2018
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
2019
|
+
})
|
|
2020
|
+
|
|
2021
|
+
const signature = Secp256k1.sign({
|
|
2022
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction, {
|
|
2023
|
+
from: root.address,
|
|
2024
|
+
}),
|
|
2025
|
+
privateKey: accessPrivateKey,
|
|
2026
|
+
})
|
|
2027
|
+
|
|
2028
|
+
const serialized = TxEnvelopeTempo.serialize(transaction, {
|
|
2029
|
+
signature: SignatureEnvelope.from({
|
|
2030
|
+
userAddress: root.address,
|
|
2031
|
+
inner: SignatureEnvelope.from(signature),
|
|
2032
|
+
type: 'keychain',
|
|
2033
|
+
}),
|
|
2034
|
+
})
|
|
2035
|
+
|
|
2036
|
+
const receipt = (await client
|
|
2037
|
+
.request({
|
|
2038
|
+
method: 'eth_sendRawTransactionSync',
|
|
2039
|
+
params: [serialized],
|
|
2040
|
+
})
|
|
2041
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
2042
|
+
expect(receipt.status).toBe('success')
|
|
2043
|
+
}
|
|
2044
|
+
},
|
|
2045
|
+
)
|
|
2046
|
+
|
|
2047
|
+
// TODO: remove skipIf when testnet has T3
|
|
2048
|
+
test.skipIf(nodeEnv === 'testnet')(
|
|
2049
|
+
'behavior: access key with call scopes (transfer)',
|
|
2050
|
+
async () => {
|
|
2051
|
+
const accessPrivateKey = Secp256k1.randomPrivateKey()
|
|
2052
|
+
const accessAddress = Address.fromPublicKey(
|
|
2053
|
+
Secp256k1.getPublicKey({ privateKey: accessPrivateKey }),
|
|
2054
|
+
)
|
|
2055
|
+
const recipient = '0x0000000000000000000000000000000000000001'
|
|
2056
|
+
const token = '0x20c0000000000000000000000000000000000001'
|
|
2057
|
+
const transfer = AbiFunction.from(
|
|
2058
|
+
'function transfer(address to, uint256 amount)',
|
|
2059
|
+
)
|
|
2060
|
+
const transferData = AbiFunction.encodeData(transfer, [
|
|
2061
|
+
recipient,
|
|
2062
|
+
Value.from('1', 6),
|
|
2063
|
+
])
|
|
2064
|
+
|
|
2065
|
+
// Scope key: only transfer() on token contract, with sufficient spending limit
|
|
2066
|
+
const keyAuth = KeyAuthorization.from({
|
|
2067
|
+
address: accessAddress,
|
|
2068
|
+
chainId: BigInt(chainId),
|
|
2069
|
+
type: 'secp256k1',
|
|
2070
|
+
limits: [{ token, limit: Value.from('10000', 6) }],
|
|
2071
|
+
scopes: [
|
|
2072
|
+
{
|
|
2073
|
+
contractAddress: token,
|
|
2074
|
+
selector: AbiFunction.getSelector(transfer),
|
|
2075
|
+
},
|
|
2076
|
+
],
|
|
2077
|
+
})
|
|
2078
|
+
|
|
2079
|
+
const keyAuth_signed = KeyAuthorization.from(keyAuth, {
|
|
2080
|
+
signature: SignatureEnvelope.from(
|
|
2081
|
+
Secp256k1.sign({
|
|
2082
|
+
payload: KeyAuthorization.getSignPayload(keyAuth),
|
|
2083
|
+
privateKey: root.privateKey,
|
|
2084
|
+
}),
|
|
2085
|
+
),
|
|
2086
|
+
})
|
|
2087
|
+
|
|
2088
|
+
const nonce = await getTransactionCount(client, {
|
|
2089
|
+
address: root.address,
|
|
2090
|
+
blockTag: 'pending',
|
|
2091
|
+
})
|
|
2092
|
+
|
|
2093
|
+
const transaction = TxEnvelopeTempo.from({
|
|
2094
|
+
calls: [{ to: token, data: transferData }],
|
|
2095
|
+
chainId,
|
|
2096
|
+
feeToken: token,
|
|
2097
|
+
keyAuthorization: keyAuth_signed,
|
|
2098
|
+
nonce: BigInt(nonce),
|
|
2099
|
+
gas: 5_000_000n,
|
|
2100
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
2101
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
2102
|
+
})
|
|
2103
|
+
|
|
2104
|
+
const signature = Secp256k1.sign({
|
|
2105
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction, {
|
|
2106
|
+
from: root.address,
|
|
2107
|
+
}),
|
|
2108
|
+
privateKey: accessPrivateKey,
|
|
2109
|
+
})
|
|
2110
|
+
|
|
2111
|
+
const serialized_signed = TxEnvelopeTempo.serialize(transaction, {
|
|
2112
|
+
signature: SignatureEnvelope.from({
|
|
2113
|
+
userAddress: root.address,
|
|
2114
|
+
inner: SignatureEnvelope.from(signature),
|
|
2115
|
+
type: 'keychain',
|
|
2116
|
+
}),
|
|
2117
|
+
})
|
|
2118
|
+
|
|
2119
|
+
const receipt = (await client
|
|
2120
|
+
.request({
|
|
2121
|
+
method: 'eth_sendRawTransactionSync',
|
|
2122
|
+
params: [serialized_signed],
|
|
2123
|
+
})
|
|
2124
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
2125
|
+
expect(receipt).toBeDefined()
|
|
2126
|
+
expect(receipt.status).toBe('success')
|
|
2127
|
+
|
|
2128
|
+
{
|
|
2129
|
+
const response = await client
|
|
2130
|
+
.request({
|
|
2131
|
+
method: 'eth_getTransactionByHash',
|
|
2132
|
+
params: [receipt.transactionHash],
|
|
2133
|
+
})
|
|
2134
|
+
.then((tx) => Transaction.fromRpc(tx as any))
|
|
2135
|
+
if (!response) throw new Error()
|
|
2136
|
+
|
|
2137
|
+
expect(response.from).toBe(root.address)
|
|
2138
|
+
expect(response.keyAuthorization).toBeDefined()
|
|
2139
|
+
expect(response.keyAuthorization?.scopes?.[0]?.contractAddress).toBe(
|
|
2140
|
+
token,
|
|
2141
|
+
)
|
|
2142
|
+
expect(response.keyAuthorization?.scopes?.[0]?.selector).toBe(
|
|
2143
|
+
'0xa9059cbb',
|
|
2144
|
+
)
|
|
2145
|
+
}
|
|
2146
|
+
},
|
|
2147
|
+
)
|
|
2148
|
+
|
|
2149
|
+
// TODO: remove skipIf when testnet has T3
|
|
2150
|
+
test.skipIf(nodeEnv === 'testnet')(
|
|
2151
|
+
'behavior: access key with call scopes + recipient allowlist (transfer)',
|
|
2152
|
+
async () => {
|
|
2153
|
+
const accessPrivateKey = Secp256k1.randomPrivateKey()
|
|
2154
|
+
const accessAddress = Address.fromPublicKey(
|
|
2155
|
+
Secp256k1.getPublicKey({ privateKey: accessPrivateKey }),
|
|
2156
|
+
)
|
|
2157
|
+
const recipient = '0x0000000000000000000000000000000000000001'
|
|
2158
|
+
const token = '0x20c0000000000000000000000000000000000001'
|
|
2159
|
+
const transfer = AbiFunction.from(
|
|
2160
|
+
'function transfer(address to, uint256 amount)',
|
|
2161
|
+
)
|
|
2162
|
+
const transferData = AbiFunction.encodeData(transfer, [
|
|
2163
|
+
recipient,
|
|
2164
|
+
Value.from('1', 6),
|
|
2165
|
+
])
|
|
2166
|
+
|
|
2167
|
+
// Scope key: transfer() on token, only to recipient, with sufficient spending limit
|
|
2168
|
+
const keyAuth = KeyAuthorization.from({
|
|
2169
|
+
address: accessAddress,
|
|
2170
|
+
chainId: BigInt(chainId),
|
|
2171
|
+
type: 'secp256k1',
|
|
2172
|
+
limits: [{ token, limit: Value.from('10000', 6) }],
|
|
2173
|
+
scopes: [
|
|
2174
|
+
{
|
|
2175
|
+
contractAddress: token,
|
|
2176
|
+
selector: AbiFunction.getSelector(transfer),
|
|
2177
|
+
recipients: [recipient],
|
|
2178
|
+
},
|
|
2179
|
+
],
|
|
2180
|
+
})
|
|
2181
|
+
|
|
2182
|
+
const keyAuth_signed = KeyAuthorization.from(keyAuth, {
|
|
2183
|
+
signature: SignatureEnvelope.from(
|
|
2184
|
+
Secp256k1.sign({
|
|
2185
|
+
payload: KeyAuthorization.getSignPayload(keyAuth),
|
|
2186
|
+
privateKey: root.privateKey,
|
|
2187
|
+
}),
|
|
2188
|
+
),
|
|
2189
|
+
})
|
|
2190
|
+
|
|
2191
|
+
const nonce = await getTransactionCount(client, {
|
|
2192
|
+
address: root.address,
|
|
2193
|
+
blockTag: 'pending',
|
|
2194
|
+
})
|
|
2195
|
+
|
|
2196
|
+
const transaction = TxEnvelopeTempo.from({
|
|
2197
|
+
calls: [{ to: token, data: transferData }],
|
|
2198
|
+
chainId,
|
|
2199
|
+
feeToken: token,
|
|
2200
|
+
keyAuthorization: keyAuth_signed,
|
|
2201
|
+
nonce: BigInt(nonce),
|
|
2202
|
+
gas: 5_000_000n,
|
|
2203
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
2204
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
2205
|
+
})
|
|
2206
|
+
|
|
2207
|
+
const signature = Secp256k1.sign({
|
|
2208
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction, {
|
|
2209
|
+
from: root.address,
|
|
2210
|
+
}),
|
|
2211
|
+
privateKey: accessPrivateKey,
|
|
2212
|
+
})
|
|
2213
|
+
|
|
2214
|
+
const serialized_signed = TxEnvelopeTempo.serialize(transaction, {
|
|
2215
|
+
signature: SignatureEnvelope.from({
|
|
2216
|
+
userAddress: root.address,
|
|
2217
|
+
inner: SignatureEnvelope.from(signature),
|
|
2218
|
+
type: 'keychain',
|
|
2219
|
+
}),
|
|
2220
|
+
})
|
|
2221
|
+
|
|
2222
|
+
const receipt = (await client
|
|
2223
|
+
.request({
|
|
2224
|
+
method: 'eth_sendRawTransactionSync',
|
|
2225
|
+
params: [serialized_signed],
|
|
2226
|
+
})
|
|
2227
|
+
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
2228
|
+
expect(receipt).toBeDefined()
|
|
2229
|
+
expect(receipt.status).toBe('success')
|
|
2230
|
+
|
|
2231
|
+
{
|
|
2232
|
+
const response = await client
|
|
2233
|
+
.request({
|
|
2234
|
+
method: 'eth_getTransactionByHash',
|
|
2235
|
+
params: [receipt.transactionHash],
|
|
2236
|
+
})
|
|
2237
|
+
.then((tx) => Transaction.fromRpc(tx as any))
|
|
2238
|
+
if (!response) throw new Error()
|
|
2239
|
+
|
|
2240
|
+
expect(response.from).toBe(root.address)
|
|
2241
|
+
expect(response.keyAuthorization).toBeDefined()
|
|
2242
|
+
expect(response.keyAuthorization?.scopes?.[0]?.selector).toBe(
|
|
2243
|
+
'0xa9059cbb',
|
|
2244
|
+
)
|
|
2245
|
+
expect(response.keyAuthorization?.scopes?.[0]?.recipients).toEqual([
|
|
2246
|
+
recipient,
|
|
2247
|
+
])
|
|
2248
|
+
}
|
|
2249
|
+
},
|
|
2250
|
+
)
|
|
2251
|
+
|
|
2252
|
+
// TODO: remove skipIf when testnet has T3
|
|
2253
|
+
test.skipIf(nodeEnv === 'testnet')(
|
|
2254
|
+
'behavior: rejects transfer to wrong contract (outside scope)',
|
|
2255
|
+
async () => {
|
|
2256
|
+
const accessPrivateKey = Secp256k1.randomPrivateKey()
|
|
2257
|
+
const accessAddress = Address.fromPublicKey(
|
|
2258
|
+
Secp256k1.getPublicKey({ privateKey: accessPrivateKey }),
|
|
2259
|
+
)
|
|
2260
|
+
const token1 = '0x20c0000000000000000000000000000000000001'
|
|
2261
|
+
const token2 = '0x20c0000000000000000000000000000000000002'
|
|
2262
|
+
const transfer = AbiFunction.from(
|
|
2263
|
+
'function transfer(address to, uint256 amount)',
|
|
2264
|
+
)
|
|
2265
|
+
const transferData = AbiFunction.encodeData(transfer, [
|
|
2266
|
+
'0x0000000000000000000000000000000000000001',
|
|
2267
|
+
Value.from('1', 6),
|
|
2268
|
+
])
|
|
2269
|
+
|
|
2270
|
+
// Scope key to only token1
|
|
2271
|
+
const keyAuth = KeyAuthorization.from({
|
|
2272
|
+
address: accessAddress,
|
|
2273
|
+
chainId: BigInt(chainId),
|
|
2274
|
+
type: 'secp256k1',
|
|
2275
|
+
scopes: [
|
|
2276
|
+
{
|
|
2277
|
+
contractAddress: token1,
|
|
2278
|
+
selector: AbiFunction.getSelector(transfer),
|
|
2279
|
+
},
|
|
2280
|
+
],
|
|
2281
|
+
})
|
|
2282
|
+
|
|
2283
|
+
const keyAuth_signed = KeyAuthorization.from(keyAuth, {
|
|
2284
|
+
signature: SignatureEnvelope.from(
|
|
2285
|
+
Secp256k1.sign({
|
|
2286
|
+
payload: KeyAuthorization.getSignPayload(keyAuth),
|
|
2287
|
+
privateKey: root.privateKey,
|
|
2288
|
+
}),
|
|
2289
|
+
),
|
|
2290
|
+
})
|
|
2291
|
+
|
|
2292
|
+
const nonce = await getTransactionCount(client, {
|
|
2293
|
+
address: root.address,
|
|
2294
|
+
blockTag: 'pending',
|
|
2295
|
+
})
|
|
2296
|
+
|
|
2297
|
+
// Call transfer on token2 (not scoped) — should be rejected
|
|
2298
|
+
const transaction = TxEnvelopeTempo.from({
|
|
2299
|
+
calls: [{ to: token2, data: transferData }],
|
|
2300
|
+
chainId,
|
|
2301
|
+
feeToken: token1,
|
|
2302
|
+
keyAuthorization: keyAuth_signed,
|
|
2303
|
+
nonce: BigInt(nonce),
|
|
2304
|
+
gas: 5_000_000n,
|
|
2305
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
2306
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
2307
|
+
})
|
|
2308
|
+
|
|
2309
|
+
const signature = Secp256k1.sign({
|
|
2310
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction, {
|
|
2311
|
+
from: root.address,
|
|
2312
|
+
}),
|
|
2313
|
+
privateKey: accessPrivateKey,
|
|
2314
|
+
})
|
|
2315
|
+
|
|
2316
|
+
const serialized_signed = TxEnvelopeTempo.serialize(transaction, {
|
|
2317
|
+
signature: SignatureEnvelope.from({
|
|
2318
|
+
userAddress: root.address,
|
|
2319
|
+
inner: SignatureEnvelope.from(signature),
|
|
2320
|
+
type: 'keychain',
|
|
2321
|
+
}),
|
|
2322
|
+
})
|
|
2323
|
+
|
|
2324
|
+
await expect(
|
|
2325
|
+
client.request({
|
|
2326
|
+
method: 'eth_sendRawTransactionSync',
|
|
2327
|
+
params: [serialized_signed],
|
|
2328
|
+
}),
|
|
2329
|
+
).rejects.toThrow()
|
|
2330
|
+
},
|
|
2331
|
+
)
|
|
2332
|
+
|
|
2333
|
+
// TODO: remove skipIf when testnet has T3
|
|
2334
|
+
test.skipIf(nodeEnv === 'testnet')(
|
|
2335
|
+
'behavior: rejects approve when only transfer is scoped',
|
|
2336
|
+
async () => {
|
|
2337
|
+
const accessPrivateKey = Secp256k1.randomPrivateKey()
|
|
2338
|
+
const accessAddress = Address.fromPublicKey(
|
|
2339
|
+
Secp256k1.getPublicKey({ privateKey: accessPrivateKey }),
|
|
2340
|
+
)
|
|
2341
|
+
const token = '0x20c0000000000000000000000000000000000001'
|
|
2342
|
+
const transfer = AbiFunction.from(
|
|
2343
|
+
'function transfer(address to, uint256 amount)',
|
|
2344
|
+
)
|
|
2345
|
+
const approve = AbiFunction.from(
|
|
2346
|
+
'function approve(address spender, uint256 amount)',
|
|
2347
|
+
)
|
|
2348
|
+
const approveData = AbiFunction.encodeData(approve, [
|
|
2349
|
+
'0x0000000000000000000000000000000000000001',
|
|
2350
|
+
Value.from('1', 6),
|
|
2351
|
+
])
|
|
2352
|
+
|
|
2353
|
+
// Scope key to only transfer()
|
|
2354
|
+
const keyAuth = KeyAuthorization.from({
|
|
2355
|
+
address: accessAddress,
|
|
2356
|
+
chainId: BigInt(chainId),
|
|
2357
|
+
type: 'secp256k1',
|
|
2358
|
+
scopes: [
|
|
2359
|
+
{
|
|
2360
|
+
contractAddress: token,
|
|
2361
|
+
selector: AbiFunction.getSelector(transfer),
|
|
2362
|
+
},
|
|
2363
|
+
],
|
|
2364
|
+
})
|
|
2365
|
+
|
|
2366
|
+
const keyAuth_signed = KeyAuthorization.from(keyAuth, {
|
|
2367
|
+
signature: SignatureEnvelope.from(
|
|
2368
|
+
Secp256k1.sign({
|
|
2369
|
+
payload: KeyAuthorization.getSignPayload(keyAuth),
|
|
2370
|
+
privateKey: root.privateKey,
|
|
2371
|
+
}),
|
|
2372
|
+
),
|
|
2373
|
+
})
|
|
2374
|
+
|
|
2375
|
+
const nonce = await getTransactionCount(client, {
|
|
2376
|
+
address: root.address,
|
|
2377
|
+
blockTag: 'pending',
|
|
2378
|
+
})
|
|
2379
|
+
|
|
2380
|
+
// Call approve() instead — should be rejected
|
|
2381
|
+
const transaction = TxEnvelopeTempo.from({
|
|
2382
|
+
calls: [{ to: token, data: approveData }],
|
|
2383
|
+
chainId,
|
|
2384
|
+
feeToken: token,
|
|
2385
|
+
keyAuthorization: keyAuth_signed,
|
|
2386
|
+
nonce: BigInt(nonce),
|
|
2387
|
+
gas: 5_000_000n,
|
|
2388
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
2389
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
2390
|
+
})
|
|
2391
|
+
|
|
2392
|
+
const signature = Secp256k1.sign({
|
|
2393
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction, {
|
|
2394
|
+
from: root.address,
|
|
2395
|
+
}),
|
|
2396
|
+
privateKey: accessPrivateKey,
|
|
2397
|
+
})
|
|
2398
|
+
|
|
2399
|
+
const serialized_signed = TxEnvelopeTempo.serialize(transaction, {
|
|
2400
|
+
signature: SignatureEnvelope.from({
|
|
2401
|
+
userAddress: root.address,
|
|
2402
|
+
inner: SignatureEnvelope.from(signature),
|
|
2403
|
+
type: 'keychain',
|
|
2404
|
+
}),
|
|
2405
|
+
})
|
|
2406
|
+
|
|
2407
|
+
await expect(
|
|
2408
|
+
client.request({
|
|
2409
|
+
method: 'eth_sendRawTransactionSync',
|
|
2410
|
+
params: [serialized_signed],
|
|
2411
|
+
}),
|
|
2412
|
+
).rejects.toThrow()
|
|
2413
|
+
},
|
|
2414
|
+
)
|
|
2415
|
+
|
|
2416
|
+
// TODO: remove skipIf when testnet has T3
|
|
2417
|
+
test.skipIf(nodeEnv === 'testnet')(
|
|
2418
|
+
'behavior: rejects transfer to wrong recipient',
|
|
2419
|
+
async () => {
|
|
2420
|
+
const accessPrivateKey = Secp256k1.randomPrivateKey()
|
|
2421
|
+
const accessAddress = Address.fromPublicKey(
|
|
2422
|
+
Secp256k1.getPublicKey({ privateKey: accessPrivateKey }),
|
|
2423
|
+
)
|
|
2424
|
+
const token = '0x20c0000000000000000000000000000000000001'
|
|
2425
|
+
const allowedRecipient = '0x0000000000000000000000000000000000000001'
|
|
2426
|
+
const wrongRecipient = '0x0000000000000000000000000000000000000002'
|
|
2427
|
+
const transfer = AbiFunction.from(
|
|
2428
|
+
'function transfer(address to, uint256 amount)',
|
|
2429
|
+
)
|
|
2430
|
+
const transferData = AbiFunction.encodeData(transfer, [
|
|
2431
|
+
wrongRecipient,
|
|
2432
|
+
Value.from('1', 6),
|
|
2433
|
+
])
|
|
2434
|
+
|
|
2435
|
+
// Scope key: transfer only to allowedRecipient
|
|
2436
|
+
const keyAuth = KeyAuthorization.from({
|
|
2437
|
+
address: accessAddress,
|
|
2438
|
+
chainId: BigInt(chainId),
|
|
2439
|
+
type: 'secp256k1',
|
|
2440
|
+
scopes: [
|
|
2441
|
+
{
|
|
2442
|
+
contractAddress: token,
|
|
2443
|
+
selector: AbiFunction.getSelector(transfer),
|
|
2444
|
+
recipients: [allowedRecipient],
|
|
2445
|
+
},
|
|
2446
|
+
],
|
|
2447
|
+
})
|
|
2448
|
+
|
|
2449
|
+
const keyAuth_signed = KeyAuthorization.from(keyAuth, {
|
|
2450
|
+
signature: SignatureEnvelope.from(
|
|
2451
|
+
Secp256k1.sign({
|
|
2452
|
+
payload: KeyAuthorization.getSignPayload(keyAuth),
|
|
2453
|
+
privateKey: root.privateKey,
|
|
2454
|
+
}),
|
|
2455
|
+
),
|
|
2456
|
+
})
|
|
2457
|
+
|
|
2458
|
+
const nonce = await getTransactionCount(client, {
|
|
2459
|
+
address: root.address,
|
|
2460
|
+
blockTag: 'pending',
|
|
2461
|
+
})
|
|
2462
|
+
|
|
2463
|
+
// transfer to wrongRecipient — should be rejected
|
|
2464
|
+
const transaction = TxEnvelopeTempo.from({
|
|
2465
|
+
calls: [{ to: token, data: transferData }],
|
|
2466
|
+
chainId,
|
|
2467
|
+
feeToken: token,
|
|
2468
|
+
keyAuthorization: keyAuth_signed,
|
|
2469
|
+
nonce: BigInt(nonce),
|
|
2470
|
+
gas: 5_000_000n,
|
|
2471
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
2472
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
2473
|
+
})
|
|
2474
|
+
|
|
2475
|
+
const signature = Secp256k1.sign({
|
|
2476
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction, {
|
|
2477
|
+
from: root.address,
|
|
2478
|
+
}),
|
|
2479
|
+
privateKey: accessPrivateKey,
|
|
2480
|
+
})
|
|
2481
|
+
|
|
2482
|
+
const serialized_signed = TxEnvelopeTempo.serialize(transaction, {
|
|
2483
|
+
signature: SignatureEnvelope.from({
|
|
2484
|
+
userAddress: root.address,
|
|
2485
|
+
inner: SignatureEnvelope.from(signature),
|
|
2486
|
+
type: 'keychain',
|
|
2487
|
+
}),
|
|
2488
|
+
})
|
|
2489
|
+
|
|
2490
|
+
await expect(
|
|
2491
|
+
client.request({
|
|
2492
|
+
method: 'eth_sendRawTransactionSync',
|
|
2493
|
+
params: [serialized_signed],
|
|
2494
|
+
}),
|
|
2495
|
+
).rejects.toThrow()
|
|
2496
|
+
},
|
|
2497
|
+
)
|
|
2498
|
+
|
|
2499
|
+
// TODO: remove skipIf when testnet has T3
|
|
2500
|
+
test.skipIf(nodeEnv === 'testnet')(
|
|
2501
|
+
'behavior: rejects any call when scopes = [] (empty)',
|
|
2502
|
+
async () => {
|
|
2503
|
+
const accessPrivateKey = Secp256k1.randomPrivateKey()
|
|
2504
|
+
const accessAddress = Address.fromPublicKey(
|
|
2505
|
+
Secp256k1.getPublicKey({ privateKey: accessPrivateKey }),
|
|
2506
|
+
)
|
|
2507
|
+
const token = '0x20c0000000000000000000000000000000000001'
|
|
2508
|
+
const transfer = AbiFunction.from(
|
|
2509
|
+
'function transfer(address to, uint256 amount)',
|
|
2510
|
+
)
|
|
2511
|
+
const transferData = AbiFunction.encodeData(transfer, [
|
|
2512
|
+
'0x0000000000000000000000000000000000000001',
|
|
2513
|
+
Value.from('1', 6),
|
|
2514
|
+
])
|
|
2515
|
+
|
|
2516
|
+
// scopes = [] → scoped mode with NO calls allowed
|
|
2517
|
+
const keyAuth = KeyAuthorization.from({
|
|
2518
|
+
address: accessAddress,
|
|
2519
|
+
chainId: BigInt(chainId),
|
|
2520
|
+
type: 'secp256k1',
|
|
2521
|
+
scopes: [],
|
|
2522
|
+
})
|
|
2523
|
+
|
|
2524
|
+
const keyAuth_signed = KeyAuthorization.from(keyAuth, {
|
|
2525
|
+
signature: SignatureEnvelope.from(
|
|
2526
|
+
Secp256k1.sign({
|
|
2527
|
+
payload: KeyAuthorization.getSignPayload(keyAuth),
|
|
2528
|
+
privateKey: root.privateKey,
|
|
2529
|
+
}),
|
|
2530
|
+
),
|
|
2531
|
+
})
|
|
2532
|
+
|
|
2533
|
+
const nonce = await getTransactionCount(client, {
|
|
2534
|
+
address: root.address,
|
|
2535
|
+
blockTag: 'pending',
|
|
2536
|
+
})
|
|
2537
|
+
|
|
2538
|
+
const transaction = TxEnvelopeTempo.from({
|
|
2539
|
+
calls: [{ to: token, data: transferData }],
|
|
2540
|
+
chainId,
|
|
2541
|
+
feeToken: token,
|
|
2542
|
+
keyAuthorization: keyAuth_signed,
|
|
2543
|
+
nonce: BigInt(nonce),
|
|
2544
|
+
gas: 5_000_000n,
|
|
2545
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
2546
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
2547
|
+
})
|
|
2548
|
+
|
|
2549
|
+
const signature = Secp256k1.sign({
|
|
2550
|
+
payload: TxEnvelopeTempo.getSignPayload(transaction, {
|
|
2551
|
+
from: root.address,
|
|
2552
|
+
}),
|
|
2553
|
+
privateKey: accessPrivateKey,
|
|
2554
|
+
})
|
|
2555
|
+
|
|
2556
|
+
const serialized_signed = TxEnvelopeTempo.serialize(transaction, {
|
|
2557
|
+
signature: SignatureEnvelope.from({
|
|
2558
|
+
userAddress: root.address,
|
|
2559
|
+
inner: SignatureEnvelope.from(signature),
|
|
2560
|
+
type: 'keychain',
|
|
2561
|
+
}),
|
|
2562
|
+
})
|
|
2563
|
+
|
|
2564
|
+
await expect(
|
|
2565
|
+
client.request({
|
|
2566
|
+
method: 'eth_sendRawTransactionSync',
|
|
2567
|
+
params: [serialized_signed],
|
|
2568
|
+
}),
|
|
2569
|
+
).rejects.toThrow()
|
|
2570
|
+
},
|
|
2571
|
+
)
|
|
1683
2572
|
})
|