Skip to content

Commit

Permalink
feat!: transfer for multiple addresses (#2333)
Browse files Browse the repository at this point in the history
  • Loading branch information
Torres-ssf authored May 21, 2024
1 parent 67afa32 commit 7c08593
Show file tree
Hide file tree
Showing 11 changed files with 285 additions and 76 deletions.
6 changes: 6 additions & 0 deletions .changeset/poor-ways-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-ts/account": minor
"@fuel-ts/program": minor
---

feat!: transfer for multiple addresses
24 changes: 16 additions & 8 deletions apps/docs-snippets/src/guide/contracts/add-transfer.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ASSET_A, ASSET_B } from '@fuel-ts/utils/test-utils';
import type { Account, Contract, Provider } from 'fuels';
import type { Account, Contract, Provider, TransferParams } from 'fuels';
import { Wallet } from 'fuels';

import { DocSnippetProjectsEnum } from '../../../test/fixtures/forc-projects';
Expand Down Expand Up @@ -31,7 +31,14 @@ describe(__filename, () => {
// #region add-transfer-1
const recipient = Wallet.generate({ provider });

await contract.functions.echo_u64(100).addTransfer(recipient.address, 100, baseAssetId).call();
await contract.functions
.echo_u64(100)
.addTransfer({
destination: recipient.address,
amount: 100,
assetId: baseAssetId,
})
.call();
// #endregion add-transfer-1

const recipientBalance = await recipient.getBalance(baseAssetId);
Expand All @@ -44,12 +51,13 @@ describe(__filename, () => {
const recipient1 = Wallet.generate({ provider });
const recipient2 = Wallet.generate({ provider });

await contract.functions
.echo_u64(100)
.addTransfer(recipient1.address, 100, baseAssetId)
.addTransfer(recipient1.address, 400, ASSET_A)
.addTransfer(recipient2.address, 300, ASSET_B)
.call();
const transferParams: TransferParams[] = [
{ destination: recipient1.address, amount: 100, assetId: baseAssetId },
{ destination: recipient1.address, amount: 400, assetId: ASSET_A },
{ destination: recipient2.address, amount: 300, assetId: ASSET_B },
];

await contract.functions.echo_u64(100).addBatchTransfer(transferParams).call();
// #endregion add-transfer-2

const recipient1BalanceBaseAsset = await recipient1.getBalance(baseAssetId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ describe('Custom Transactions from Contract Calls', () => {
// Connect to the contract
const contractInstance = new Contract(contract.id, abi, senderWallet);
// Create an invocation scope for the contract function you'd like to call in the transaction
const scope = contractInstance.functions
.increment_count(amountToRecipient)
.addTransfer(receiverWallet.address, amountToRecipient, baseAssetId);
const scope = contractInstance.functions.increment_count(amountToRecipient).addTransfer({
amount: amountToRecipient,
destination: receiverWallet.address,
assetId: baseAssetId,
});

// Build a transaction request from the invocation scope
const transactionRequest = await scope.getTransactionRequest();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ describe('Signing transactions', () => {
const script = new Script(bytecode, abi, sender);
const { value } = await script.functions
.main(signer.address.toB256())
.addTransfer(receiver.address, amountToReceiver, baseAssetId)
.addTransfer({
destination: receiver.address,
amount: amountToReceiver,
assetId: baseAssetId,
})
.addSigners(signer)
.call<BN>();
// #endregion multiple-signers-2
Expand Down
24 changes: 23 additions & 1 deletion apps/docs-snippets/src/guide/wallets/wallet-transferring.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { generateTestWallet } from '@fuel-ts/account/test-utils';
import { ASSET_A } from '@fuel-ts/utils/test-utils';
import type { Contract } from 'fuels';
import type { Contract, TransferParams } from 'fuels';
import { FUEL_NETWORK_URL, Provider, Wallet } from 'fuels';

import { DocSnippetProjectsEnum } from '../../../test/fixtures/forc-projects';
Expand Down Expand Up @@ -74,6 +74,28 @@ describe(__filename, () => {
expect(newBalance.toNumber()).toBeGreaterThan(0);
});

it('should successfully multi transfer to more than one receiver', async () => {
const someOtherAssetId = ASSET_A;

// #region wallet-transferring-6
const myWallet = Wallet.fromPrivateKey(privateKey, provider);

const recipient1 = Wallet.generate({ provider });
const recipient2 = Wallet.generate({ provider });

const transfersToMake: TransferParams[] = [
{ amount: 100, destination: recipient1.address, assetId: baseAssetId },
{ amount: 200, destination: recipient2.address, assetId: baseAssetId },
{ amount: 300, destination: recipient2.address, assetId: someOtherAssetId },
];

const tx = await myWallet.batchTransfer(transfersToMake);
const { isStatusSuccess } = await tx.waitForResult();
// #endregion wallet-transferring-6

expect(isStatusSuccess).toBeTruthy();
});

it('should transfer assets to a deployed contract instance just fine', async () => {
// #region wallet-transferring-4
const myWallet = Wallet.fromPrivateKey(privateKey, provider);
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/src/guide/contracts/transferring-assets.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ The `addTransfer` method allows you to append an asset transfer to your contract

In the previous example, we first use a contract call to the `echo_u64` function. Following this, `addTransfer` is added to chain call to include a transfer of `100` units of the `BaseAssetId` in the transaction.

## Multiple Transfers
## Batch Transfer

You can chain multiple `addTransfer` calls to include various transfers in a single transaction. Here's how you can concatenate these calls:
You can add a batch of transfers into a single transaction by using `addBatchTransfer`:

<<< @/../../docs-snippets/src/guide/contracts/add-transfer.test.ts#add-transfer-2{ts:line-numbers}
6 changes: 6 additions & 0 deletions apps/docs/src/guide/wallets/wallet-transferring.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ When transferring the base chain coin like ETH, you can omit the `assetId`:

<<< @/../../docs-snippets/src/guide/wallets/wallet-transferring.test.ts#wallet-transferring-3{ts:line-numbers}

## Transferring To Multiple Wallets

To transfer assets to multiple wallets, use the `Account.batchTransfer` method:

<<< @/../../docs-snippets/src/guide/wallets/wallet-transferring.test.ts#wallet-transferring-6{ts:line-numbers}

## Transferring To Contracts

Transferring assets from your wallet to a deployed contract is straightforward. All you need is the contract's address.
Expand Down
103 changes: 86 additions & 17 deletions packages/account/src/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { bn } from '@fuel-ts/math';
import { PolicyType } from '@fuel-ts/transactions';
import { ASSET_A, ASSET_B } from '@fuel-ts/utils/test-utils';

import type { TransferParams } from './account';
import { Account } from './account';
import { FUEL_NETWORK_URL } from './configs';
import { ScriptTransactionRequest, Provider } from './providers';
Expand All @@ -14,22 +15,23 @@ import type { Coin, CoinQuantity, Message, Resource } from './providers';
import { generateTestWallet, seedTestWallet } from './test-utils';
import { Wallet } from './wallet';

let provider: Provider;
let baseAssetId: string;
afterEach(() => {
vi.restoreAllMocks();
});

beforeAll(async () => {
provider = await Provider.create(FUEL_NETWORK_URL);
baseAssetId = provider.getBaseAssetId();
});

/**
* @group node
*/

describe('Account', () => {
const assets = [ASSET_A, ASSET_B, ZeroBytes32];
let provider: Provider;
let baseAssetId: string;

beforeAll(async () => {
provider = await Provider.create(FUEL_NETWORK_URL);
baseAssetId = provider.getBaseAssetId();
});

afterEach(() => {
vi.restoreAllMocks();
});

it('should create account using an address, with a provider', () => {
const account = new Account(
Expand Down Expand Up @@ -391,6 +393,77 @@ describe('Account', () => {
expect(receiverBalances).toEqual([{ assetId: baseAssetId, amount: bn(1) }]);
});

it('can transfer to multiple destinations', async () => {
const sender = await generateTestWallet(provider, [
[900_000, baseAssetId],
[900_000, ASSET_A],
[900_000, ASSET_B],
]);

const amounts = [100, 200, 300, 400];

const receivers = [
Wallet.generate({ provider }),
Wallet.generate({ provider }),
Wallet.generate({ provider }),
];

const transferConfig: TransferParams[] = [
{ amount: amounts[0], destination: receivers[0].address, assetId: baseAssetId },
{ amount: amounts[1], destination: receivers[1].address, assetId: ASSET_A },
{ amount: amounts[2], destination: receivers[2].address, assetId: ASSET_B },
{ amount: amounts[3], destination: receivers[2].address, assetId: ASSET_A },
];

const response1 = await sender.batchTransfer(transferConfig);
const { isStatusSuccess } = await response1.waitForResult();
expect(isStatusSuccess).toBeTruthy();

const expectedBalances = [
{ receiver: receivers[0], assetId: baseAssetId, expectedBalance: amounts[0] },
{ receiver: receivers[1], assetId: ASSET_A, expectedBalance: amounts[1] },
{ receiver: receivers[2], assetId: ASSET_B, expectedBalance: amounts[2] },
{ receiver: receivers[2], assetId: ASSET_A, expectedBalance: amounts[3] },
];

for (const { receiver, assetId, expectedBalance } of expectedBalances) {
const balance = await receiver.getBalance(assetId);
expect(balance.toNumber()).toBe(expectedBalance);
}

// Test with custom TX Params
const gasLimit = 100_000;
const maxFee = 120_000;
const tip = 1_000;
const witnessLimit = 10_000;
const maturity = 1;

const response = await sender.batchTransfer(transferConfig, {
gasLimit,
maxFee,
tip,
witnessLimit,
maturity,
});

const {
transaction: { policies, scriptGasLimit },
isStatusSuccess: isStatusSuccess2,
} = await response.waitForResult();

expect(isStatusSuccess2).toBeTruthy();
expect(scriptGasLimit?.toNumber()).toBe(gasLimit);
expect(bn(policies?.[0].data).toNumber()).toBe(tip);
expect(bn(policies?.[1].data).toNumber()).toBe(witnessLimit);
expect(policies?.[2].data).toBe(maturity);
expect(bn(policies?.[3].data).toNumber()).toBe(maxFee);

for (const { receiver, assetId, expectedBalance } of expectedBalances) {
const balance = await receiver.getBalance(assetId);
expect(balance.toNumber()).toBe(expectedBalance * 2);
}
});

it('can create transfer request just fine', async () => {
const sender = await generateTestWallet(provider, [[500_000, baseAssetId]]);
const receiver = await generateTestWallet(provider);
Expand Down Expand Up @@ -575,9 +648,7 @@ describe('Account', () => {
});

// seed wallet with 3 distinct utxos
await seedTestWallet(sender, [[500_000, baseAssetId]]);
await seedTestWallet(sender, [[500_000, baseAssetId]]);
await seedTestWallet(sender, [[500_000, baseAssetId]]);
await seedTestWallet(sender, [[1_500_000, baseAssetId]], 3);

const transfer = await sender.transfer(receiver.address, 110, baseAssetId, {
gasLimit: 10_000,
Expand All @@ -593,9 +664,7 @@ describe('Account', () => {
provider,
});
// seed wallet with 3 distinct utxos
await seedTestWallet(sender, [[500_000, baseAssetId]]);
await seedTestWallet(sender, [[500_000, baseAssetId]]);
await seedTestWallet(sender, [[500_000, baseAssetId]]);
await seedTestWallet(sender, [[1_500_000, baseAssetId]], 3);
const recipient = Address.fromB256(
'0x00000000000000000000000047ba61eec8e5e65247d717ff236f504cf3b0a263'
);
Expand Down
Loading

0 comments on commit 7c08593

Please sign in to comment.