@across-protocol/contracts 4.0.12 → 4.0.14-alpha.1

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.
Files changed (51) hide show
  1. package/README.md +186 -3
  2. package/dist/deployments/deployments.json +8 -6
  3. package/dist/scripts/svm/closeRelayerPdas.js +1 -1
  4. package/dist/scripts/svm/createVault.js +2 -2
  5. package/dist/scripts/svm/fakeFillWithRandomDistribution.js +8 -7
  6. package/dist/scripts/svm/findFillStatusPdaFromEvent.js +1 -1
  7. package/dist/scripts/svm/initialize.js +19 -5
  8. package/dist/scripts/svm/nativeDeposit.js +30 -9
  9. package/dist/scripts/svm/queryEvents.js +1 -1
  10. package/dist/scripts/svm/queryEventsV2.js +1 -1
  11. package/dist/scripts/svm/queryFills.js +1 -1
  12. package/dist/scripts/svm/simpleDeposit.js +30 -9
  13. package/dist/scripts/svm/simpleFakeRelayerRepayment.js +19 -12
  14. package/dist/scripts/svm/simpleFill.js +7 -6
  15. package/dist/scripts/svm/squadsIdlUpgrade.d.ts +1 -0
  16. package/dist/scripts/svm/squadsIdlUpgrade.js +76 -0
  17. package/dist/src/svm/assets/idl/svm_spoke.json +77 -40
  18. package/dist/src/svm/assets/svm_spoke.d.ts +77 -40
  19. package/dist/src/svm/clients/SvmSpoke/instructions/deposit.d.ts +2 -2
  20. package/dist/src/svm/clients/SvmSpoke/instructions/deposit.js +2 -2
  21. package/dist/src/svm/clients/SvmSpoke/instructions/depositNow.d.ts +2 -2
  22. package/dist/src/svm/clients/SvmSpoke/instructions/depositNow.js +2 -2
  23. package/dist/src/svm/clients/SvmSpoke/instructions/handleReceiveMessage.d.ts +1 -1
  24. package/dist/src/svm/clients/SvmSpoke/instructions/handleReceiveMessage.js +5 -5
  25. package/dist/src/svm/clients/SvmSpoke/instructions/unsafeDeposit.d.ts +2 -2
  26. package/dist/src/svm/clients/SvmSpoke/instructions/unsafeDeposit.js +2 -2
  27. package/dist/src/svm/clients/SvmSpoke/programs/svmSpoke.d.ts +2 -2
  28. package/dist/src/svm/clients/SvmSpoke/programs/svmSpoke.js +1 -1
  29. package/dist/src/svm/clients/SvmSpoke/types/filledRelay.d.ts +2 -2
  30. package/dist/src/svm/clients/SvmSpoke/types/filledRelay.js +2 -2
  31. package/dist/src/svm/clients/SvmSpoke/types/fundsDeposited.d.ts +2 -2
  32. package/dist/src/svm/clients/SvmSpoke/types/fundsDeposited.js +2 -2
  33. package/dist/src/svm/clients/SvmSpoke/types/relayData.d.ts +2 -2
  34. package/dist/src/svm/clients/SvmSpoke/types/relayData.js +2 -2
  35. package/dist/src/svm/clients/SvmSpoke/types/requestedSlowFill.d.ts +2 -2
  36. package/dist/src/svm/clients/SvmSpoke/types/requestedSlowFill.js +2 -2
  37. package/dist/src/svm/web3-v1/helpers.d.ts +6 -6
  38. package/dist/src/svm/web3-v1/helpers.js +2 -2
  39. package/dist/src/svm/web3-v1/relayHashUtils.js +3 -3
  40. package/dist/src/svm/web3-v1/solanaProgramUtils.d.ts +1 -1
  41. package/dist/src/svm/web3-v1/solanaProgramUtils.js +17 -7
  42. package/dist/src/types/svm.d.ts +3 -3
  43. package/dist/target/types/svm_spoke.d.ts +77 -40
  44. package/dist/tasks/enableL1TokenAcrossEcosystem.js +4 -4
  45. package/dist/test/svm/SvmSpoke.Deposit.js +21 -3
  46. package/dist/test/svm/SvmSpoke.Fill.AcrossPlus.js +3 -3
  47. package/dist/test/svm/SvmSpoke.Fill.js +3 -3
  48. package/dist/test/svm/SvmSpoke.SlowFill.AcrossPlus.js +3 -3
  49. package/dist/test/svm/SvmSpoke.SlowFill.js +2 -2
  50. package/dist/test/svm/SvmSpoke.common.js +1 -1
  51. package/package.json +2 -2
package/README.md CHANGED
@@ -31,17 +31,20 @@ Note if you get build issues on the initial `yarn` command try downgrading to no
31
31
 
32
32
  ```shell
33
33
  yarn
34
- yarn build # Will build all code. Compile solidity & rust, generate ts outputs
34
+ yarn build # Will build all code. Compile solidity & rust (local toolchain), generate ts outputs
35
+ yarn build-verified # Will build all code. Compile solidity & rust (verified docker build), generate ts outputs
35
36
  ```
36
37
 
37
38
  ## Test
38
39
 
39
40
  ```shell
40
- yarn test # Run all unit tests without gas analysis
41
+ yarn test # Run all unit tests without gas analysis, using local toolchain SVM build
42
+ yarn test-verified # Run all unit tests (without gas analysis) with verified SVM docker build
41
43
  yarn test:gas-analytics # Run only tests that count gas costs
42
44
  yarn test:report-gas # Run unit tests with hardhat-gas-reporter enabled
43
45
  yarn test-evm # Only test EVM code
44
- yarn test-svm # Only test SVM code
46
+ yarn test-svm # Only test SVM code (local toolchain build)
47
+ yarn test-svm-solana-verify # Only test SVM code (verified docker build)
45
48
  ```
46
49
 
47
50
  ## Lint
@@ -56,11 +59,191 @@ yarn lint-fix
56
59
 
57
60
  ## Deploy and Verify
58
61
 
62
+ ### EVM
63
+
59
64
  ```shell
60
65
  NODE_URL_1=https://mainnet.infura.com/xxx yarn hardhat deploy --tags HubPool --network mainnet
61
66
  ETHERSCAN_API_KEY=XXX yarn hardhat etherscan-verify --network mainnet --license AGPL-3.0 --force-license --solc-input
62
67
  ```
63
68
 
69
+ ### SVM
70
+
71
+ Before deploying for the first time make sure all program IDs in `lib.rs` and `Anchor.toml` are the same as listed when running `anchor keys list`. If not, update them to match the deployment keypairs under `target/deploy/` and commit the changes.
72
+
73
+ Make sure to use the verified docker binaries that can be built:
74
+
75
+ ```shell
76
+ unset IS_TEST # Ensures the production build is used (not the test feature)
77
+ yarn build-svm-solana-verify # Builds verified SVM binaries
78
+ yarn generate-svm-artifacts # Builds IDLs
79
+ ```
80
+
81
+ Export required environment variables, e.g.:
82
+
83
+ ```shell
84
+ export RPC_URL=https://api.devnet.solana.com
85
+ export KEYPAIR=~/.config/solana/dev-wallet.json
86
+ export PROGRAM=svm_spoke # Also repeat the deployment process for multicall_handler
87
+ export PROGRAM_ID=$(cat target/idl/$PROGRAM.json | jq -r ".address")
88
+ export MULTISIG= # Export the Squads vault, not the multisig address!
89
+ ```
90
+
91
+ For the initial deployment also need these:
92
+
93
+ ```shell
94
+ export SVM_CHAIN_ID=$(cast to-dec $(cast shr $(cast shl $(cast keccak solana-devnet) 208) 208))
95
+ export HUB_POOL=0x14224e63716afAcE30C9a417E0542281869f7d9e # This is for sepolia, update for mainnet
96
+ export DEPOSIT_QUOTE_TIME_BUFFER=3600
97
+ export FILL_DEADLINE_BUFFER=21600
98
+ export MAX_LEN=$(( 2 * $(stat -c %s target/deploy/$PROGRAM.so) )) # Reserve twice the size of the program for future upgrades
99
+ ```
100
+
101
+ #### Initial deployment
102
+
103
+ Deploy the program and set the upgrade authority to the multisig:
104
+
105
+ ```shell
106
+ solana program deploy \
107
+ --url $RPC_URL \
108
+ --keypair $KEYPAIR \
109
+ --program-id target/deploy/$PROGRAM-keypair.json \
110
+ --max-len $MAX_LEN \
111
+ --with-compute-unit-price 50000 \
112
+ --max-sign-attempts 100 \
113
+ --use-rpc \
114
+ target/deploy/$PROGRAM.so
115
+ solana program set-upgrade-authority \
116
+ --url $RPC_URL \
117
+ --keypair $KEYPAIR \
118
+ --skip-new-upgrade-authority-signer-check \
119
+ $PROGRAM_ID \
120
+ --new-upgrade-authority $MULTISIG
121
+ ```
122
+
123
+ Update and commit `deployments/deployments.json` with the deployed program ID and deployment slot.
124
+
125
+ Upload the IDL and set the upgrade authority to the multisig:
126
+
127
+ ```shell
128
+ anchor idl init \
129
+ --provider.cluster $RPC_URL \
130
+ --provider.wallet $KEYPAIR \
131
+ --filepath target/idl/$PROGRAM.json \
132
+ $PROGRAM_ID
133
+ anchor idl set-authority \
134
+ --provider.cluster $RPC_URL \
135
+ --provider.wallet $KEYPAIR \
136
+ --program-id $PROGRAM_ID \
137
+ --new-authority $MULTISIG
138
+ ```
139
+
140
+ `svm_spoke` also requires initialization and transfer of ownership on the first deployment:
141
+
142
+ ```shell
143
+ anchor run initialize \
144
+ --provider.cluster $RPC_URL \
145
+ --provider.wallet $KEYPAIR -- \
146
+ --chainId $SVM_CHAIN_ID \
147
+ --remoteDomain 0 \
148
+ --crossDomainAdmin $HUB_POOL \
149
+ --svmAdmin $MULTISIG \
150
+ --depositQuoteTimeBuffer $DEPOSIT_QUOTE_TIME_BUFFER \
151
+ --fillDeadlineBuffer $FILL_DEADLINE_BUFFER
152
+ ```
153
+
154
+ Create the vault for accepting deposits, e.g.:
155
+
156
+ ```shell
157
+ export MINT=4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU # This is USDC on devnet, update with address for mainnet
158
+ anchor run createVault \
159
+ --provider.cluster $RPC_URL \
160
+ --provider.wallet $KEYPAIR -- \
161
+ --originToken $MINT
162
+ ```
163
+
164
+ #### Upgrades
165
+
166
+ Initiate the program upgrade:
167
+
168
+ ```shell
169
+ solana program write-buffer \
170
+ --url $RPC_URL \
171
+ --keypair $KEYPAIR \
172
+ --with-compute-unit-price 50000 \
173
+ --max-sign-attempts 100 \
174
+ --use-rpc \
175
+ target/deploy/$PROGRAM.so
176
+ export BUFFER= # Export the logged buffer address from the command above
177
+ solana program set-buffer-authority \
178
+ --url $RPC_URL \
179
+ --keypair $KEYPAIR \
180
+ $BUFFER \
181
+ --new-buffer-authority $MULTISIG
182
+ ```
183
+
184
+ Add the program ID to Squads multisig (`https://devnet.squads.so/` for devnet and `https://app.squads.so/` for mainnet) in the Developers/Programs section. Then add the upgrade filling in the buffer address and buffer refund. After creating the upgrade verify the buffer authority as prompted and proceed with initiating the upgrade. Once all required signers have approved, execute the upgrade in the transactions section.
185
+
186
+ Start the IDL upgrade by writing it to the buffer:
187
+
188
+ ```shell
189
+ anchor idl write-buffer \
190
+ --provider.cluster $RPC_URL \
191
+ --provider.wallet $KEYPAIR \
192
+ --filepath target/idl/$PROGRAM.json \
193
+ $PROGRAM_ID
194
+ export IDL_BUFFER= # Export the logged IDL buffer address from the command above
195
+ anchor idl set-authority \
196
+ --provider.cluster $RPC_URL \
197
+ --provider.wallet $KEYPAIR \
198
+ --program-id $PROGRAM_ID \
199
+ --new-authority $MULTISIG \
200
+ $IDL_BUFFER
201
+ ```
202
+
203
+ Construct the multisig transaction for finalizing the IDL upgrade. Copy the printed base58 encoded transaction from below command and import it into the Squads multisig for approval and execution:
204
+
205
+ ```shell
206
+ anchor run squadsIdlUpgrade -- \
207
+ --programId $PROGRAM_ID \
208
+ --idlBuffer $IDL_BUFFER \
209
+ --multisig $MULTISIG \
210
+ --closeRecipient $(solana address --keypair $KEYPAIR)
211
+ ```
212
+
213
+ #### Verify
214
+
215
+ Start with verifying locally that the deployed program matches the source code of the public repository:
216
+
217
+ ```shell
218
+ solana-verify verify-from-repo \
219
+ --url $RPC_URL \
220
+ --program-id $PROGRAM_ID \
221
+ --library-name $PROGRAM \
222
+ https://github.com/across-protocol/contracts
223
+ ```
224
+
225
+ When prompted, don't yet upload the verification data to the blockchain as that should be done by the multisig. Proceed with creating the upload transaction and then import and sign/execute it in the Squads multisig:
226
+
227
+ ```shell
228
+ solana-verify export-pda-tx \
229
+ --url $RPC_URL \
230
+ --program-id $PROGRAM_ID \
231
+ --library-name $PROGRAM \
232
+ --uploader $MULTISIG \
233
+ https://github.com/across-protocol/contracts
234
+ ```
235
+
236
+ Note that the initial upload transaction might fail if the multisig vault does not have enough SOL for PDA creation. In that case, transfer the required funds to the multisig vault before executing the upload transaction.
237
+
238
+ Finally, submit the verification to OtterSec API (only works on mainnet):
239
+
240
+ ```shell
241
+ solana-verify remote submit-job \
242
+ --url $RPC_URL \
243
+ --program-id $PROGRAM_ID \
244
+ --uploader $MULTISIG
245
+ ```
246
+
64
247
  ## Miscellaneous topics
65
248
 
66
249
  ### Manually Finalizing Scroll Claims from L2 -> L1 (Mainnet | Sepolia)
@@ -34,7 +34,8 @@
34
34
  "Ink_Adapter": { "address": "0x7e90a40c7519b041a7df6498fbf5662e8cfc61d2", "blockNumber": 21438590 },
35
35
  "Cher_Adapter": { "address": "0x0c9d064523177dBB55CFE52b9D0c485FBFc35FD2", "blockNumber": 21597341 },
36
36
  "Lens_Adapter": { "address": "0x63AC22131eD457aeCbD63e6c4C7eeC7BBC74fF1F", "blockNumber": 22167069 },
37
- "DoctorWho_Adapter": { "address": "0xFADcC43096756e1527306FD92982FEbBe3c629Fa", "blockNumber": 21773451 }
37
+ "DoctorWho_Adapter": { "address": "0xFADcC43096756e1527306FD92982FEbBe3c629Fa", "blockNumber": 21773451 },
38
+ "Solana_Adapter": { "address": "0x1E22A3146439C68A2d247448372AcAEe9E201AB1", "blockNumber": 22566473 }
38
39
  },
39
40
  "10": {
40
41
  "SpokePool": { "address": "0x6f26Bf09B1C792e3228e5467807a900A503c0281", "blockNumber": 93903076 },
@@ -169,7 +170,8 @@
169
170
  "Lisk_Adapter": { "address": "0x13a8B1D6443016424e2b8Bac40dD884Ee679AFc4", "blockNumber": 6226289 },
170
171
  "Lens_Adapter": { "address": "0x8fac6F764ae0b4F632FE2E6c938ED5637E629ff2", "blockNumber": 7448085 },
171
172
  "Blast_Adapter": { "address": "0x09500Ffd743e01B4146a4BA795231Ca7Ca37819f", "blockNumber": 6233857 },
172
- "DoctorWho_Adapter": { "address": "0x2b482aFb675e1F231521d5E56770ce4aac592246", "blockNumber": 7698546 }
173
+ "DoctorWho_Adapter": { "address": "0x2b482aFb675e1F231521d5E56770ce4aac592246", "blockNumber": 7698546 },
174
+ "Solana_Adapter": { "address": "0x9b2c2f3fD98cF8468715Be31155cc053C56f822A", "blockNumber": 8409722 }
173
175
  },
174
176
  "37111": {
175
177
  "SpokePool": { "address": "0x6A0a7f39530923911832Dd60667CE5da5449967B", "blockNumber": 156275 },
@@ -206,8 +208,8 @@
206
208
  },
207
209
  "133268194659241": {
208
210
  "SvmSpoke": {
209
- "address": "JAZWcGrpSWNPTBj8QtJ9UyQqhJCDhG9GJkDeMf5NQBiq",
210
- "blockNumber": 356313770
211
+ "address": "4Jg83Lhggz2EdzSsDX2NVcEK56F25jJKK5btUbHAU4Xu",
212
+ "blockNumber": 388467389
211
213
  },
212
214
  "MulticallHandler": {
213
215
  "address": "Fk1RpqsfeWt8KnFCTW9NQVdVxYvxuqjGn6iPB9wrmM8h",
@@ -224,8 +226,8 @@
224
226
  },
225
227
  "34268394551451": {
226
228
  "SvmSpoke": {
227
- "address": "JAZWcGrpSWNPTBj8QtJ9UyQqhJCDhG9GJkDeMf5NQBiq",
228
- "blockNumber": 317101505
229
+ "address": "4Jg83Lhggz2EdzSsDX2NVcEK56F25jJKK5btUbHAU4Xu",
230
+ "blockNumber": 347611576
229
231
  },
230
232
  "MulticallHandler": {
231
233
  "address": "Fk1RpqsfeWt8KnFCTW9NQVdVxYvxuqjGn6iPB9wrmM8h",
@@ -82,7 +82,7 @@ async function closeFillPda(eventData, seed) {
82
82
  exclusiveRelayer: new web3_js_1.PublicKey(eventData.exclusiveRelayer),
83
83
  inputToken: new web3_js_1.PublicKey(eventData.inputToken),
84
84
  outputToken: new web3_js_1.PublicKey(eventData.outputToken),
85
- inputAmount: new anchor_1.BN(eventData.inputAmount),
85
+ inputAmount: eventData.inputAmount,
86
86
  outputAmount: new anchor_1.BN(eventData.outputAmount),
87
87
  originChainId: new anchor_1.BN(eventData.originChainId),
88
88
  depositId: eventData.depositId,
@@ -45,11 +45,11 @@ const programId = program.programId;
45
45
  console.log("SVM-Spoke Program ID:", programId.toString());
46
46
  // Parse arguments
47
47
  const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
48
- .option("seed", { type: "string", demandOption: true, describe: "Seed for the state account PDA" })
48
+ .option("seed", { type: "string", demandOption: false, describe: "Seed for the state account PDA" })
49
49
  .option("originToken", { type: "string", demandOption: true, describe: "Origin token public key" }).argv;
50
50
  async function createVault() {
51
51
  const resolvedArgv = await argv;
52
- const seed = new anchor_1.BN(resolvedArgv.seed);
52
+ const seed = resolvedArgv.seed ? new anchor_1.BN(resolvedArgv.seed) : web3_v1_1.SOLANA_SPOKE_STATE_SEED;
53
53
  const originToken = new web3_js_1.PublicKey(resolvedArgv.originToken);
54
54
  // Define the state account PDA
55
55
  const [statePda, _] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("state"), seed.toArrayLike(Buffer, "le", 8)], programId);
@@ -48,10 +48,10 @@ const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
48
48
  .option("exclusiveRelayer", { type: "string", demandOption: false, describe: "Exclusive relayer public key" })
49
49
  .option("inputToken", { type: "string", demandOption: true, describe: "Input token public key" })
50
50
  .option("outputToken", { type: "string", demandOption: true, describe: "Output token public key" })
51
- .option("inputAmount", { type: "number", demandOption: true, describe: "Input amount" })
51
+ .option("inputAmount", { type: "string", demandOption: true, describe: "Input amount" })
52
52
  .option("outputAmount", { type: "number", demandOption: true, describe: "Output amount" })
53
53
  .option("originChainId", { type: "string", demandOption: true, describe: "Origin chain ID" })
54
- .option("depositId", { type: "array", demandOption: true, describe: "Deposit ID" })
54
+ .option("depositId", { type: "string", demandOption: true, describe: "Deposit ID" })
55
55
  .option("fillDeadline", { type: "number", demandOption: false, describe: "Fill deadline" })
56
56
  .option("exclusivityDeadline", { type: "number", demandOption: false, describe: "Exclusivity deadline" })
57
57
  .option("repaymentChain", { type: "number", demandOption: false, description: "Repayment chain ID" })
@@ -65,10 +65,10 @@ async function fillRelayToRandom() {
65
65
  const exclusiveRelayer = new web3_js_1.PublicKey(resolvedArgv.exclusiveRelayer || web3_js_1.PublicKey.default.toString());
66
66
  const inputToken = new web3_js_1.PublicKey(resolvedArgv.inputToken);
67
67
  const outputToken = new web3_js_1.PublicKey(resolvedArgv.outputToken);
68
- const inputAmount = new anchor_1.BN(resolvedArgv.inputAmount);
68
+ const inputAmount = (0, web3_v1_1.intToU8Array32)(new anchor_1.BN(resolvedArgv.inputAmount));
69
69
  const outputAmount = new anchor_1.BN(resolvedArgv.outputAmount);
70
70
  const originChainId = new anchor_1.BN(resolvedArgv.originChainId);
71
- const depositId = resolvedArgv.depositId.map((id) => id); // Ensure depositId is an array of BN
71
+ const depositId = (0, web3_v1_1.intToU8Array32)(new anchor_1.BN(resolvedArgv.depositId));
72
72
  const fillDeadline = resolvedArgv.fillDeadline || Math.floor(Date.now() / 1000) + 60; // Current time + 1 minute
73
73
  const exclusivityDeadline = resolvedArgv.exclusivityDeadline || Math.floor(Date.now() / 1000) + 30; // Current time + 30 seconds
74
74
  const repaymentChain = new anchor_1.BN(resolvedArgv.repaymentChain || 1);
@@ -148,8 +148,9 @@ async function fillRelayToRandom() {
148
148
  key,
149
149
  value: key === "message" ? value.toString("hex") : value.toString(),
150
150
  })));
151
- // Delegate state PDA to pull relayer tokens.
152
- const approveInstruction = await (0, spl_token_1.createApproveCheckedInstruction)(relayerTokenAccount, outputToken, statePda, signer.publicKey, BigInt(relayData.outputAmount.toString()), tokenDecimals, undefined, spl_token_1.TOKEN_PROGRAM_ID);
151
+ // Delegate to pull relayer tokens.
152
+ const delegatePda = (0, web3_v1_1.getFillRelayDelegatePda)(relayHashUint8Array, repaymentChain, repaymentAddress, program.programId).pda;
153
+ const approveInstruction = await (0, spl_token_1.createApproveCheckedInstruction)(relayerTokenAccount, outputToken, delegatePda, signer.publicKey, BigInt(relayData.outputAmount.toString()), tokenDecimals, undefined, spl_token_1.TOKEN_PROGRAM_ID);
153
154
  // Prepare fill instruction as we will need to use Address Lookup Table (ALT).
154
155
  const fillRelayValues = [relayHash, relayData, repaymentChain, repaymentAddress];
155
156
  if (bufferParams) {
@@ -162,7 +163,7 @@ async function fillRelayToRandom() {
162
163
  const fillAccounts = {
163
164
  state: statePda,
164
165
  signer: signer.publicKey,
165
- delegate: (0, web3_v1_1.getFillRelayDelegatePda)(relayHashUint8Array, repaymentChain, repaymentAddress, program.programId).pda,
166
+ delegate: delegatePda,
166
167
  instructionParams,
167
168
  mint: outputToken,
168
169
  relayerTokenAccount,
@@ -77,7 +77,7 @@ async function findFillStatusPda() {
77
77
  exclusiveRelayer: convertAddress(resolvedArgv.exclusive_relayer),
78
78
  inputToken: convertAddress(resolvedArgv.input_token),
79
79
  outputToken: convertAddress(resolvedArgv.output_token),
80
- inputAmount: new anchor_1.BN(resolvedArgv.input_amount),
80
+ inputAmount: (0, web3_v1_1.intToU8Array32)(new anchor_1.BN(resolvedArgv.input_amount)),
81
81
  outputAmount: new anchor_1.BN(resolvedArgv.output_amount),
82
82
  originChainId: new anchor_1.BN(resolvedArgv.origin_chain_id),
83
83
  depositId: parseStringToUint8Array(resolvedArgv.deposit_id),
@@ -40,11 +40,12 @@ const program = (0, web3_v1_1.getSpokePoolProgram)(provider);
40
40
  const programId = program.programId;
41
41
  // Parse arguments
42
42
  const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
43
- .option("seed", { type: "string", demandOption: true, describe: "Seed for the state account PDA" })
44
- .option("initNumbDeposits", { type: "string", demandOption: true, describe: "Init numb of deposits" })
43
+ .option("seed", { type: "string", demandOption: false, describe: "Seed for the state account PDA" })
44
+ .option("initNumbDeposits", { type: "string", demandOption: false, describe: "Init numb of deposits" })
45
45
  .option("chainId", { type: "string", demandOption: true, describe: "Chain ID" })
46
46
  .option("remoteDomain", { type: "number", demandOption: true, describe: "CCTP domain for Mainnet Ethereum" })
47
47
  .option("crossDomainAdmin", { type: "string", demandOption: true, describe: "HubPool on Mainnet Ethereum" })
48
+ .option("svmAdmin", { type: "string", demandOption: false, describe: "SVM admin" })
48
49
  .option("depositQuoteTimeBuffer", {
49
50
  type: "number",
50
51
  demandOption: false,
@@ -54,16 +55,17 @@ const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
54
55
  .option("fillDeadlineBuffer", {
55
56
  type: "number",
56
57
  demandOption: false,
57
- default: 3600 * 4,
58
+ default: 3600 * 6,
58
59
  describe: "Fill deadline buffer",
59
60
  }).argv;
60
61
  async function initialize() {
61
62
  const resolvedArgv = await argv;
62
- const seed = new anchor_1.BN(resolvedArgv.seed);
63
- const initialNumberOfDeposits = new anchor_1.BN(resolvedArgv.initNumbDeposits);
63
+ const seed = resolvedArgv.seed ? new anchor_1.BN(resolvedArgv.seed) : web3_v1_1.SOLANA_SPOKE_STATE_SEED;
64
+ const initialNumberOfDeposits = resolvedArgv.initNumbDeposits ? new anchor_1.BN(resolvedArgv.initNumbDeposits) : new anchor_1.BN(0);
64
65
  const chainId = new anchor_1.BN(resolvedArgv.chainId);
65
66
  const remoteDomain = resolvedArgv.remoteDomain;
66
67
  const crossDomainAdmin = (0, web3_v1_1.evmAddressToPublicKey)(resolvedArgv.crossDomainAdmin); // Use the function to cast the value
68
+ const svmAdmin = resolvedArgv.svmAdmin ? new web3_js_1.PublicKey(resolvedArgv.svmAdmin) : provider.wallet.publicKey;
67
69
  const depositQuoteTimeBuffer = resolvedArgv.depositQuoteTimeBuffer;
68
70
  const fillDeadlineBuffer = resolvedArgv.fillDeadlineBuffer;
69
71
  // Define the state account PDA
@@ -82,6 +84,7 @@ async function initialize() {
82
84
  { Property: "chainId", Value: chainId.toString() },
83
85
  { Property: "remoteDomain", Value: remoteDomain.toString() },
84
86
  { Property: "crossDomainAdmin", Value: crossDomainAdmin.toString() },
87
+ { Property: "svmAdmin", Value: svmAdmin.toString() },
85
88
  { Property: "depositQuoteTimeBuffer", Value: depositQuoteTimeBuffer.toString() },
86
89
  { Property: "fillDeadlineBuffer", Value: fillDeadlineBuffer.toString() },
87
90
  ]);
@@ -93,6 +96,17 @@ async function initialize() {
93
96
  })
94
97
  .rpc();
95
98
  console.log("Transaction signature:", tx);
99
+ if (!svmAdmin.equals(provider.wallet.publicKey)) {
100
+ console.log("Transferring ownership to SVM admin...");
101
+ const tx = await program.methods
102
+ .transferOwnership(svmAdmin)
103
+ .accountsPartial({
104
+ state: statePda,
105
+ signer: signer,
106
+ })
107
+ .rpc();
108
+ console.log("Transfer ownership transaction signature:", tx);
109
+ }
96
110
  }
97
111
  // Run the initialize function
98
112
  initialize();
@@ -45,7 +45,7 @@ const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
45
45
  .option("recipient", { type: "string", demandOption: true, describe: "Recipient public key" })
46
46
  .option("outputToken", { type: "string", demandOption: true, describe: "Output token public key" })
47
47
  .option("inputAmount", { type: "number", demandOption: true, describe: "Input amount" })
48
- .option("outputAmount", { type: "number", demandOption: true, describe: "Output amount" })
48
+ .option("outputAmount", { type: "string", demandOption: true, describe: "Output amount" })
49
49
  .option("destinationChainId", { type: "string", demandOption: true, describe: "Destination chain ID" })
50
50
  .option("integratorId", { type: "string", demandOption: false, describe: "integrator ID" }).argv;
51
51
  async function nativeDeposit() {
@@ -55,7 +55,7 @@ async function nativeDeposit() {
55
55
  const inputToken = spl_token_1.NATIVE_MINT;
56
56
  const outputToken = new web3_js_1.PublicKey(resolvedArgv.outputToken);
57
57
  const inputAmount = new anchor_1.BN(resolvedArgv.inputAmount);
58
- const outputAmount = new anchor_1.BN(resolvedArgv.outputAmount);
58
+ const outputAmount = (0, web3_v1_1.intToU8Array32)(new anchor_1.BN(resolvedArgv.outputAmount));
59
59
  const destinationChainId = new anchor_1.BN(resolvedArgv.destinationChainId);
60
60
  const exclusiveRelayer = web3_js_1.PublicKey.default;
61
61
  const quoteTimestamp = Math.floor(Date.now() / 1000) - 1;
@@ -79,7 +79,7 @@ async function nativeDeposit() {
79
79
  { property: "inputToken", value: inputToken.toString() },
80
80
  { property: "outputToken", value: outputToken.toString() },
81
81
  { property: "inputAmount", value: inputAmount.toString() },
82
- { property: "outputAmount", value: outputAmount.toString() },
82
+ { property: "outputAmount", value: (0, web3_v1_1.u8Array32ToInt)(outputAmount).toString() },
83
83
  { property: "destinationChainId", value: destinationChainId.toString() },
84
84
  { property: "quoteTimestamp", value: quoteTimestamp.toString() },
85
85
  { property: "fillDeadline", value: fillDeadline.toString() },
@@ -109,17 +109,38 @@ async function nativeDeposit() {
109
109
  const lastIxs = existingTokenAccount
110
110
  ? []
111
111
  : [(0, spl_token_1.createCloseAccountInstruction)(userTokenAccount, signer.publicKey, signer.publicKey)];
112
+ const depositData = {
113
+ depositor: signer.publicKey,
114
+ recipient,
115
+ inputToken,
116
+ outputToken,
117
+ inputAmount,
118
+ outputAmount,
119
+ destinationChainId,
120
+ exclusiveRelayer,
121
+ quoteTimestamp: new anchor_1.BN(quoteTimestamp),
122
+ fillDeadline: new anchor_1.BN(fillDeadline),
123
+ exclusivityParameter: new anchor_1.BN(exclusivityDeadline),
124
+ message,
125
+ };
126
+ const delegatePda = (0, web3_v1_1.getDepositPda)(depositData, program.programId);
112
127
  // Delegate state PDA to pull depositor tokens.
113
- const approveIx = await (0, spl_token_1.createApproveCheckedInstruction)(userTokenAccount, inputToken, statePda, signer.publicKey, BigInt(inputAmount.toString()), tokenDecimals, undefined, spl_token_1.TOKEN_PROGRAM_ID);
114
- const depositIx = await program.methods.deposit(signer.publicKey, recipient, inputToken, outputToken, inputAmount, outputAmount, destinationChainId, exclusiveRelayer, quoteTimestamp, fillDeadline, exclusivityDeadline, message)
115
- .accounts({
128
+ const approveIx = await (0, spl_token_1.createApproveCheckedInstruction)(userTokenAccount, inputToken, delegatePda, signer.publicKey, BigInt(inputAmount.toString()), tokenDecimals, undefined, spl_token_1.TOKEN_PROGRAM_ID);
129
+ const depositAccounts = {
116
130
  state: statePda,
131
+ delegate: delegatePda,
117
132
  signer: signer.publicKey,
118
- userTokenAccount,
133
+ depositorTokenAccount: userTokenAccount,
119
134
  vault: vault,
120
- tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
121
135
  mint: inputToken,
122
- })
136
+ tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
137
+ associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
138
+ systemProgram: web3_js_1.SystemProgram.programId,
139
+ program: programId,
140
+ };
141
+ const depositIx = await program.methods
142
+ .deposit(signer.publicKey, recipient, inputToken, outputToken, inputAmount, outputAmount, destinationChainId, exclusiveRelayer, quoteTimestamp, fillDeadline, exclusivityDeadline, message)
143
+ .accounts(depositAccounts)
123
144
  .instruction();
124
145
  // Create the deposit transaction
125
146
  const depositTx = new web3_js_1.Transaction().add(transferIx, syncOrCreateIx, approveIx, depositIx, ...lastIxs);
@@ -65,7 +65,7 @@ async function queryEvents() {
65
65
  const eventName = argv.eventName || "any";
66
66
  const events = await (0, web3_v1_1.readProgramEvents)(provider.connection, program, "confirmed");
67
67
  const filteredEvents = events.filter((event) => (eventName == "any" ? true : event.name == eventName));
68
- const formattedEvents = filteredEvents.map((event) => (0, web3_v1_1.stringifyCpiEvent)(event));
68
+ const formattedEvents = filteredEvents.map((event) => (0, web3_v1_1.stringifyCpiEvent)(event.data, event.name));
69
69
  console.log(JSON.stringify(formattedEvents, null, 2));
70
70
  }
71
71
  // Run the queryEvents function
@@ -49,7 +49,7 @@ async function queryEvents() {
49
49
  const rpcSubscriptions = (0, kit_1.createSolanaRpcSubscriptions)(rpcEndpoint.replace(/^http(s?):\/\//i, (_m, s) => `ws${s ?? ""}://`));
50
50
  const events = await (0, svm_2.readProgramEvents)({ rpc, rpcSubscriptions }, (0, kit_1.address)(programId), svm_1.SvmSpokeIdl, "confirmed");
51
51
  const filteredEvents = events.filter((e) => (eventName === "any" ? true : e.name === eventName));
52
- const formattedEvents = filteredEvents.map(web3_v1_1.stringifyCpiEvent);
52
+ const formattedEvents = filteredEvents.map((event) => (0, web3_v1_1.stringifyCpiEvent)(event.data, event.name));
53
53
  console.log(JSON.stringify(formattedEvents, null, 2));
54
54
  }
55
55
  // Run the queryEvents function
@@ -67,7 +67,7 @@ async function queryFills() {
67
67
  console.table([
68
68
  { Property: "inputToken", Value: (0, web3_v1_1.strPublicKey)(event.data.inputToken) },
69
69
  { Property: "outputToken", Value: (0, web3_v1_1.strPublicKey)(event.data.outputToken) },
70
- { Property: "inputAmount", Value: event.data.inputAmount.toString() },
70
+ { Property: "inputAmount", Value: (0, web3_v1_1.u8Array32ToInt)(event.data.inputAmount).toString() },
71
71
  { Property: "outputAmount", Value: event.data.outputAmount.toString() },
72
72
  { Property: "repaymentChainId", Value: event.data.repaymentChainId.toString() },
73
73
  { Property: "originChainId", Value: event.data.originChainId.toString() },
@@ -47,7 +47,7 @@ const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
47
47
  .option("inputToken", { type: "string", demandOption: true, describe: "Input token public key" })
48
48
  .option("outputToken", { type: "string", demandOption: true, describe: "Output token public key" })
49
49
  .option("inputAmount", { type: "number", demandOption: true, describe: "Input amount" })
50
- .option("outputAmount", { type: "number", demandOption: true, describe: "Output amount" })
50
+ .option("outputAmount", { type: "string", demandOption: true, describe: "Output amount" })
51
51
  .option("destinationChainId", { type: "string", demandOption: true, describe: "Destination chain ID" })
52
52
  .option("integratorId", { type: "string", demandOption: false, describe: "integrator ID" }).argv;
53
53
  async function deposit() {
@@ -57,7 +57,7 @@ async function deposit() {
57
57
  const inputToken = new web3_js_1.PublicKey(resolvedArgv.inputToken);
58
58
  const outputToken = new web3_js_1.PublicKey(resolvedArgv.outputToken);
59
59
  const inputAmount = new anchor_1.BN(resolvedArgv.inputAmount);
60
- const outputAmount = new anchor_1.BN(resolvedArgv.outputAmount);
60
+ const outputAmount = (0, web3_v1_1.intToU8Array32)(new anchor_1.BN(resolvedArgv.outputAmount));
61
61
  const destinationChainId = new anchor_1.BN(resolvedArgv.destinationChainId);
62
62
  const exclusiveRelayer = web3_js_1.PublicKey.default;
63
63
  const quoteTimestamp = Math.floor(Date.now() / 1000) - 1;
@@ -79,7 +79,7 @@ async function deposit() {
79
79
  { property: "inputToken", value: inputToken.toString() },
80
80
  { property: "outputToken", value: outputToken.toString() },
81
81
  { property: "inputAmount", value: inputAmount.toString() },
82
- { property: "outputAmount", value: outputAmount.toString() },
82
+ { property: "outputAmount", value: (0, web3_v1_1.u8Array32ToInt)(outputAmount).toString() },
83
83
  { property: "destinationChainId", value: destinationChainId.toString() },
84
84
  { property: "quoteTimestamp", value: quoteTimestamp.toString() },
85
85
  { property: "fillDeadline", value: fillDeadline.toString() },
@@ -93,17 +93,38 @@ async function deposit() {
93
93
  ]);
94
94
  const userTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(inputToken, signer.publicKey);
95
95
  const tokenDecimals = (await (0, spl_token_1.getMint)(provider.connection, inputToken, undefined, spl_token_1.TOKEN_PROGRAM_ID)).decimals;
96
+ const depositData = {
97
+ depositor: signer.publicKey,
98
+ recipient,
99
+ inputToken,
100
+ outputToken,
101
+ inputAmount,
102
+ outputAmount,
103
+ destinationChainId,
104
+ exclusiveRelayer,
105
+ quoteTimestamp: new anchor_1.BN(quoteTimestamp),
106
+ fillDeadline: new anchor_1.BN(fillDeadline),
107
+ exclusivityParameter: new anchor_1.BN(exclusivityDeadline),
108
+ message,
109
+ };
110
+ const delegatePda = (0, web3_v1_1.getDepositPda)(depositData, program.programId);
96
111
  // Delegate state PDA to pull depositor tokens.
97
- const approveIx = await (0, spl_token_1.createApproveCheckedInstruction)(userTokenAccount, inputToken, statePda, signer.publicKey, BigInt(inputAmount.toString()), tokenDecimals, undefined, spl_token_1.TOKEN_PROGRAM_ID);
98
- const depositIx = await program.methods.deposit(signer.publicKey, recipient, inputToken, outputToken, inputAmount, outputAmount, destinationChainId, exclusiveRelayer, quoteTimestamp, fillDeadline, exclusivityDeadline, message)
99
- .accounts({
112
+ const approveIx = await (0, spl_token_1.createApproveCheckedInstruction)(userTokenAccount, inputToken, delegatePda, signer.publicKey, BigInt(inputAmount.toString()), tokenDecimals, undefined, spl_token_1.TOKEN_PROGRAM_ID);
113
+ const depositAccounts = {
100
114
  state: statePda,
115
+ delegate: delegatePda,
101
116
  signer: signer.publicKey,
102
- userTokenAccount,
117
+ depositorTokenAccount: userTokenAccount,
103
118
  vault: vault,
104
- tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
105
119
  mint: inputToken,
106
- })
120
+ tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
121
+ associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
122
+ systemProgram: web3_js_1.SystemProgram.programId,
123
+ program: programId,
124
+ };
125
+ const depositIx = await program.methods
126
+ .deposit(signer.publicKey, recipient, inputToken, outputToken, inputAmount, outputAmount, destinationChainId, exclusiveRelayer, quoteTimestamp, fillDeadline, exclusivityDeadline, message)
127
+ .accounts(depositAccounts)
107
128
  .instruction();
108
129
  // Create a custom instruction with arbitrary data
109
130
  const depositTx = new web3_js_1.Transaction().add(approveIx, depositIx);
@@ -68,20 +68,27 @@ async function testBundleLogic() {
68
68
  const tokenDecimals = (await (0, spl_token_1.getMint)(provider.connection, inputToken, undefined, spl_token_1.TOKEN_PROGRAM_ID)).decimals;
69
69
  // Use program.methods.deposit to send tokens to the spoke. note this is NOT a valid deposit, we just want to
70
70
  // seed tokens into the spoke to test repayment.
71
- // Delegate state PDA to pull depositor tokens.
72
71
  const inputAmount = amounts.reduce((acc, amount) => acc.add(amount), new anchor_1.BN(0));
73
- const approveIx = await (0, spl_token_1.createApproveCheckedInstruction)(userTokenAccount, inputToken, statePda, signer.publicKey, BigInt(inputAmount.toString()), tokenDecimals, undefined, spl_token_1.TOKEN_PROGRAM_ID);
74
- const depositIx = await program.methods.deposit(signer.publicKey, signer.publicKey, // recipient is the signer for this example
75
- inputToken, inputToken, // Re-use inputToken as outputToken. does not matter for this deposit.
76
- inputAmount, new anchor_1.BN(0), new anchor_1.BN(11155111), // destinationChainId.
77
- web3_js_1.PublicKey.default, // exclusiveRelayer
78
- Math.floor(Date.now() / 1000) - 1, // quoteTimestamp
79
- Math.floor(Date.now() / 1000) + 3600, // fillDeadline
80
- 0, // exclusivityDeadline
81
- Buffer.from([]) // message
82
- )
72
+ const depositData = {
73
+ depositor: signer.publicKey,
74
+ recipient: signer.publicKey, // recipient is the signer for this example
75
+ inputToken,
76
+ outputToken: inputToken, // Re-use inputToken as outputToken. does not matter for this deposit.
77
+ inputAmount,
78
+ outputAmount: (0, web3_v1_1.intToU8Array32)(inputAmount),
79
+ destinationChainId: new anchor_1.BN(11155111),
80
+ exclusiveRelayer: web3_js_1.PublicKey.default,
81
+ quoteTimestamp: new anchor_1.BN(Math.floor(Date.now() / 1000) - 1),
82
+ fillDeadline: new anchor_1.BN(Math.floor(Date.now() / 1000) + 3600),
83
+ exclusivityParameter: new anchor_1.BN(0),
84
+ message: Buffer.from([]),
85
+ };
86
+ const delegatePda = (0, web3_v1_1.getDepositPda)(depositData, program.programId);
87
+ const approveIx = await (0, spl_token_1.createApproveCheckedInstruction)(userTokenAccount, inputToken, delegatePda, signer.publicKey, BigInt(inputAmount.toString()), tokenDecimals, undefined, spl_token_1.TOKEN_PROGRAM_ID);
88
+ const depositIx = await program.methods.deposit(depositData.depositor, depositData.recipient, depositData.inputToken, depositData.outputToken, depositData.inputAmount, depositData.outputAmount, depositData.destinationChainId, depositData.exclusiveRelayer, depositData.quoteTimestamp.toNumber(), depositData.fillDeadline.toNumber(), depositData.exclusivityParameter.toNumber(), Buffer.from([]))
83
89
  .accounts({
84
90
  state: statePda,
91
+ delegate: delegatePda,
85
92
  signer: signer.publicKey,
86
93
  userTokenAccount: (0, spl_token_1.getAssociatedTokenAddressSync)(inputToken, signer.publicKey),
87
94
  vault: vault,
@@ -122,7 +129,7 @@ async function testBundleLogic() {
122
129
  const rootBundleId = state.rootBundleId;
123
130
  const rootBundleIdBuffer = Buffer.alloc(4);
124
131
  rootBundleIdBuffer.writeUInt32LE(rootBundleId);
125
- const seeds = [Buffer.from("root_bundle"), statePda.toBuffer(), rootBundleIdBuffer];
132
+ const seeds = [Buffer.from("root_bundle"), seed.toArrayLike(Buffer, "le", 8), rootBundleIdBuffer];
126
133
  const [rootBundle] = web3_js_1.PublicKey.findProgramAddressSync(seeds, programId);
127
134
  console.table([
128
135
  { property: "State PDA", value: statePda.toString() },