Skip to content

Commit

Permalink
Initial support for chained Tx in SingerProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
polarker committed Oct 7, 2024
1 parent 90ac6c5 commit 975bb16
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 81 deletions.
2 changes: 2 additions & 0 deletions packages/web3-test/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
*/

import { NodeProvider, web3 } from '@alephium/web3'
import { PrivateKeyWallet } from '@alephium/web3-wallet'

export const testPrivateKeys = [
'a642942e67258589cd2b1822c631506632db5a12aabcf413604e785300d762a5',
Expand All @@ -31,6 +32,7 @@ export const testWalletName = 'alephium-web3-test-only-wallet'
export const testAddress = '1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH'
export const testPrivateKey = testPrivateKeys[0]
export const testPassword = 'alph'
export const testPrivateKeyWallet = new PrivateKeyWallet({ privateKey: testPrivateKey })

export async function tryGetDevnetNodeProvider(): Promise<NodeProvider> {
const currentNodeProvider = (() => {
Expand Down
2 changes: 1 addition & 1 deletion packages/web3-test/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.

import { disableContractDebugMessage } from '@alephium/web3'

export { testMnemonic, testWalletName, testAddress, testPrivateKey, testPassword } from './const'
export { testMnemonic, testWalletName, testAddress, testPrivateKey, testPrivateKeyWallet, testPassword } from './const'
export { mintToken } from './token'
export * from './test-wallet'

Expand Down
5 changes: 2 additions & 3 deletions packages/web3-test/src/test-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
testAddress,
testMnemonic,
testPassword,
testPrivateKey,
testPrivateKeyWallet,
testWalletName,
tryGetDevnetNodeProvider
} from './const'
Expand Down Expand Up @@ -94,11 +94,10 @@ export async function getSigner(alphAmount = ONE_ALPH * 100n, group = 0): Promis
if (availableBalance < alphAmount) {
throw new Error('Not enough balance, please restart the devnet')
}
const rootWallet = new PrivateKeyWallet({ privateKey: testPrivateKey })
const wallet = PrivateKeyWallet.Random(group)
if (alphAmount > 0n) {
const destinations = [{ address: wallet.address, attoAlphAmount: alphAmount }]
await rootWallet.signAndSubmitTransferTx({ signerAddress: testAddress, destinations })
await testPrivateKeyWallet.signAndSubmitTransferTx({ signerAddress: testAddress, destinations })
}
return wallet
} catch (error) {
Expand Down
16 changes: 12 additions & 4 deletions packages/web3-wallet/src/privatekey-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,16 @@ export class PrivateKeyWallet extends SignerProviderSimple {
readonly publicKey: string
readonly address: string
readonly group: number
readonly nodeProvider: NodeProvider
readonly explorerProvider: ExplorerProvider | undefined
readonly _nodeProvider: NodeProvider | undefined
readonly _explorerProvider: ExplorerProvider | undefined

public get nodeProvider(): NodeProvider {
return this._nodeProvider ?? web3.getCurrentNodeProvider()
}

public get explorerProvider(): ExplorerProvider | undefined {
return this._explorerProvider ?? web3.getCurrentExplorerProvider()
}

protected unsafeGetSelectedAccount(): Promise<Account> {
return Promise.resolve(this.account)
Expand Down Expand Up @@ -66,8 +74,8 @@ export class PrivateKeyWallet extends SignerProviderSimple {
this.publicKey = publicKeyFromPrivateKey(privateKey, this.keyType)
this.address = addressFromPublicKey(this.publicKey, this.keyType)
this.group = groupOfAddress(this.address)
this.nodeProvider = nodeProvider ?? web3.getCurrentNodeProvider()
this.explorerProvider = explorerProvider ?? web3.getCurrentExplorerProvider()
this._nodeProvider = nodeProvider
this._explorerProvider = explorerProvider
}

static Random(targetGroup?: number, nodeProvider?: NodeProvider, keyType?: KeyType): PrivateKeyWallet {
Expand Down
130 changes: 57 additions & 73 deletions test/transaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
*/

import { SignTransferChainedTxParams, subscribeToTxStatus } from '../packages/web3'
import { node } from '../packages/web3'
import { node, ONE_ALPH } from '../packages/web3'
import { SubscribeOptions, sleep } from '../packages/web3'
import { web3 } from '../packages/web3'
import { TxStatus } from '../packages/web3'
import { PrivateKeyWallet } from '@alephium/web3-wallet'
import { ONE_ALPH } from '../packages/web3/src'
import { HDWallet, HDWalletAccount, PrivateKeyWallet, generateMnemonic } from '@alephium/web3-wallet'
import { Add, Sub, AddMain, Transact, Deposit } from '../artifacts/ts'
import { getSigner, mintToken } from '@alephium/web3-test'
import { getSigner, mintToken, testPrivateKeyWallet } from '../packages/web3-test'
import { TransactionBuilder } from '../packages/web3'
import { ALPH_TOKEN_ID } from '../packages/web3'

Expand Down Expand Up @@ -105,46 +104,49 @@ describe('transactions', function () {
expect((await addInstance.fetchState()).fields.result).toBe(3n)
})

async function prepareChainedTxTest(): Promise<[HDWallet, HDWalletAccount, HDWalletAccount, HDWalletAccount]> {
const mnemonic = generateMnemonic()
const wallet = new HDWallet({ mnemonic })
const address1 = wallet.deriveAndAddNewAccount(1)
const address2 = wallet.deriveAndAddNewAccount(2)
const address3 = wallet.deriveAndAddNewAccount(3)

await testPrivateKeyWallet.signAndSubmitTransferTx({
signerAddress: testPrivateKeyWallet.address,
destinations: [{ address: address1.address, attoAlphAmount: 100n * ONE_ALPH }]
})

return [wallet, address1, address2, address3]
}

it('should build chained transfer txs across groups', async () => {
const nodeProvider = web3.getCurrentNodeProvider()
const signer1 = await getSigner(100n * ONE_ALPH, 1)
const signer2 = await getSigner(0n, 2)
const signer3 = await getSigner(0n, 3)
const [wallet, address1, address2, address3] = await prepareChainedTxTest()

const transferFrom1To2: SignTransferChainedTxParams = {
signerAddress: signer1.address,
destinations: [{ address: signer2.address, attoAlphAmount: 10n * ONE_ALPH }],
signerAddress: address1.address,
destinations: [{ address: address2.address, attoAlphAmount: 10n * ONE_ALPH }],
type: 'Transfer'
}

const transferFrom2To3: SignTransferChainedTxParams = {
signerAddress: signer2.address,
destinations: [{ address: signer3.address, attoAlphAmount: 5n * ONE_ALPH }],
signerAddress: address2.address,
destinations: [{ address: address3.address, attoAlphAmount: 5n * ONE_ALPH }],
type: 'Transfer'
}

await expect(signer2.signAndSubmitTransferTx(transferFrom2To3)).rejects.toThrow(
await expect(wallet.signAndSubmitTransferTx(transferFrom2To3)).rejects.toThrow(
`[API Error] - Not enough balance: got 0, expected 5001000000000000000 - Status code: 500`
)

const [transferFrom1To2Result, transferFrom2To3Result] = await TransactionBuilder.from(nodeProvider).buildChainedTx(
[transferFrom1To2, transferFrom2To3],
[signer1.publicKey, signer2.publicKey]
)

const signedTransferFrom1To2 = await signer1.signAndSubmitUnsignedTx({
unsignedTx: transferFrom1To2Result.unsignedTx,
signerAddress: signer1.address
})

const signedTransferFrom2To3 = await signer2.signAndSubmitUnsignedTx({
unsignedTx: transferFrom2To3Result.unsignedTx,
signerAddress: signer2.address
})
const [signedTransferFrom1To2, signedTransferFrom2To3] = await wallet.signAndSubmitChainedTx([
transferFrom1To2,
transferFrom2To3
])

const signer1Balance = await nodeProvider.addresses.getAddressesAddressBalance(signer1.address)
const signer2Balance = await nodeProvider.addresses.getAddressesAddressBalance(signer2.address)
const signer3Balance = await nodeProvider.addresses.getAddressesAddressBalance(signer3.address)
const signer1Balance = await nodeProvider.addresses.getAddressesAddressBalance(address1.address)
const signer2Balance = await nodeProvider.addresses.getAddressesAddressBalance(address2.address)
const signer3Balance = await nodeProvider.addresses.getAddressesAddressBalance(address3.address)

const gasCostTransferFrom1To2 = BigInt(signedTransferFrom1To2.gasAmount) * BigInt(signedTransferFrom1To2.gasPrice)
const gasCostTransferFrom2To3 = BigInt(signedTransferFrom2To3.gasAmount) * BigInt(signedTransferFrom2To3.gasPrice)
Expand All @@ -159,41 +161,32 @@ describe('transactions', function () {

it('should build chain txs that deploy contract in another group', async () => {
const nodeProvider = web3.getCurrentNodeProvider()
const signer1 = await getSigner(100n * ONE_ALPH, 1)
const signer2 = await getSigner(0n, 2)
const [wallet, address1, address2] = await prepareChainedTxTest()

const deployTxParams = await Transact.contract.txParamsForDeployment(signer2, {
await wallet.setSelectedAccount(address2.address)
const deployTxParams = await Transact.contract.txParamsForDeployment(wallet, {
initialAttoAlphAmount: ONE_ALPH,
initialFields: { tokenId: ALPH_TOKEN_ID, totalALPH: 0n, totalTokens: 0n }
})
expect(deployTxParams.signerAddress).toBe(address2.address)

await expect(signer2.signAndSubmitDeployContractTx(deployTxParams)).rejects.toThrow(
await expect(wallet.signAndSubmitDeployContractTx(deployTxParams)).rejects.toThrow(
`[API Error] - Insufficient funds for gas`
)

const transferTxParams: SignTransferChainedTxParams = {
signerAddress: signer1.address,
destinations: [{ address: signer2.address, attoAlphAmount: 10n * ONE_ALPH }],
signerAddress: address1.address,
destinations: [{ address: address2.address, attoAlphAmount: 10n * ONE_ALPH }],
type: 'Transfer'
}

const [transferResult, deployResult] = await TransactionBuilder.from(nodeProvider).buildChainedTx(
[transferTxParams, { ...deployTxParams, type: 'DeployContract' }],
[signer1.publicKey, signer2.publicKey]
)
const [transferResult, deployResult] = await wallet.buildChainedTx([
transferTxParams,
{ ...deployTxParams, type: 'DeployContract' }
])

await signer1.signAndSubmitUnsignedTx({
unsignedTx: transferResult.unsignedTx,
signerAddress: signer1.address
})

await signer2.signAndSubmitUnsignedTx({
unsignedTx: deployResult.unsignedTx,
signerAddress: signer2.address
})

const signer1Balance = await nodeProvider.addresses.getAddressesAddressBalance(signer1.address)
const signer2Balance = await nodeProvider.addresses.getAddressesAddressBalance(signer2.address)
const signer1Balance = await nodeProvider.addresses.getAddressesAddressBalance(address1.address)
const signer2Balance = await nodeProvider.addresses.getAddressesAddressBalance(address2.address)

const transferTxGasCost = BigInt(transferResult.gasAmount) * BigInt(transferResult.gasPrice)
const deployTxGasCost = BigInt(deployResult.gasAmount) * BigInt(deployResult.gasPrice)
Expand All @@ -211,8 +204,7 @@ describe('transactions', function () {

it('should build chain txs that interact with dApp in another group', async () => {
const nodeProvider = web3.getCurrentNodeProvider()
const signer1 = await getSigner(100n * ONE_ALPH, 1)
const signer2 = await getSigner(0n, 2)
const [wallet, address1, address2] = await prepareChainedTxTest()

// Deploy contract in group 2
const deployer = await getSigner(100n * ONE_ALPH, 2)
Expand All @@ -224,38 +216,30 @@ describe('transactions', function () {
const transactInstance = deploy.contractInstance
expect(transactInstance.groupIndex).toBe(2)

const depositTxParams = await Deposit.script.txParamsForExecution(signer2, {
await wallet.setSelectedAccount(address2.address)
const depositTxParams = await Deposit.script.txParamsForExecution(wallet, {
initialFields: { c: transactInstance.contractId },
attoAlphAmount: ONE_ALPH
})
expect(depositTxParams.signerAddress).toBe(address2.address)

await expect(signer2.signAndSubmitExecuteScriptTx(depositTxParams)).rejects.toThrow(
await expect(wallet.signAndSubmitExecuteScriptTx(depositTxParams)).rejects.toThrow(
`[API Error] - Insufficient funds for gas`
)

const transferTxParams: SignTransferChainedTxParams = {
signerAddress: signer1.address,
destinations: [{ address: signer2.address, attoAlphAmount: 10n * ONE_ALPH }],
signerAddress: address1.address,
destinations: [{ address: address2.address, attoAlphAmount: 10n * ONE_ALPH }],
type: 'Transfer'
}

const [transferResult, depositResult] = await TransactionBuilder.from(nodeProvider).buildChainedTx(
[transferTxParams, { ...depositTxParams, type: 'ExecuteScript' }],
[signer1.publicKey, signer2.publicKey]
)

await signer1.signAndSubmitUnsignedTx({
unsignedTx: transferResult.unsignedTx,
signerAddress: signer1.address
})

await signer2.signAndSubmitUnsignedTx({
unsignedTx: depositResult.unsignedTx,
signerAddress: signer2.address
})
const [transferResult, depositResult] = await wallet.buildChainedTx([
transferTxParams,
{ ...depositTxParams, type: 'ExecuteScript' }
])

const signer1Balance = await nodeProvider.addresses.getAddressesAddressBalance(signer1.address)
const signer2Balance = await nodeProvider.addresses.getAddressesAddressBalance(signer2.address)
const signer1Balance = await nodeProvider.addresses.getAddressesAddressBalance(address1.address)
const signer2Balance = await nodeProvider.addresses.getAddressesAddressBalance(address2.address)
const contractBalance = await nodeProvider.addresses.getAddressesAddressBalance(transactInstance.address)

const transferTxGasCost = BigInt(transferResult.gasAmount) * BigInt(transferResult.gasPrice)
Expand Down

0 comments on commit 975bb16

Please sign in to comment.