Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(stellar): its example scripts #490

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions stellar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Deploy Interchain Token wasm first.

```bash
node stellar/deploy-contract.js deploy interchain_token --chain-name <CHAIN_NAME> --wasm-path ../axelar-cgp-soroban/target/wasm32-unknown-unknown/release/interchain_token.optimized.wasm

node stellar/deploy-contract.js deploy interchain_token_service --chain-name <CHAIN_NAME> --wasm-path ../axelar-cgp-soroban/target/wasm32-unknown-unknown/release/interchain_token_service.optimized.wasm
```

Expand Down Expand Up @@ -198,6 +199,36 @@ node stellar/its.js set-trusted-chain [chain-name]
node stellar/its.js remove-trusted-chain [chain-name]
```

#### Deploy Interchain Token

```bash
node stellar/its.js deploy-interchain-token [name] [symbol] [decimal] [salt] [initial-supply]
```

#### Deploy Remote Interchain Token

```bash
node stellar/its.js deploy-remote-interchain-token [salt] [destination-chain] [gas-token-address] [gas-fee-amount]
```

#### Register Canonical Token

```bash
node stellar/its.js register-canonical-token [token-address]
```

#### Interchain Transfer

```bash
node stellar/its.js interchain-transfer [token-id] [destination-chain] [destination-address] [amount] [data] [gas-token-address] [gas-fee-amount]
```

#### Execute

```bash
node stellar/its.js execute [source-chain] [message-id] [source-address] [payload]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
node stellar/its.js execute [source-chain] [message-id] [source-address] [payload]
node stellar/gateway.js execute [source-chain] [message-id] [source-address] [payload]

Move this under gateway section

```

## TTL extension and state archival recovery

All Soroban storage entries, including contract instances, have a 'time to live' (`ttl`) after which entries will be archived and no longer accessible until restored. The following commands can be used to extend `ttl` or restore archived contract instances.
Expand Down
22 changes: 22 additions & 0 deletions stellar/gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,21 @@ async function submitProof(wallet, config, chain, contractConfig, args, options)
await broadcast(operation, wallet, chain, 'Amplifier Proof Submitted', options);
}

async function execute(wallet, _, chain, contractConfig, args, options) {
const contract = new Contract(contractConfig.address);
const [sourceChain, messageId, sourceAddress, payload] = args;

const operation = contract.call(
'execute',
nativeToScVal(sourceChain, { type: 'string' }),
nativeToScVal(messageId, { type: 'string' }),
nativeToScVal(sourceAddress, { type: 'string' }),
nativeToScVal(Buffer.from(arrayify(payload)), { type: 'bytes' }),
);

await broadcast(operation, wallet, chain, 'Executed', options);
}

async function mainProcessor(processor, args, options) {
const config = loadConfig(options.env);
const chain = getChainConfig(config, options.chainName);
Expand Down Expand Up @@ -223,6 +238,13 @@ if (require.main === module) {
mainProcessor(callContract, [destinationChain, destinationAddress, payload], options);
});

program
.command('execute <sourceChain> <messageId> <sourceAddress> <payload>')
.description('execute a message')
.action((sourceChain, messageId, sourceAddress, payload, options) => {
mainProcessor(execute, [sourceChain, messageId, sourceAddress, payload], options);
});

addOptionsToCommands(program, addBaseOptions);

program.parse();
Expand Down
17 changes: 1 addition & 16 deletions stellar/gmp.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,14 @@
const { Contract, Address, nativeToScVal } = require('@stellar/stellar-sdk');
const { Command } = require('commander');
const { loadConfig, printInfo, saveConfig } = require('../evm/utils');
const { getWallet, broadcast, addBaseOptions } = require('./utils');
const { getWallet, broadcast, addBaseOptions, tokenToScVal } = require('./utils');
const { addOptionsToCommands, getChainConfig } = require('../common');
const { ethers } = require('hardhat');
const {
utils: { arrayify },
} = ethers;
require('./cli-utils');

function tokenToScVal(tokenAddress, tokenAmount) {
return nativeToScVal(
{
address: Address.fromString(tokenAddress),
amount: tokenAmount,
},
{
type: {
address: ['symbol', 'address'],
amount: ['symbol', 'i128'],
},
},
);
}

async function send(wallet, _, chain, contractConfig, args, options) {
const contract = new Contract(contractConfig.address);
const caller = nativeToScVal(Address.fromString(wallet.publicKey()), { type: 'address' });
Expand Down
209 changes: 202 additions & 7 deletions stellar/its.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,184 @@
'use strict';

const { Address, Contract, nativeToScVal, Operation, xdr, authorizeInvocation, rpc } = require('@stellar/stellar-sdk');
const { Command } = require('commander');
const { Contract, nativeToScVal } = require('@stellar/stellar-sdk');
const { ethers } = require('hardhat');
const {
utils: { arrayify, hexZeroPad, isHexString, keccak256 },
} = ethers;

const { saveConfig, loadConfig, addOptionsToCommands, getChainConfig } = require('../common');
const { addBaseOptions, getWallet, broadcast } = require('./utils');
const {
addBaseOptions,
getWallet,
broadcast,
tokenToScVal,
tokenMetadataToScVal,
getNetworkPassphrase,
createAuthorizedFunc,
} = require('./utils');
const { prompt } = require('../common/utils');

async function setTrustedChain(wallet, _, chain, contractConfig, arg, options) {
const contract = new Contract(contractConfig.address);
async function setTrustedChain(wallet, _, chain, arg, options) {
const contract = new Contract(chain.contracts.interchain_token_service?.address);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

provide contract as an arg to reduce duplication

const callArg = nativeToScVal(arg, { type: 'string' });

const operation = contract.call('set_trusted_chain', callArg);

await broadcast(operation, wallet, chain, 'Trusted Chain Set', options);
}

async function removeTrustedChain(wallet, _, chain, contractConfig, arg, options) {
const contract = new Contract(contractConfig.address);
async function removeTrustedChain(wallet, _, chain, arg, options) {
const contract = new Contract(chain.contracts.interchain_token_service?.address);
const callArg = nativeToScVal(arg, { type: 'string' });

const operation = contract.call('remove_trusted_chain', callArg);

await broadcast(operation, wallet, chain, 'Trusted Chain Removed', options);
}

async function deployInterchainToken(wallet, _, chain, args, options) {
const contract = new Contract(chain.contracts.interchain_token_service?.address);
const caller = nativeToScVal(Address.fromString(wallet.publicKey()), { type: 'address' });
const minter = caller;
const [symbol, name, decimal, salt, initialSupply] = args;
const saltBytes32 = isHexString(salt) ? hexZeroPad(salt, 32) : keccak256(salt);

const operation = contract.call(
'deploy_interchain_token',
caller,
nativeToScVal(Buffer.from(arrayify(saltBytes32)), { type: 'bytes' }),
tokenMetadataToScVal(decimal, name, symbol),
nativeToScVal(initialSupply, { type: 'i128' }),
minter,
);

await broadcast(operation, wallet, chain, 'Interchain Token Deployed', options);
}

async function deployRemoteInterchainToken(wallet, _, chain, args, options) {
const contract = new Contract(chain.contracts.interchain_token_service?.address);
const caller = nativeToScVal(Address.fromString(wallet.publicKey()), { type: 'address' });
const [salt, destinationChain, gasTokenAddress, gasFeeAmount] = args;
const saltBytes32 = hexZeroPad(salt.startsWith('0x') ? salt : '0x' + salt, 32);

const operation = contract.call(
'deploy_remote_interchain_token',
caller,
nativeToScVal(Buffer.from(arrayify(saltBytes32)), { type: 'bytes' }),
nativeToScVal(destinationChain, { type: 'string' }),
tokenToScVal(gasTokenAddress, gasFeeAmount),
);

await broadcast(operation, wallet, chain, 'Remote Interchain Token Deployed', options);
}

async function registerCanonicalToken(wallet, _, chain, args, options) {
const contract = new Contract(chain.contracts.interchain_token_service?.address);
const [tokenAddress] = args;

const operation = contract.call('register_canonical_token', nativeToScVal(tokenAddress, { type: 'address' }));

await broadcast(operation, wallet, chain, 'Canonical Token Registered', options);
}

async function createAuth(itsAddress, gasServiceAddress, gatewayAddress, spenderScVal, gasTokenScVal, chain, wallet) {
const validUntil = await new rpc.Server(chain.rpc).getLatestLedger().then((info) => info.sequence + 100);

const itsAddressScVal = nativeToScVal(Address.fromString(itsAddress), { type: 'address' });
const axlearScVal = nativeToScVal('axelar', { type: 'string' });
const axelarAddressScVal = nativeToScVal('axelar13ehuhysn5mqjeaheeuew2gjs785f6k7jm8vfsqg3jhtpkwppcmzqtedxty', { type: 'string' });
const payloadHex =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debug payload?

'0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000096176616c616e636865000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000001c00a0cb91e6505241fd8c744cc5444fe3e0f5e18c551f5a3f09e9cfde4727a8100000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000066e6174697665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066e617469766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
const emptyBytesScVal = nativeToScVal('()');

return Promise.all(
[
createAuthorizedFunc(gasServiceAddress, 'pay_gas', [
itsAddressScVal,
axlearScVal,
axelarAddressScVal,
nativeToScVal(Buffer.from(arrayify(payloadHex)), { type: 'bytes' }),
spenderScVal,
gasTokenScVal,
emptyBytesScVal,
]),
createAuthorizedFunc(gatewayAddress, 'call_contract', [itsAddressScVal, axlearScVal, axelarAddressScVal, emptyBytesScVal]),
].map((auth) =>
authorizeInvocation(
wallet,
validUntil,
new xdr.SorobanAuthorizedInvocation({
function: auth,
subInvocations: [],
}),
wallet.publicKey(),
getNetworkPassphrase(chain.networkType),
),
),
);
}

async function deployRemoteCanonicalToken(wallet, _, chain, args, options) {
let itsAddress = chain.contracts.interchain_token_service?.address;
let gasServiceAddress = chain.contracts.axelar_gas_service?.address;
let gatewayAddress = chain.contracts.axelar_gateway?.address;

const spenderScVal = nativeToScVal(Address.fromString(wallet.publicKey()), { type: 'address' });
const [tokenAddress, destinationChain, gasTokenAddress, gasFeeAmount] = args;

gasServiceAddress = Address.fromString(gasServiceAddress);
gatewayAddress = Address.fromString(gatewayAddress);

let tokenAddressScVal = nativeToScVal(tokenAddress, { type: 'address' });
let destinationChainsScVal = nativeToScVal(destinationChain, { type: 'string' });
let gasTokenScVal = tokenToScVal(gasTokenAddress, gasFeeAmount);

const operation = Operation.invokeContractFunction({
contract: itsAddress,
function: 'deploy_remote_canonical_token',
args: [tokenAddressScVal, destinationChainsScVal, spenderScVal, gasTokenScVal],
auth: await createAuth(itsAddress, gasServiceAddress, gatewayAddress, spenderScVal, gasTokenScVal, chain, wallet),
});

await broadcast(operation, wallet, chain, 'Remote Canonical Token Deployed', options);
}

// async function deployRemoteCanonicalTokenOrg(wallet, _, chain, args, options) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

// const contract = new Contract(chain.contracts.interchain_token_service?.address);
// const spender = nativeToScVal(Address.fromString(wallet.publicKey()), { type: 'address' });
// const [tokenAddress, destinationChain, gasTokenAddress, gasFeeAmount] = args;

// const operation = contract.call(
// 'deploy_remote_canonical_token',
// nativeToScVal(tokenAddress, { type: 'address' }),
// nativeToScVal(destinationChain, { type: 'string' }),
// spender,
// tokenToScVal(gasTokenAddress, gasFeeAmount),
// );

// await broadcast(operation, wallet, chain, 'Remote Canonical Token Deployed', options);
// }

async function interchainTransfer(wallet, _, chain, args, options) {
const contract = new Contract(chain.contracts.interchain_token_service?.address);
const caller = nativeToScVal(Address.fromString(wallet.publicKey()), { type: 'address' });
const [tokenId, destinationChain, destinationAddress, amount, data, gasTokenAddress, gasFeeAmount] = args;

const operation = contract.call(
'interchain_transfer',
caller,
nativeToScVal(Buffer.from(arrayify(tokenId)), { type: 'bytes' }),
nativeToScVal(destinationChain, { type: 'string' }),
nativeToScVal(Buffer.from(arrayify(destinationAddress)), { type: 'bytes' }),
nativeToScVal(amount, { type: 'i128' }),
nativeToScVal(Buffer.from(arrayify(data)), { type: 'bytes' }),
tokenToScVal(gasTokenAddress, gasFeeAmount),
);

await broadcast(operation, wallet, chain, 'Interchain Token Transferred', options);
}

async function mainProcessor(processor, args, options) {
const { yes } = options;
const config = loadConfig(options.env);
Expand All @@ -37,7 +193,7 @@ async function mainProcessor(processor, args, options) {
throw new Error('Interchain Token Service package not found.');
}

await processor(wallet, config, chain, chain.contracts.interchain_token_service, args, options);
await processor(wallet, config, chain, args, options);

saveConfig(config, options.env);
}
Expand All @@ -61,6 +217,45 @@ if (require.main === module) {
mainProcessor(removeTrustedChain, chainName, options);
});

program
.command('deploy-interchain-token <symbol> <name> <decimals> <salt> <initialSupply> ')
.description('deploy interchain token')
.action((symbol, name, decimal, salt, initialSupply, options) => {
mainProcessor(deployInterchainToken, [symbol, name, decimal, salt, initialSupply], options);
});

program
.command('deploy-remote-interchain-token <salt> <destinationChain> <gasTokenAddress> <gasFeeAmount>')
.description('deploy remote interchain token')
.action((salt, destinationChain, gasTokenAddress, gasFeeAmount, options) => {
mainProcessor(deployRemoteInterchainToken, [salt, destinationChain, gasTokenAddress, gasFeeAmount], options);
});

program
.command('register-canonical-token <tokenAddress>')
.description('register canonical token')
.action((tokenAddress, options) => {
mainProcessor(registerCanonicalToken, [tokenAddress], options);
});

program
.command('deploy-remote-canonical-token <tokenAddress> <destinationChain> <gasTokenAddress> <gasFeeAmount>')
.description('deploy remote canonical token')
.action((tokenAddress, destinationChain, gasTokenAddress, gasFeeAmount, options) => {
mainProcessor(deployRemoteCanonicalToken, [tokenAddress, destinationChain, gasTokenAddress, gasFeeAmount], options);
});

program
.command('interchain-transfer <tokenId> <destinationChain> <destinationAddress> <amount> <data> <gasTokenAddress> <gasFeeAmount>')
.description('interchain transfer')
.action((tokenId, destinationChain, destinationAddress, amount, data, gasTokenAddress, gasFeeAmount, options) => {
mainProcessor(
interchainTransfer,
[tokenId, destinationChain, destinationAddress, amount, data, gasTokenAddress, gasFeeAmount],
options,
);
});

addOptionsToCommands(program, addBaseOptions);

program.parse();
Expand Down
Loading
Loading