1: <?php declare( strict_types = 1 );
2:
3: namespace Waves\Transactions;
4:
5: use Exception;
6: use Waves\Account\PrivateKey;
7: use Waves\Common\Base58String;
8: use Waves\Account\PublicKey;
9: use Waves\Common\ExceptionCode;
10: use Waves\Common\Json;
11: use Waves\Common\Value;
12: use Waves\Model\AssetId;
13: use Waves\Model\ChainId;
14: use Waves\Transactions\Mass\Transfer;
15:
16: use Waves\Transactions\MassTransferTransaction as CurrentTransaction;
17:
18: class MassTransferTransaction extends Transaction
19: {
20: const TYPE = 11;
21: const LATEST_VERSION = 2;
22: const MIN_FEE = 100_000;
23:
24: /**
25: * @var array<int, Transfer>
26: */
27: private array $transfers;
28: private AssetId $assetId;
29: private Base58String $attachment;
30:
31: /**
32: * @param PublicKey $sender
33: * @param AssetId $assetId
34: * @param array<int, Transfer> $transfers
35: * @param Base58String $attachment
36: * @return CurrentTransaction
37: */
38: static function build( PublicKey $sender, AssetId $assetId, array $transfers, Base58String $attachment = null ): CurrentTransaction
39: {
40: $tx = new CurrentTransaction;
41: $tx->setBase( $sender, CurrentTransaction::TYPE, CurrentTransaction::LATEST_VERSION, CurrentTransaction::calculateFee( count( $transfers ) ) );
42:
43: // MASS_TRANSFER TRANSACTION
44: {
45: $tx->setAssetId( $assetId );
46: $tx->setTransfers( $transfers );
47: $tx->setAttachment( $attachment );
48: }
49:
50: return $tx;
51: }
52:
53: static function calculateFee( int $transfersCount ): int
54: {
55: return 100_000 + ( $transfersCount + ( $transfersCount & 1 ) ) * 50_000;
56: }
57:
58: function getUnsigned(): CurrentTransaction
59: {
60: // VERSION
61: if( $this->version() !== CurrentTransaction::LATEST_VERSION )
62: throw new Exception( __FUNCTION__ . ' unexpected version = ' . $this->version(), ExceptionCode::UNEXPECTED );
63:
64: // BASE
65: $pb_Transaction = $this->getProtobufTransactionBase();
66:
67: // MASS_TRANSFER TRANSACTION
68: {
69: $pb_TransactionData = new \Waves\Protobuf\MassTransferTransactionData;
70: // TRANSFERS
71: {
72: $pb_Transfers = [];
73: foreach( $this->transfers() as $transfer )
74: {
75: $pb_Transfer = new \Waves\Protobuf\MassTransferTransactionData\Transfer;
76: $pb_Transfer->setRecipient( $transfer->recipient()->toProtobuf() );
77: $pb_Transfer->setAmount( $transfer->amount() );
78: $pb_Transfers[] = $pb_Transfer;
79: }
80:
81: $pb_TransactionData->setTransfers( $pb_Transfers );
82: }
83: // ASSET
84: {
85: $pb_TransactionData->setAssetId( $this->assetId()->bytes() );
86: }
87: // ATTACHMENT
88: {
89: $pb_TransactionData->setAttachment( $this->attachment()->bytes() );
90: }
91: }
92:
93: // MASS_TRANSFER TRANSACTION
94: $this->setBodyBytes( $pb_Transaction->setMassTransfer( $pb_TransactionData )->serializeToString() );
95: return $this;
96: }
97:
98: function assetId(): AssetId
99: {
100: if( !isset( $this->assetId ) )
101: $this->assetId = $this->json->get( 'assetId' )->asAssetId();
102: return $this->assetId;
103: }
104:
105: function setAssetId( AssetId $assetId ): CurrentTransaction
106: {
107: $this->assetId = $assetId;
108: $this->json->put( 'assetId', $assetId->toJsonValue() );
109: return $this;
110: }
111:
112: /**
113: * @return array<int, Transfer>
114: */
115: function transfers(): array
116: {
117: if( !isset( $this->transfers ) )
118: {
119: $transfers = [];
120: foreach( $this->json->get( 'amount' )->asArray() as $value )
121: {
122: $json = Value::as( $value )->asJson();
123: $recipient = $json->get( 'recipient' )->asRecipient();
124: $amount = $json->get( 'amount' )->asInt();
125: $transfers[] = new Transfer( $recipient, $amount );
126: }
127: $this->transfers = $transfers;
128: }
129: return $this->transfers;
130: }
131:
132: /**
133: * @param array<int, Transfer> $transfers
134: * @return CurrentTransaction
135: */
136: function setTransfers( array $transfers ): CurrentTransaction
137: {
138: $this->transfers = $transfers;
139:
140: $transfers = [];
141: foreach( $this->transfers as $transfer )
142: $transfers[] = [ 'recipient' => $transfer->recipient()->toString(), 'amount' => $transfer->amount() ];
143: $this->json->put( 'transfers', $transfers );
144: return $this;
145: }
146:
147: function attachment(): Base58String
148: {
149: if( !isset( $this->attachment ) )
150: $this->attachment = $this->json->get( 'attachment' )->asBase58String();
151: return $this->attachment;
152: }
153:
154: function setAttachment( Base58String $attachment = null ): CurrentTransaction
155: {
156: $attachment = $attachment ?? Base58String::emptyString();
157: $this->attachment = $attachment;
158: $this->json->put( 'attachment', $attachment->toString() );
159: return $this;
160: }
161:
162: // COMMON
163:
164: function __construct( Json $json = null )
165: {
166: parent::__construct( $json );
167: }
168:
169: function addProof( PrivateKey $privateKey, int $index = null ): CurrentTransaction
170: {
171: $proof = (new \deemru\WavesKit)->sign( $this->bodyBytes(), $privateKey->bytes() );
172: if( $proof === false )
173: throw new Exception( __FUNCTION__ . ' unexpected sign() error', ExceptionCode::UNEXPECTED );
174: $proof = Base58String::fromBytes( $proof )->encoded();
175:
176: $proofs = $this->proofs();
177: if( !isset( $index ) )
178: $proofs[] = $proof;
179: else
180: $proofs[$index] = $proof;
181: return $this->setProofs( $proofs );
182: }
183:
184: /**
185: * @return CurrentTransaction
186: */
187: function setType( int $type )
188: {
189: parent::setType( $type );
190: return $this;
191: }
192:
193: /**
194: * @return CurrentTransaction
195: */
196: function setSender( PublicKey $sender )
197: {
198: parent::setSender( $sender );
199: return $this;
200: }
201:
202: /**
203: * @return CurrentTransaction
204: */
205: function setVersion( int $version )
206: {
207: parent::setVersion( $version );
208: return $this;
209: }
210:
211: /**
212: * @return CurrentTransaction
213: */
214: function setFee( Amount $fee )
215: {
216: parent::setFee( $fee );
217: return $this;
218: }
219:
220: /**
221: * @return CurrentTransaction
222: */
223: function setChainId( ChainId $chainId = null )
224: {
225: parent::setChainId( $chainId );
226: return $this;
227: }
228:
229: /**
230: * @return CurrentTransaction
231: */
232: function setTimestamp( int $timestamp = null )
233: {
234: parent::setTimestamp( $timestamp );
235: return $this;
236: }
237:
238: /**
239: * @param array<int, string> $proofs
240: * @return CurrentTransaction
241: */
242: function setProofs( array $proofs = null )
243: {
244: parent::setProofs( $proofs );
245: return $this;
246: }
247:
248: function bodyBytes(): string
249: {
250: if( !isset( $this->bodyBytes ) )
251: $this->getUnsigned();
252: return parent::bodyBytes();
253: }
254: }
255: