@solana/web3.js 1.75.0 → 1.77.0

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.
@@ -1,10 +1,7 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
3
  var buffer = require('buffer');
6
- var sha512 = require('@noble/hashes/sha512');
7
- var ed25519 = require('@noble/ed25519');
4
+ var ed25519 = require('@noble/curves/ed25519');
8
5
  var BN = require('bn.js');
9
6
  var bs58 = require('bs58');
10
7
  var sha256 = require('@noble/hashes/sha256');
@@ -16,13 +13,12 @@ var RpcClient = require('jayson/lib/client/browser');
16
13
  var RpcWebSocketCommonClient = require('rpc-websockets/dist/lib/client');
17
14
  var createRpc = require('rpc-websockets/dist/lib/client/websocket.browser');
18
15
  var sha3 = require('@noble/hashes/sha3');
19
- var hmac = require('@noble/hashes/hmac');
20
- var secp256k1 = require('@noble/secp256k1');
16
+ var secp256k1 = require('@noble/curves/secp256k1');
21
17
 
22
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
18
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
23
19
 
24
- function _interopNamespace(e) {
25
- if (e && e.__esModule) return e;
20
+ function _interopNamespaceCompat(e) {
21
+ if (e && typeof e === 'object' && 'default' in e) return e;
26
22
  var n = Object.create(null);
27
23
  if (e) {
28
24
  Object.keys(e).forEach(function (k) {
@@ -35,18 +31,16 @@ function _interopNamespace(e) {
35
31
  }
36
32
  });
37
33
  }
38
- n["default"] = e;
34
+ n.default = e;
39
35
  return Object.freeze(n);
40
36
  }
41
37
 
42
- var ed25519__namespace = /*#__PURE__*/_interopNamespace(ed25519);
43
- var BN__default = /*#__PURE__*/_interopDefaultLegacy(BN);
44
- var bs58__default = /*#__PURE__*/_interopDefaultLegacy(bs58);
45
- var BufferLayout__namespace = /*#__PURE__*/_interopNamespace(BufferLayout);
46
- var RpcClient__default = /*#__PURE__*/_interopDefaultLegacy(RpcClient);
47
- var RpcWebSocketCommonClient__default = /*#__PURE__*/_interopDefaultLegacy(RpcWebSocketCommonClient);
48
- var createRpc__default = /*#__PURE__*/_interopDefaultLegacy(createRpc);
49
- var secp256k1__namespace = /*#__PURE__*/_interopNamespace(secp256k1);
38
+ var BN__default = /*#__PURE__*/_interopDefaultCompat(BN);
39
+ var bs58__default = /*#__PURE__*/_interopDefaultCompat(bs58);
40
+ var BufferLayout__namespace = /*#__PURE__*/_interopNamespaceCompat(BufferLayout);
41
+ var RpcClient__default = /*#__PURE__*/_interopDefaultCompat(RpcClient);
42
+ var RpcWebSocketCommonClient__default = /*#__PURE__*/_interopDefaultCompat(RpcWebSocketCommonClient);
43
+ var createRpc__default = /*#__PURE__*/_interopDefaultCompat(createRpc);
50
44
 
51
45
  /**
52
46
  * A 64 byte secret key, the first 32 bytes of which is the
@@ -54,10 +48,9 @@ var secp256k1__namespace = /*#__PURE__*/_interopNamespace(secp256k1);
54
48
  * Read more: https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/
55
49
  */
56
50
 
57
- ed25519__namespace.utils.sha512Sync = (...m) => sha512.sha512(ed25519__namespace.utils.concatBytes(...m));
58
- const generatePrivateKey = ed25519__namespace.utils.randomPrivateKey;
51
+ const generatePrivateKey = ed25519.ed25519.utils.randomPrivateKey;
59
52
  const generateKeypair = () => {
60
- const privateScalar = ed25519__namespace.utils.randomPrivateKey();
53
+ const privateScalar = ed25519.ed25519.utils.randomPrivateKey();
61
54
  const publicKey = getPublicKey(privateScalar);
62
55
  const secretKey = new Uint8Array(64);
63
56
  secretKey.set(privateScalar);
@@ -67,17 +60,17 @@ const generateKeypair = () => {
67
60
  secretKey
68
61
  };
69
62
  };
70
- const getPublicKey = ed25519__namespace.sync.getPublicKey;
63
+ const getPublicKey = ed25519.ed25519.getPublicKey;
71
64
  function isOnCurve(publicKey) {
72
65
  try {
73
- ed25519__namespace.Point.fromHex(publicKey, true /* strict */);
66
+ ed25519.ed25519.ExtendedPoint.fromHex(publicKey);
74
67
  return true;
75
68
  } catch {
76
69
  return false;
77
70
  }
78
71
  }
79
- const sign = (message, secretKey) => ed25519__namespace.sync.sign(message, secretKey.slice(0, 32));
80
- const verify = ed25519__namespace.sync.verify;
72
+ const sign = (message, secretKey) => ed25519.ed25519.sign(message, secretKey.slice(0, 32));
73
+ const verify = ed25519.ed25519.verify;
81
74
 
82
75
  const toBuffer = arr => {
83
76
  if (buffer.Buffer.isBuffer(arr)) {
@@ -163,13 +156,13 @@ class PublicKey extends Struct {
163
156
  } else {
164
157
  if (typeof value === 'string') {
165
158
  // assume base 58 encoding by default
166
- const decoded = bs58__default["default"].decode(value);
159
+ const decoded = bs58__default.default.decode(value);
167
160
  if (decoded.length != PUBLIC_KEY_LENGTH) {
168
161
  throw new Error(`Invalid public key input`);
169
162
  }
170
- this._bn = new BN__default["default"](decoded);
163
+ this._bn = new BN__default.default(decoded);
171
164
  } else {
172
- this._bn = new BN__default["default"](value);
165
+ this._bn = new BN__default.default(value);
173
166
  }
174
167
  if (this._bn.byteLength() > PUBLIC_KEY_LENGTH) {
175
168
  throw new Error(`Invalid public key input`);
@@ -202,7 +195,7 @@ class PublicKey extends Struct {
202
195
  * Return the base-58 representation of the public key
203
196
  */
204
197
  toBase58() {
205
- return bs58__default["default"].encode(this.toBytes());
198
+ return bs58__default.default.encode(this.toBytes());
206
199
  }
207
200
  toJSON() {
208
201
  return this.toBase58();
@@ -634,8 +627,8 @@ class CompiledKeys {
634
627
  getOrInsertDefault(ix.programId).isInvoked = true;
635
628
  for (const accountMeta of ix.keys) {
636
629
  const keyMeta = getOrInsertDefault(accountMeta.pubkey);
637
- keyMeta.isSigner || (keyMeta.isSigner = accountMeta.isSigner);
638
- keyMeta.isWritable || (keyMeta.isWritable = accountMeta.isWritable);
630
+ keyMeta.isSigner ||= accountMeta.isSigner;
631
+ keyMeta.isWritable ||= accountMeta.isWritable;
639
632
  }
640
633
  }
641
634
  return new CompiledKeys(payer, keyMetaMap);
@@ -734,7 +727,7 @@ class Message {
734
727
  return this.instructions.map(ix => ({
735
728
  programIdIndex: ix.programIdIndex,
736
729
  accountKeyIndexes: ix.accounts,
737
- data: bs58__default["default"].decode(ix.data)
730
+ data: bs58__default.default.decode(ix.data)
738
731
  }));
739
732
  }
740
733
  get addressTableLookups() {
@@ -750,7 +743,7 @@ class Message {
750
743
  const instructions = accountKeys.compileInstructions(args.instructions).map(ix => ({
751
744
  programIdIndex: ix.programIdIndex,
752
745
  accounts: ix.accountKeyIndexes,
753
- data: bs58__default["default"].encode(ix.data)
746
+ data: bs58__default.default.encode(ix.data)
754
747
  }));
755
748
  return new Message({
756
749
  header,
@@ -792,7 +785,7 @@ class Message {
792
785
  accounts,
793
786
  programIdIndex
794
787
  } = instruction;
795
- const data = Array.from(bs58__default["default"].decode(instruction.data));
788
+ const data = Array.from(bs58__default.default.decode(instruction.data));
796
789
  let keyIndicesCount = [];
797
790
  encodeLength(keyIndicesCount, accounts.length);
798
791
  let dataCount = [];
@@ -823,7 +816,7 @@ class Message {
823
816
  numReadonlyUnsignedAccounts: buffer.Buffer.from([this.header.numReadonlyUnsignedAccounts]),
824
817
  keyCount: buffer.Buffer.from(keyCount),
825
818
  keys: this.accountKeys.map(key => toBuffer(key.toBytes())),
826
- recentBlockhash: bs58__default["default"].decode(this.recentBlockhash)
819
+ recentBlockhash: bs58__default.default.decode(this.recentBlockhash)
827
820
  };
828
821
  let signData = buffer.Buffer.alloc(2048);
829
822
  const length = signDataLayout.encode(transaction, signData);
@@ -861,7 +854,7 @@ class Message {
861
854
  byteArray = byteArray.slice(accountCount);
862
855
  const dataLength = decodeLength(byteArray);
863
856
  const dataSlice = byteArray.slice(0, dataLength);
864
- const data = bs58__default["default"].encode(buffer.Buffer.from(dataSlice));
857
+ const data = bs58__default.default.encode(buffer.Buffer.from(dataSlice));
865
858
  byteArray = byteArray.slice(dataLength);
866
859
  instructions.push({
867
860
  programIdIndex,
@@ -875,7 +868,7 @@ class Message {
875
868
  numReadonlySignedAccounts,
876
869
  numReadonlyUnsignedAccounts
877
870
  },
878
- recentBlockhash: bs58__default["default"].encode(buffer.Buffer.from(recentBlockhash)),
871
+ recentBlockhash: bs58__default.default.encode(buffer.Buffer.from(recentBlockhash)),
879
872
  accountKeys,
880
873
  instructions
881
874
  };
@@ -1019,7 +1012,7 @@ class MessageV0 {
1019
1012
  header: this.header,
1020
1013
  staticAccountKeysLength: new Uint8Array(encodedStaticAccountKeysLength),
1021
1014
  staticAccountKeys: this.staticAccountKeys.map(key => key.toBytes()),
1022
- recentBlockhash: bs58__default["default"].decode(this.recentBlockhash),
1015
+ recentBlockhash: bs58__default.default.decode(this.recentBlockhash),
1023
1016
  instructionsLength: new Uint8Array(encodedInstructionsLength),
1024
1017
  serializedInstructions,
1025
1018
  addressTableLookupsLength: new Uint8Array(encodedAddressTableLookupsLength),
@@ -1082,7 +1075,7 @@ class MessageV0 {
1082
1075
  for (let i = 0; i < staticAccountKeysLength; i++) {
1083
1076
  staticAccountKeys.push(new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH)));
1084
1077
  }
1085
- const recentBlockhash = bs58__default["default"].encode(byteArray.splice(0, PUBLIC_KEY_LENGTH));
1078
+ const recentBlockhash = bs58__default.default.encode(byteArray.splice(0, PUBLIC_KEY_LENGTH));
1086
1079
  const instructionCount = decodeLength(byteArray);
1087
1080
  const compiledInstructions = [];
1088
1081
  for (let i = 0; i < instructionCount; i++) {
@@ -1488,7 +1481,7 @@ class Transaction {
1488
1481
  return {
1489
1482
  programIdIndex: accountKeys.indexOf(programId.toString()),
1490
1483
  accounts: instruction.keys.map(meta => accountKeys.indexOf(meta.pubkey.toString())),
1491
- data: bs58__default["default"].encode(data)
1484
+ data: bs58__default.default.encode(data)
1492
1485
  };
1493
1486
  });
1494
1487
  compiledInstructions.forEach(instruction => {
@@ -1781,7 +1774,7 @@ class Transaction {
1781
1774
  for (let i = 0; i < signatureCount; i++) {
1782
1775
  const signature = byteArray.slice(0, SIGNATURE_LENGTH_IN_BYTES);
1783
1776
  byteArray = byteArray.slice(SIGNATURE_LENGTH_IN_BYTES);
1784
- signatures.push(bs58__default["default"].encode(buffer.Buffer.from(signature)));
1777
+ signatures.push(bs58__default.default.encode(buffer.Buffer.from(signature)));
1785
1778
  }
1786
1779
  return Transaction.populate(Message.from(byteArray), signatures);
1787
1780
  }
@@ -1797,7 +1790,7 @@ class Transaction {
1797
1790
  }
1798
1791
  signatures.forEach((signature, index) => {
1799
1792
  const sigPubkeyPair = {
1800
- signature: signature == bs58__default["default"].encode(DEFAULT_SIGNATURE) ? null : bs58__default["default"].decode(signature),
1793
+ signature: signature == bs58__default.default.encode(DEFAULT_SIGNATURE) ? null : bs58__default.default.decode(signature),
1801
1794
  publicKey: message.accountKeys[index]
1802
1795
  };
1803
1796
  transaction.signatures.push(sigPubkeyPair);
@@ -1814,7 +1807,7 @@ class Transaction {
1814
1807
  transaction.instructions.push(new TransactionInstruction({
1815
1808
  keys,
1816
1809
  programId: message.accountKeys[instruction.programIdIndex],
1817
- data: bs58__default["default"].decode(instruction.data)
1810
+ data: bs58__default.default.decode(instruction.data)
1818
1811
  }));
1819
1812
  });
1820
1813
  transaction._message = message;
@@ -1973,6 +1966,29 @@ class VersionedTransaction {
1973
1966
  }
1974
1967
  }
1975
1968
 
1969
+ // TODO: These constants should be removed in favor of reading them out of a
1970
+ // Syscall account
1971
+
1972
+ /**
1973
+ * @internal
1974
+ */
1975
+ const NUM_TICKS_PER_SECOND = 160;
1976
+
1977
+ /**
1978
+ * @internal
1979
+ */
1980
+ const DEFAULT_TICKS_PER_SLOT = 64;
1981
+
1982
+ /**
1983
+ * @internal
1984
+ */
1985
+ const NUM_SLOTS_PER_SECOND = NUM_TICKS_PER_SECOND / DEFAULT_TICKS_PER_SLOT;
1986
+
1987
+ /**
1988
+ * @internal
1989
+ */
1990
+ const MS_PER_SLOT = 1000 / NUM_SLOTS_PER_SECOND;
1991
+
1976
1992
  const SYSVAR_CLOCK_PUBKEY = new PublicKey('SysvarC1ock11111111111111111111111111111111');
1977
1993
  const SYSVAR_EPOCH_SCHEDULE_PUBKEY = new PublicKey('SysvarEpochSchedu1e111111111111111111111111');
1978
1994
  const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey('Sysvar1nstructions1111111111111111111111111');
@@ -2987,9 +3003,37 @@ class Loader {
2987
3003
  programId,
2988
3004
  data
2989
3005
  });
2990
- await sendAndConfirmTransaction(connection, transaction, [payer, program], {
2991
- commitment: 'confirmed'
3006
+ const deployCommitment = 'processed';
3007
+ const finalizeSignature = await connection.sendTransaction(transaction, [payer, program], {
3008
+ preflightCommitment: deployCommitment
2992
3009
  });
3010
+ const {
3011
+ context,
3012
+ value
3013
+ } = await connection.confirmTransaction({
3014
+ signature: finalizeSignature,
3015
+ lastValidBlockHeight: transaction.lastValidBlockHeight,
3016
+ blockhash: transaction.recentBlockhash
3017
+ }, deployCommitment);
3018
+ if (value.err) {
3019
+ throw new Error(`Transaction ${finalizeSignature} failed (${JSON.stringify(value)})`);
3020
+ }
3021
+ // We prevent programs from being usable until the slot after their deployment.
3022
+ // See https://github.com/solana-labs/solana/pull/29654
3023
+ while (true // eslint-disable-line no-constant-condition
3024
+ ) {
3025
+ try {
3026
+ const currentSlot = await connection.getSlot({
3027
+ commitment: deployCommitment
3028
+ });
3029
+ if (currentSlot > context.slot) {
3030
+ break;
3031
+ }
3032
+ } catch {
3033
+ /* empty */
3034
+ }
3035
+ await new Promise(resolve => setTimeout(resolve, Math.round(MS_PER_SLOT / 2)));
3036
+ }
2993
3037
  }
2994
3038
 
2995
3039
  // success
@@ -3239,10 +3283,10 @@ class SolanaJSONRPCError extends Error {
3239
3283
 
3240
3284
  var fetchImpl = globalThis.fetch;
3241
3285
 
3242
- class RpcWebSocketClient extends RpcWebSocketCommonClient__default["default"] {
3286
+ class RpcWebSocketClient extends RpcWebSocketCommonClient__default.default {
3243
3287
  constructor(address, options, generate_request_id) {
3244
3288
  const webSocketFactory = url => {
3245
- const rpc = createRpc__default["default"](url, {
3289
+ const rpc = createRpc__default.default(url, {
3246
3290
  autoconnect: true,
3247
3291
  max_reconnects: 5,
3248
3292
  reconnect: true,
@@ -3275,29 +3319,6 @@ class RpcWebSocketClient extends RpcWebSocketCommonClient__default["default"] {
3275
3319
  }
3276
3320
  }
3277
3321
 
3278
- // TODO: These constants should be removed in favor of reading them out of a
3279
- // Syscall account
3280
-
3281
- /**
3282
- * @internal
3283
- */
3284
- const NUM_TICKS_PER_SECOND = 160;
3285
-
3286
- /**
3287
- * @internal
3288
- */
3289
- const DEFAULT_TICKS_PER_SLOT = 64;
3290
-
3291
- /**
3292
- * @internal
3293
- */
3294
- const NUM_SLOTS_PER_SECOND = NUM_TICKS_PER_SECOND / DEFAULT_TICKS_PER_SLOT;
3295
-
3296
- /**
3297
- * @internal
3298
- */
3299
- const MS_PER_SLOT = 1000 / NUM_SLOTS_PER_SECOND;
3300
-
3301
3322
  /**
3302
3323
  * @internal
3303
3324
  */
@@ -3496,7 +3517,7 @@ function versionedMessageFromResponse(version, response) {
3496
3517
  compiledInstructions: response.instructions.map(ix => ({
3497
3518
  programIdIndex: ix.programIdIndex,
3498
3519
  accountKeyIndexes: ix.accounts,
3499
- data: bs58__default["default"].decode(ix.data)
3520
+ data: bs58__default.default.decode(ix.data)
3500
3521
  })),
3501
3522
  addressTableLookups: response.addressTableLookups
3502
3523
  });
@@ -3657,7 +3678,7 @@ function createRpcClient(url, httpHeaders, customFetch, fetchMiddleware, disable
3657
3678
  return await fetch(...modifiedFetchArgs);
3658
3679
  };
3659
3680
  }
3660
- const clientBrowser = new RpcClient__default["default"](async (request, callback) => {
3681
+ const clientBrowser = new RpcClient__default.default(async (request, callback) => {
3661
3682
  const options = {
3662
3683
  method: 'POST',
3663
3684
  body: request,
@@ -4729,7 +4750,7 @@ class Connection {
4729
4750
  /**
4730
4751
  * Fetch all the token accounts owned by the specified account
4731
4752
  *
4732
- * @return {Promise<RpcResponseAndContext<Array<{pubkey: PublicKey, account: AccountInfo<Buffer>}>>>}
4753
+ * @return {Promise<RpcResponseAndContext<GetProgramAccountsResponse>}
4733
4754
  */
4734
4755
  async getTokenAccountsByOwner(ownerAddress, filter, commitmentOrConfig) {
4735
4756
  const {
@@ -4926,6 +4947,8 @@ class Connection {
4926
4947
  *
4927
4948
  * @return {Promise<Array<{pubkey: PublicKey, account: AccountInfo<Buffer>}>>}
4928
4949
  */
4950
+
4951
+ // eslint-disable-next-line no-dupe-class-members
4929
4952
  async getProgramAccounts(programId, configOrCommitment) {
4930
4953
  const {
4931
4954
  commitment,
@@ -4937,7 +4960,8 @@ class Connection {
4937
4960
  } = config || {};
4938
4961
  const args = this._buildArgs([programId.toBase58()], commitment, encoding || 'base64', configWithoutEncoding);
4939
4962
  const unsafeRes = await this._rpcRequest('getProgramAccounts', args);
4940
- const res = superstruct.create(unsafeRes, jsonRpcResult(superstruct.array(KeyedAccountInfoResult)));
4963
+ const baseSchema = superstruct.array(KeyedAccountInfoResult);
4964
+ const res = configWithoutEncoding.withContext === true ? superstruct.create(unsafeRes, jsonRpcResultAndContext(baseSchema)) : superstruct.create(unsafeRes, jsonRpcResult(baseSchema));
4941
4965
  if ('error' in res) {
4942
4966
  throw new SolanaJSONRPCError(res.error, `failed to get accounts owned by program ${programId.toBase58()}`);
4943
4967
  }
@@ -4976,7 +5000,7 @@ class Connection {
4976
5000
  }
4977
5001
  let decodedSignature;
4978
5002
  try {
4979
- decodedSignature = bs58__default["default"].decode(rawSignature);
5003
+ decodedSignature = bs58__default.default.decode(rawSignature);
4980
5004
  } catch (err) {
4981
5005
  throw new Error('signature must be base58 encoded: ' + rawSignature);
4982
5006
  }
@@ -6669,12 +6693,11 @@ class Connection {
6669
6693
  * @internal
6670
6694
  */
6671
6695
  _onSubscriptionStateChange(clientSubscriptionId, callback) {
6672
- var _this$_subscriptionSt;
6673
6696
  const hash = this._subscriptionHashByClientSubscriptionId[clientSubscriptionId];
6674
6697
  if (hash == null) {
6675
6698
  return () => {};
6676
6699
  }
6677
- const stateChangeCallbacks = (_this$_subscriptionSt = this._subscriptionStateChangeCallbacksByHash)[hash] || (_this$_subscriptionSt[hash] = new Set());
6700
+ const stateChangeCallbacks = this._subscriptionStateChangeCallbacksByHash[hash] ||= new Set();
6678
6701
  stateChangeCallbacks.add(callback);
6679
6702
  return () => {
6680
6703
  stateChangeCallbacks.delete(callback);
@@ -7902,19 +7925,12 @@ class Ed25519Program {
7902
7925
  }
7903
7926
  Ed25519Program.programId = new PublicKey('Ed25519SigVerify111111111111111111111111111');
7904
7927
 
7905
- // Supply a synchronous hashing algorithm to make this
7906
- // library interoperable with the synchronous APIs in web3.js.
7907
- secp256k1__namespace.utils.hmacSha256Sync = (key, ...msgs) => {
7908
- const h = hmac.hmac.create(sha256.sha256, key);
7909
- msgs.forEach(msg => h.update(msg));
7910
- return h.digest();
7928
+ const ecdsaSign = (msgHash, privKey) => {
7929
+ const signature = secp256k1.secp256k1.sign(msgHash, privKey);
7930
+ return [signature.toCompactRawBytes(), signature.recovery];
7911
7931
  };
7912
- const ecdsaSign = (msgHash, privKey) => secp256k1__namespace.signSync(msgHash, privKey, {
7913
- der: false,
7914
- recovered: true
7915
- });
7916
- secp256k1__namespace.utils.isValidPrivateKey;
7917
- const publicKeyCreate = secp256k1__namespace.getPublicKey;
7932
+ secp256k1.secp256k1.utils.isValidPrivateKey;
7933
+ const publicKeyCreate = secp256k1.secp256k1.getPublicKey;
7918
7934
 
7919
7935
  const PRIVATE_KEY_BYTES = 32;
7920
7936
  const ETHEREUM_ADDRESS_BYTES = 20;
@@ -9182,7 +9198,7 @@ class VoteProgram {
9182
9198
  }
9183
9199
  }
9184
9200
  VoteProgram.programId = new PublicKey('Vote111111111111111111111111111111111111111');
9185
- VoteProgram.space = 3731;
9201
+ VoteProgram.space = process.env.TEST_LIVE ? 3762 : 3731;
9186
9202
 
9187
9203
  const VALIDATOR_INFO_KEY = new PublicKey('Va1idator1nfo111111111111111111111111111111');
9188
9204