@stellar/typescript-wallet-sdk 1.1.3 → 1.2.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.
- package/examples/sep24/README.md +22 -0
- package/examples/sep24/sep24.ts +199 -0
- package/package.json +5 -3
- package/src/index.ts +1 -0
- package/src/walletSdk/Anchor/Sep24.ts +2 -0
- package/src/walletSdk/Auth/WalletSigner.ts +64 -1
- package/src/walletSdk/Auth/index.ts +1 -1
- package/src/walletSdk/Exceptions/index.ts +6 -0
- package/src/walletSdk/Horizon/Transaction/CommonTransactionBuilder.ts +75 -0
- package/src/walletSdk/Horizon/Transaction/SponsoringBuilder.ts +58 -0
- package/src/walletSdk/Horizon/Transaction/TransactionBuilder.ts +116 -34
- package/src/walletSdk/Horizon/index.ts +1 -0
- package/src/walletSdk/Types/auth.ts +4 -0
- package/src/walletSdk/Types/horizon.ts +12 -1
- package/src/walletSdk/index.ts +7 -1
- package/test/stellar.test.ts +198 -1
- package/test/transaction.test.ts +325 -0
- package/test/wallet.test.ts +24 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Sep-24 examples for stellar typescript-wallet-sdk
|
|
2
|
+
|
|
3
|
+
## Example code
|
|
4
|
+
|
|
5
|
+
To view how the wallet-sdk can be used to create sep-24 deposits and withdrawals
|
|
6
|
+
look at `sep24.ts`.
|
|
7
|
+
|
|
8
|
+
## Running deposit and withdrawals
|
|
9
|
+
|
|
10
|
+
To see them in action you can run below from the project root:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
$ yarn example:sep24
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This will run the deposit flow and watch for it to finish. At the end it will
|
|
17
|
+
ask if you'd like to run the withdraw flow. Use USDC as the asset for the
|
|
18
|
+
example.
|
|
19
|
+
|
|
20
|
+
Progress will be logged in the terminal.
|
|
21
|
+
|
|
22
|
+
_note: the identity values used in the sep24 interactive portal can all be fake_
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import readline from "readline";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
walletSdk,
|
|
6
|
+
Anchor,
|
|
7
|
+
SigningKeypair,
|
|
8
|
+
Types,
|
|
9
|
+
IssuedAssetId,
|
|
10
|
+
} from "../../src";
|
|
11
|
+
import { Memo, MemoText } from "stellar-sdk";
|
|
12
|
+
|
|
13
|
+
const wallet = walletSdk.Wallet.TestNet();
|
|
14
|
+
const stellar = wallet.stellar();
|
|
15
|
+
const anchor = wallet.anchor({ homeDomain: "testanchor.stellar.org" });
|
|
16
|
+
const account = stellar.account();
|
|
17
|
+
|
|
18
|
+
let kp: SigningKeypair;
|
|
19
|
+
|
|
20
|
+
const asset = new IssuedAssetId(
|
|
21
|
+
"USDC",
|
|
22
|
+
"GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const runSep24 = async () => {
|
|
26
|
+
await createAccount();
|
|
27
|
+
await runDeposit(anchor, kp);
|
|
28
|
+
await runDepositWatcher(anchor);
|
|
29
|
+
|
|
30
|
+
while (!depositDone) {
|
|
31
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ans = await askQuestion("Do you want to start withdrawal? (y/n)");
|
|
35
|
+
if (ans !== "y") {
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
await runWithdraw(anchor, kp);
|
|
40
|
+
await runWithdrawWatcher(anchor, kp);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Create Account
|
|
44
|
+
const createAccount = async () => {
|
|
45
|
+
console.log("creating account ...");
|
|
46
|
+
kp = account.createKeypair();
|
|
47
|
+
console.log(`kp: \n${kp.publicKey}\n${kp.secretKey}`);
|
|
48
|
+
|
|
49
|
+
// funding new account
|
|
50
|
+
await axios.get("https://friendbot.stellar.org/?addr=" + kp.publicKey);
|
|
51
|
+
|
|
52
|
+
const txBuilder = await stellar.transaction({
|
|
53
|
+
sourceAddress: kp,
|
|
54
|
+
baseFee: 1000,
|
|
55
|
+
});
|
|
56
|
+
const tx = txBuilder.addAssetSupport(asset).build();
|
|
57
|
+
kp.sign(tx);
|
|
58
|
+
await stellar.submitTransaction(tx);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Create Deposit
|
|
62
|
+
let authToken: string;
|
|
63
|
+
export const runDeposit = async (anchor: Anchor, kp: SigningKeypair) => {
|
|
64
|
+
console.log("\ncreating deposit ...");
|
|
65
|
+
const auth = await anchor.sep10();
|
|
66
|
+
authToken = await auth.authenticate({ accountKp: kp });
|
|
67
|
+
|
|
68
|
+
const resp = await anchor.sep24().deposit({
|
|
69
|
+
assetCode: asset.code,
|
|
70
|
+
authToken: authToken,
|
|
71
|
+
lang: "en-US",
|
|
72
|
+
destinationMemo: new Memo(MemoText, "test-memo"),
|
|
73
|
+
// Optional field. Same result would be achieved with omitting this field.
|
|
74
|
+
// Replace with a different account if you want to change destination
|
|
75
|
+
destinationAccount: kp.publicKey,
|
|
76
|
+
// If not specified, amount will be collected in the interactive flow. You can also pass extra SEP-9 fields.
|
|
77
|
+
extraFields: { amount: "10" },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
console.log("Open url:\n", resp.url);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Watch Deposit
|
|
84
|
+
export let depositDone = false;
|
|
85
|
+
export const runDepositWatcher = (anchor: Anchor) => {
|
|
86
|
+
console.log("\nstarting watcher ...");
|
|
87
|
+
|
|
88
|
+
let stop: Types.WatcherStopFunction;
|
|
89
|
+
const onMessage = (m: Types.AnchorTransaction) => {
|
|
90
|
+
console.log({ m });
|
|
91
|
+
if (m.status === Types.TransactionStatus.completed) {
|
|
92
|
+
console.log("status completed, stopping watcher");
|
|
93
|
+
stop();
|
|
94
|
+
depositDone = true;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const onError = (error: Types.AnchorTransaction | Error) => {
|
|
99
|
+
console.error({ error });
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const watcher = anchor.sep24().watcher();
|
|
103
|
+
const resp = watcher.watchAllTransactions({
|
|
104
|
+
authToken: authToken,
|
|
105
|
+
assetCode: asset.code,
|
|
106
|
+
onMessage,
|
|
107
|
+
onError,
|
|
108
|
+
timeout: 5000,
|
|
109
|
+
lang: "en-US",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
stop = resp.stop;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Create Withdrawal
|
|
116
|
+
export const runWithdraw = async (anchor, kp) => {
|
|
117
|
+
console.log("\ncreating withdrawal ...");
|
|
118
|
+
|
|
119
|
+
const resp = await anchor.sep24().withdraw({
|
|
120
|
+
assetCode: asset.code,
|
|
121
|
+
authToken: authToken,
|
|
122
|
+
lang: "en-US",
|
|
123
|
+
// Optional field. Same result would be achieved with omitting this field.
|
|
124
|
+
// Replace with a different account if you want to change destination
|
|
125
|
+
withdrawalAccount: kp.publicKey,
|
|
126
|
+
// If not specified, amount will be collected in the interactive flow. You can also pass extra SEP-9 fields.
|
|
127
|
+
extraFields: { amount: "10" },
|
|
128
|
+
});
|
|
129
|
+
console.log("Open url:\n", resp.url);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const sendWithdrawalTransaction = async (withdrawalTxn, kp) => {
|
|
133
|
+
const asset = new IssuedAssetId(
|
|
134
|
+
"USDC",
|
|
135
|
+
"GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const txBuilder = await stellar.transaction({
|
|
139
|
+
sourceAddress: kp,
|
|
140
|
+
baseFee: 1000,
|
|
141
|
+
});
|
|
142
|
+
const tx = txBuilder
|
|
143
|
+
.transferWithdrawalTransaction(withdrawalTxn, asset)
|
|
144
|
+
.build();
|
|
145
|
+
kp.sign(tx);
|
|
146
|
+
await stellar.submitTransaction(tx);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Watch Withdrawal
|
|
150
|
+
export const runWithdrawWatcher = (anchor, kp) => {
|
|
151
|
+
console.log("\nstarting watcher ...");
|
|
152
|
+
|
|
153
|
+
let stop;
|
|
154
|
+
const onMessage = (m) => {
|
|
155
|
+
console.log({ m });
|
|
156
|
+
|
|
157
|
+
if (m.status === Types.TransactionStatus.pending_user_transfer_start) {
|
|
158
|
+
sendWithdrawalTransaction(m, kp);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (m.status === Types.TransactionStatus.completed) {
|
|
162
|
+
console.log("status completed, stopping watcher");
|
|
163
|
+
|
|
164
|
+
stop();
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const onError = (e) => {
|
|
169
|
+
console.error({ e });
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const watcher = anchor.sep24().watcher();
|
|
173
|
+
const resp = watcher.watchAllTransactions({
|
|
174
|
+
authToken: authToken,
|
|
175
|
+
assetCode: asset.code,
|
|
176
|
+
onMessage,
|
|
177
|
+
onError,
|
|
178
|
+
timeout: 5000,
|
|
179
|
+
lang: "en-US",
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
stop = resp.stop;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export const askQuestion = (query) => {
|
|
186
|
+
const rl = readline.createInterface({
|
|
187
|
+
input: process.stdin,
|
|
188
|
+
output: process.stdout,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
return new Promise((resolve) =>
|
|
192
|
+
rl.question(query, (ans) => {
|
|
193
|
+
rl.close();
|
|
194
|
+
resolve(ans);
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
runSep24();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stellar/typescript-wallet-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=18"
|
|
6
6
|
},
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"stream-browserify": "^3.0.0",
|
|
30
30
|
"ts-jest": "^29.0.5",
|
|
31
31
|
"ts-loader": "^9.4.2",
|
|
32
|
+
"ts-node": "^10.9.1",
|
|
32
33
|
"tslib": "^2.5.0",
|
|
33
34
|
"typescript": "^5.0.4",
|
|
34
35
|
"webpack": "^5.83.1",
|
|
@@ -47,10 +48,11 @@
|
|
|
47
48
|
"utility-types": "^3.10.0"
|
|
48
49
|
},
|
|
49
50
|
"scripts": {
|
|
50
|
-
"prepare": "
|
|
51
|
+
"prepare": "husky install",
|
|
51
52
|
"test": "jest --watchAll",
|
|
52
53
|
"build:web": "webpack --config webpack.config.js",
|
|
53
54
|
"build:node": "webpack --env NODE=true --config webpack.config.js",
|
|
54
|
-
"build": "run-p build:web build:node"
|
|
55
|
+
"build": "run-p build:web build:node",
|
|
56
|
+
"example:sep24": "ts-node examples/sep24/sep24.ts"
|
|
55
57
|
}
|
|
56
58
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Transaction,
|
|
3
|
+
TransactionBuilder as StellarTransactionBuilder,
|
|
4
|
+
FeeBumpTransaction,
|
|
5
|
+
} from "stellar-sdk";
|
|
6
|
+
import { AxiosInstance } from "axios";
|
|
7
|
+
|
|
2
8
|
import {
|
|
3
9
|
SignWithClientAccountParams,
|
|
4
10
|
SignWithDomainAccountParams,
|
|
11
|
+
HttpHeaders,
|
|
5
12
|
} from "../Types";
|
|
13
|
+
import { AccountKeypair } from "../Horizon/Account";
|
|
14
|
+
import { DefaultClient } from "../";
|
|
6
15
|
|
|
7
16
|
export interface WalletSigner {
|
|
8
17
|
signWithClientAccount({
|
|
@@ -28,3 +37,57 @@ export const DefaultSigner: WalletSigner = {
|
|
|
28
37
|
);
|
|
29
38
|
},
|
|
30
39
|
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Represents a Domain Signer used for signing Stellar transactions with a domain server.
|
|
43
|
+
*
|
|
44
|
+
* @class
|
|
45
|
+
* @implements {WalletSigner}
|
|
46
|
+
*/
|
|
47
|
+
export class DomainSigner implements WalletSigner {
|
|
48
|
+
private url: string;
|
|
49
|
+
private client: AxiosInstance;
|
|
50
|
+
private headers: HttpHeaders;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create a new instance of the DomainSigner class.
|
|
54
|
+
*
|
|
55
|
+
* @constructor
|
|
56
|
+
* @param {string} url - The URL of the domain server.
|
|
57
|
+
* @param {HttpHeaders} headers - The HTTP headers for requests to the domain server.
|
|
58
|
+
* These headers can be used for authentication purposes.
|
|
59
|
+
*/
|
|
60
|
+
constructor(url: string, headers: HttpHeaders) {
|
|
61
|
+
this.url = url;
|
|
62
|
+
this.client = DefaultClient;
|
|
63
|
+
this.headers = headers;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
signWithClientAccount({
|
|
67
|
+
transaction,
|
|
68
|
+
accountKp,
|
|
69
|
+
}: SignWithClientAccountParams): Transaction {
|
|
70
|
+
transaction.sign(accountKp.keypair);
|
|
71
|
+
return transaction;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async signWithDomainAccount({
|
|
75
|
+
transactionXDR,
|
|
76
|
+
networkPassphrase,
|
|
77
|
+
accountKp,
|
|
78
|
+
}: SignWithDomainAccountParams): Promise<Transaction> {
|
|
79
|
+
const response = await this.client.post(
|
|
80
|
+
this.url,
|
|
81
|
+
{
|
|
82
|
+
transactionXDR,
|
|
83
|
+
networkPassphrase,
|
|
84
|
+
},
|
|
85
|
+
{ headers: this.headers },
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return StellarTransactionBuilder.fromXDR(
|
|
89
|
+
response.data.transaction,
|
|
90
|
+
networkPassphrase,
|
|
91
|
+
) as Transaction;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -147,6 +147,12 @@ export class WithdrawalTxMissingMemoError extends Error {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
export class PathPayOnlyOneAmountError extends Error {
|
|
151
|
+
constructor() {
|
|
152
|
+
super("Must give sendAmount or destAmount value, but not both");
|
|
153
|
+
Object.setPrototypeOf(this, PathPayOnlyOneAmountError.prototype);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
150
156
|
export class WithdrawalTxMemoError extends Error {
|
|
151
157
|
constructor() {
|
|
152
158
|
super(`Error parsing withdrawal transaction memo`);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import StellarSdk, { xdr } from "stellar-sdk";
|
|
2
|
+
import { IssuedAssetId } from "../../Asset";
|
|
3
|
+
import { AccountKeypair } from "../Account";
|
|
4
|
+
|
|
5
|
+
export abstract class CommonTransactionBuilder<T> {
|
|
6
|
+
protected sourceAddress: string;
|
|
7
|
+
protected operations: Array<xdr.Operation>;
|
|
8
|
+
|
|
9
|
+
constructor(sourceAddress: string, operations: Array<xdr.Operation>) {
|
|
10
|
+
this.sourceAddress = sourceAddress;
|
|
11
|
+
this.operations = operations;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
addAssetSupport(asset: IssuedAssetId, trustLimit?: string): T {
|
|
15
|
+
this.operations.push(
|
|
16
|
+
StellarSdk.Operation.changeTrust({
|
|
17
|
+
asset: asset.toAsset(),
|
|
18
|
+
limit: trustLimit,
|
|
19
|
+
source: this.sourceAddress,
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
22
|
+
return this as any as T;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
removeAssetSupport(asset: IssuedAssetId): T {
|
|
26
|
+
return this.addAssetSupport(asset, "0");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
addAccountSigner(signerAddress: AccountKeypair, signerWeight: number): T {
|
|
30
|
+
this.operations.push(
|
|
31
|
+
StellarSdk.Operation.setOptions({
|
|
32
|
+
source: this.sourceAddress,
|
|
33
|
+
signer: {
|
|
34
|
+
ed25519PublicKey: signerAddress.publicKey,
|
|
35
|
+
weight: signerWeight,
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
return this as any as T;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
removeAccountSigner(signerAddress: AccountKeypair): T {
|
|
43
|
+
return this.addAccountSigner(signerAddress, 0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
lockAccountMasterKey(): T {
|
|
47
|
+
this.operations.push(
|
|
48
|
+
StellarSdk.Operation.setOptions({
|
|
49
|
+
source: this.sourceAddress,
|
|
50
|
+
masterWeight: 0,
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
return this as any as T;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setThreshold({
|
|
57
|
+
low,
|
|
58
|
+
medium,
|
|
59
|
+
high,
|
|
60
|
+
}: {
|
|
61
|
+
low?: number;
|
|
62
|
+
medium?: number;
|
|
63
|
+
high?: number;
|
|
64
|
+
}): T {
|
|
65
|
+
this.operations.push(
|
|
66
|
+
StellarSdk.Operation.setOptions({
|
|
67
|
+
source: this.sourceAddress,
|
|
68
|
+
lowThreshold: low,
|
|
69
|
+
medThreshold: medium,
|
|
70
|
+
highThreshold: high,
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
return this as any as T;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import StellarSdk, {
|
|
2
|
+
TransactionBuilder as StellarTransactionBuilder,
|
|
3
|
+
Transaction,
|
|
4
|
+
xdr,
|
|
5
|
+
} from "stellar-sdk";
|
|
6
|
+
import { IssuedAssetId } from "../../Asset";
|
|
7
|
+
|
|
8
|
+
import { CommonTransactionBuilder } from "./CommonTransactionBuilder";
|
|
9
|
+
import { AccountKeypair } from "../Account";
|
|
10
|
+
|
|
11
|
+
export class SponsoringBuilder extends CommonTransactionBuilder<SponsoringBuilder> {
|
|
12
|
+
private sponsorAccount: AccountKeypair;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
sponsoredAddress: string,
|
|
16
|
+
sponsorAccount: AccountKeypair,
|
|
17
|
+
operations: Array<xdr.Operation>,
|
|
18
|
+
buildingFunction: (SponsoringBuilder) => SponsoringBuilder,
|
|
19
|
+
) {
|
|
20
|
+
super(sponsoredAddress, operations);
|
|
21
|
+
this.sponsorAccount = sponsorAccount;
|
|
22
|
+
|
|
23
|
+
this.startSponsoring();
|
|
24
|
+
buildingFunction(this);
|
|
25
|
+
this.stopSponsoring();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
createAccount(
|
|
29
|
+
newAccount: AccountKeypair,
|
|
30
|
+
startingBalance: number = 0,
|
|
31
|
+
): SponsoringBuilder {
|
|
32
|
+
this.operations.push(
|
|
33
|
+
StellarSdk.Operation.createAccount({
|
|
34
|
+
destination: newAccount.publicKey,
|
|
35
|
+
startingBalance: startingBalance.toString(),
|
|
36
|
+
source: this.sponsorAccount.publicKey,
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
startSponsoring() {
|
|
43
|
+
this.operations.push(
|
|
44
|
+
StellarSdk.Operation.beginSponsoringFutureReserves({
|
|
45
|
+
sponsoredId: this.sourceAddress,
|
|
46
|
+
source: this.sponsorAccount.publicKey,
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
stopSponsoring() {
|
|
52
|
+
this.operations.push(
|
|
53
|
+
StellarSdk.Operation.endSponsoringFutureReserves({
|
|
54
|
+
source: this.sourceAddress,
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -17,15 +17,19 @@ import {
|
|
|
17
17
|
WithdrawalTxMemoError,
|
|
18
18
|
} from "../../Exceptions";
|
|
19
19
|
import { IssuedAssetId, StellarAssetId } from "../../Asset";
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
import {
|
|
21
|
+
WithdrawTransaction,
|
|
22
|
+
TransactionStatus,
|
|
23
|
+
PathPayParams,
|
|
24
|
+
} from "../../Types";
|
|
25
|
+
import { PathPayOnlyOneAmountError } from "../../Exceptions";
|
|
26
|
+
import { CommonTransactionBuilder } from "./CommonTransactionBuilder";
|
|
27
|
+
import { SponsoringBuilder } from "./SponsoringBuilder";
|
|
28
|
+
|
|
29
|
+
export class TransactionBuilder extends CommonTransactionBuilder<TransactionBuilder> {
|
|
30
|
+
private cfg: Config;
|
|
25
31
|
private builder: StellarTransactionBuilder;
|
|
26
32
|
|
|
27
|
-
sourceAccount: string;
|
|
28
|
-
|
|
29
33
|
constructor(
|
|
30
34
|
cfg: Config,
|
|
31
35
|
sourceAccount: StellarAccount,
|
|
@@ -33,8 +37,7 @@ export class TransactionBuilder {
|
|
|
33
37
|
memo?: Memo,
|
|
34
38
|
timebounds?: Server.Timebounds,
|
|
35
39
|
) {
|
|
36
|
-
|
|
37
|
-
this.operations = [];
|
|
40
|
+
super(sourceAccount.accountId(), []);
|
|
38
41
|
this.builder = new StellarTransactionBuilder(sourceAccount, {
|
|
39
42
|
fee: baseFee ? baseFee.toString() : cfg.stellar.baseFee.toString(),
|
|
40
43
|
timebounds,
|
|
@@ -44,8 +47,20 @@ export class TransactionBuilder {
|
|
|
44
47
|
if (!timebounds) {
|
|
45
48
|
this.builder.setTimeout(cfg.stellar.defaultTimeout);
|
|
46
49
|
}
|
|
50
|
+
}
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
sponsoring(
|
|
53
|
+
sponsorAccount: AccountKeypair,
|
|
54
|
+
buildingFunction: (SponsoringBuilder) => SponsoringBuilder,
|
|
55
|
+
sponsoredAccount?: AccountKeypair,
|
|
56
|
+
): TransactionBuilder {
|
|
57
|
+
new SponsoringBuilder(
|
|
58
|
+
sponsoredAccount ? sponsoredAccount.publicKey : this.sourceAddress,
|
|
59
|
+
sponsorAccount,
|
|
60
|
+
this.operations,
|
|
61
|
+
buildingFunction,
|
|
62
|
+
);
|
|
63
|
+
return this;
|
|
49
64
|
}
|
|
50
65
|
|
|
51
66
|
createAccount(
|
|
@@ -60,7 +75,7 @@ export class TransactionBuilder {
|
|
|
60
75
|
StellarSdk.Operation.createAccount({
|
|
61
76
|
destination: newAccount.publicKey,
|
|
62
77
|
startingBalance: startingBalance.toString(),
|
|
63
|
-
source: this.
|
|
78
|
+
source: this.sourceAddress,
|
|
64
79
|
}),
|
|
65
80
|
);
|
|
66
81
|
return this;
|
|
@@ -81,39 +96,99 @@ export class TransactionBuilder {
|
|
|
81
96
|
return this;
|
|
82
97
|
}
|
|
83
98
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Creates and adds a path payment operation to the transaction builder.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} destinationAddress - The destination Stellar address to which the payment is sent.
|
|
103
|
+
* @param {StellarAssetId} sendAsset - The asset to be sent.
|
|
104
|
+
* @param {StellarAssetId} destAsset - The asset the destination will receive.
|
|
105
|
+
* @param {string} [sendAmount] - The amount to be sent. Must specify either sendAmount or destAmount,
|
|
106
|
+
* but not both.
|
|
107
|
+
* @param {string} [destAmount] - The amount to be received by the destination. Must specify either sendAmount or destAmount,
|
|
108
|
+
* but not both.
|
|
109
|
+
* @param {string} [destMin] - The minimum amount of the destination asset to be receive. This is a
|
|
110
|
+
* protective measure, it allows you to specify a lower bound for an acceptable conversion. Only used
|
|
111
|
+
* if using sendAmount.
|
|
112
|
+
* (optional, default is ".0000001").
|
|
113
|
+
* @param {string} [sendMax] - The maximum amount of the destination asset to be sent. This is a
|
|
114
|
+
* protective measure, it allows you to specify an upper bound for an acceptable conversion. Only used
|
|
115
|
+
* if using destAmount.
|
|
116
|
+
* (optional, default is int64 max).
|
|
117
|
+
*
|
|
118
|
+
* @returns {TransactionBuilder} - Returns the current TransactionBuilder instance for method chaining.
|
|
119
|
+
*/
|
|
120
|
+
pathPay({
|
|
121
|
+
destinationAddress,
|
|
122
|
+
sendAsset,
|
|
123
|
+
destAsset,
|
|
124
|
+
sendAmount,
|
|
125
|
+
destAmount,
|
|
126
|
+
destMin,
|
|
127
|
+
sendMax,
|
|
128
|
+
}: PathPayParams): TransactionBuilder {
|
|
129
|
+
if ((sendAmount && destAmount) || (!sendAmount && !destAmount)) {
|
|
130
|
+
throw new PathPayOnlyOneAmountError();
|
|
131
|
+
}
|
|
132
|
+
if (sendAmount) {
|
|
133
|
+
this.operations.push(
|
|
134
|
+
StellarSdk.Operation.pathPaymentStrictSend({
|
|
135
|
+
destination: destinationAddress,
|
|
136
|
+
sendAsset: sendAsset.toAsset(),
|
|
137
|
+
sendAmount,
|
|
138
|
+
destAsset: destAsset.toAsset(),
|
|
139
|
+
destMin: destMin || ".0000001",
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
} else {
|
|
143
|
+
this.operations.push(
|
|
144
|
+
StellarSdk.Operation.pathPaymentStrictReceive({
|
|
145
|
+
destination: destinationAddress,
|
|
146
|
+
sendAsset: sendAsset.toAsset(),
|
|
147
|
+
destAmount,
|
|
148
|
+
destAsset: destAsset.toAsset(),
|
|
149
|
+
sendMax: sendMax || "922337203685.4775807",
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
88
153
|
|
|
89
|
-
setMemo(memo: Memo): TransactionBuilder {
|
|
90
|
-
this.builder.addMemo(memo);
|
|
91
154
|
return this;
|
|
92
155
|
}
|
|
93
156
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Swap assets using the Stellar network. This swaps using the
|
|
159
|
+
* pathPaymentStrictReceive operation.
|
|
160
|
+
*
|
|
161
|
+
* @param {StellarAssetId} fromAsset - The source asset to be sent.
|
|
162
|
+
* @param {StellarAssetId} toAsset - The destination asset to receive.
|
|
163
|
+
* @param {string} amount - The amount of the source asset to be sent.
|
|
164
|
+
* @param {string} [destMin] - (Optional) The minimum amount of the destination asset to be received.
|
|
165
|
+
*
|
|
166
|
+
* @returns {TransactionBuilder} Returns the current instance of the TransactionBuilder for method chaining.
|
|
167
|
+
*/
|
|
168
|
+
swap(
|
|
169
|
+
fromAsset: StellarAssetId,
|
|
170
|
+
toAsset: StellarAssetId,
|
|
171
|
+
amount: string,
|
|
172
|
+
destMin?: string,
|
|
97
173
|
): TransactionBuilder {
|
|
98
|
-
this.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
);
|
|
174
|
+
this.pathPay({
|
|
175
|
+
destinationAddress: this.sourceAddress,
|
|
176
|
+
sendAsset: fromAsset,
|
|
177
|
+
destAsset: toAsset,
|
|
178
|
+
sendAmount: amount,
|
|
179
|
+
destMin,
|
|
180
|
+
});
|
|
105
181
|
return this;
|
|
106
182
|
}
|
|
107
183
|
|
|
108
|
-
|
|
109
|
-
|
|
184
|
+
addOperation(op: xdr.Operation): TransactionBuilder {
|
|
185
|
+
this.builder.addOperation(op);
|
|
186
|
+
return this;
|
|
110
187
|
}
|
|
111
188
|
|
|
112
|
-
|
|
113
|
-
this.
|
|
114
|
-
|
|
115
|
-
});
|
|
116
|
-
return this.builder.build();
|
|
189
|
+
setMemo(memo: Memo): TransactionBuilder {
|
|
190
|
+
this.builder.addMemo(memo);
|
|
191
|
+
return this;
|
|
117
192
|
}
|
|
118
193
|
|
|
119
194
|
transferWithdrawalTransaction(
|
|
@@ -150,4 +225,11 @@ export class TransactionBuilder {
|
|
|
150
225
|
transaction.amount_in,
|
|
151
226
|
);
|
|
152
227
|
}
|
|
228
|
+
|
|
229
|
+
build(): Transaction {
|
|
230
|
+
this.operations.forEach((op) => {
|
|
231
|
+
this.builder.addOperation(op);
|
|
232
|
+
});
|
|
233
|
+
return this.builder.build();
|
|
234
|
+
}
|
|
153
235
|
}
|
|
@@ -2,3 +2,4 @@ export { PublicKeypair, SigningKeypair } from "./Account";
|
|
|
2
2
|
export { AccountService } from "./AccountService";
|
|
3
3
|
export { Stellar } from "./Stellar";
|
|
4
4
|
export { TransactionBuilder } from "./Transaction/TransactionBuilder";
|
|
5
|
+
export { SponsoringBuilder } from "./Transaction/SponsoringBuilder";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Memo, Server, xdr, Transaction } from "stellar-sdk";
|
|
2
2
|
import { AccountKeypair } from "../Horizon/Account";
|
|
3
3
|
import { TransactionBuilder } from "../Horizon/Transaction/TransactionBuilder";
|
|
4
|
+
import { StellarAssetId } from "../Asset";
|
|
4
5
|
|
|
5
6
|
export enum NETWORK_URLS {
|
|
6
7
|
PUBLIC = "https://horizon.stellar.org",
|
|
@@ -9,7 +10,7 @@ export enum NETWORK_URLS {
|
|
|
9
10
|
|
|
10
11
|
export type TransactionParams = {
|
|
11
12
|
sourceAddress: AccountKeypair;
|
|
12
|
-
baseFee
|
|
13
|
+
baseFee?: number;
|
|
13
14
|
memo?: Memo;
|
|
14
15
|
timebounds?: Server.Timebounds | number;
|
|
15
16
|
};
|
|
@@ -38,3 +39,13 @@ export enum HORIZON_ORDER {
|
|
|
38
39
|
ASC = "asc",
|
|
39
40
|
DESC = "desc",
|
|
40
41
|
}
|
|
42
|
+
|
|
43
|
+
export type PathPayParams = {
|
|
44
|
+
destinationAddress: string;
|
|
45
|
+
sendAsset: StellarAssetId;
|
|
46
|
+
destAsset: StellarAssetId;
|
|
47
|
+
sendAmount?: string;
|
|
48
|
+
destAmount?: string;
|
|
49
|
+
destMin?: string;
|
|
50
|
+
sendMax?: string;
|
|
51
|
+
};
|
package/src/walletSdk/index.ts
CHANGED
|
@@ -131,9 +131,15 @@ export const DefaultClient = axios.create({
|
|
|
131
131
|
export class ApplicationConfiguration {
|
|
132
132
|
defaultSigner: WalletSigner;
|
|
133
133
|
defaultClient: AxiosInstance;
|
|
134
|
+
defaultClientDomain?: string;
|
|
134
135
|
|
|
135
|
-
constructor(
|
|
136
|
+
constructor(
|
|
137
|
+
defaultSigner?: WalletSigner,
|
|
138
|
+
defaultClient?: AxiosInstance,
|
|
139
|
+
defaultClientDomain?: string,
|
|
140
|
+
) {
|
|
136
141
|
this.defaultSigner = defaultSigner || DefaultSigner;
|
|
137
142
|
this.defaultClient = defaultClient || DefaultClient;
|
|
143
|
+
this.defaultClientDomain = defaultClientDomain;
|
|
138
144
|
}
|
|
139
145
|
}
|
package/test/stellar.test.ts
CHANGED
|
@@ -43,7 +43,6 @@ describe("Stellar", () => {
|
|
|
43
43
|
const txBuilderParams = [
|
|
44
44
|
{
|
|
45
45
|
sourceAddress: kp,
|
|
46
|
-
baseFee: 100,
|
|
47
46
|
startingBalance: 2,
|
|
48
47
|
},
|
|
49
48
|
{
|
|
@@ -247,6 +246,127 @@ describe("Stellar", () => {
|
|
|
247
246
|
}, 20000);
|
|
248
247
|
});
|
|
249
248
|
|
|
249
|
+
let txnSourceKp;
|
|
250
|
+
let sponsorKp;
|
|
251
|
+
let newKp;
|
|
252
|
+
describe("SponsoringBuilder", () => {
|
|
253
|
+
beforeAll(async () => {
|
|
254
|
+
wal = Wallet.TestNet();
|
|
255
|
+
stellar = wal.stellar();
|
|
256
|
+
|
|
257
|
+
txnSourceKp = new SigningKeypair(Keypair.random());
|
|
258
|
+
sponsorKp = new SigningKeypair(Keypair.random());
|
|
259
|
+
newKp = new SigningKeypair(Keypair.random());
|
|
260
|
+
await axios.get(
|
|
261
|
+
"https://friendbot.stellar.org/?addr=" + sponsorKp.publicKey,
|
|
262
|
+
);
|
|
263
|
+
await axios.get(
|
|
264
|
+
"https://friendbot.stellar.org/?addr=" + txnSourceKp.publicKey,
|
|
265
|
+
);
|
|
266
|
+
}, 15000);
|
|
267
|
+
|
|
268
|
+
it("should sponsor creating an account", async () => {
|
|
269
|
+
const wal = Wallet.TestNet();
|
|
270
|
+
const stellar = wal.stellar();
|
|
271
|
+
|
|
272
|
+
const txBuilder = await stellar.transaction({
|
|
273
|
+
sourceAddress: txnSourceKp,
|
|
274
|
+
baseFee: 100,
|
|
275
|
+
});
|
|
276
|
+
const buildingFunction = (bldr) => bldr.createAccount(newKp, 0);
|
|
277
|
+
// scenario of different txn source account from sponsor account
|
|
278
|
+
const txn = txBuilder
|
|
279
|
+
.sponsoring(sponsorKp, buildingFunction, newKp)
|
|
280
|
+
.build();
|
|
281
|
+
newKp.sign(txn);
|
|
282
|
+
txnSourceKp.sign(txn);
|
|
283
|
+
sponsorKp.sign(txn);
|
|
284
|
+
|
|
285
|
+
const res = await stellar.submitTransaction(txn);
|
|
286
|
+
expect(res).toBe(true);
|
|
287
|
+
|
|
288
|
+
const sponsoredLoaded = (await stellar.server.loadAccount(
|
|
289
|
+
newKp.publicKey,
|
|
290
|
+
)) as any;
|
|
291
|
+
expect(sponsoredLoaded.num_sponsored).toBe(2);
|
|
292
|
+
}, 15000);
|
|
293
|
+
|
|
294
|
+
it("should sponsor adding trustlines", async () => {
|
|
295
|
+
const txBuilder = await stellar.transaction({
|
|
296
|
+
sourceAddress: txnSourceKp,
|
|
297
|
+
baseFee: 100,
|
|
298
|
+
});
|
|
299
|
+
const buildingFunction = (bldr) =>
|
|
300
|
+
bldr.addAssetSupport(
|
|
301
|
+
new IssuedAssetId(
|
|
302
|
+
"USDC",
|
|
303
|
+
"GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
|
|
304
|
+
),
|
|
305
|
+
);
|
|
306
|
+
const txn = txBuilder.sponsoring(sponsorKp, buildingFunction).build();
|
|
307
|
+
sponsorKp.sign(txn);
|
|
308
|
+
txnSourceKp.sign(txn);
|
|
309
|
+
|
|
310
|
+
const res = await stellar.submitTransaction(txn);
|
|
311
|
+
expect(res).toBe(true);
|
|
312
|
+
|
|
313
|
+
const sponsorLoaded = (await stellar.server.loadAccount(
|
|
314
|
+
sponsorKp.publicKey,
|
|
315
|
+
)) as any;
|
|
316
|
+
expect(sponsorLoaded.num_sponsoring).toBe(3);
|
|
317
|
+
}, 15000);
|
|
318
|
+
|
|
319
|
+
it("should allow sponsoring and regular operations in same transaction", async () => {
|
|
320
|
+
const txBuilder = await stellar.transaction({
|
|
321
|
+
sourceAddress: txnSourceKp,
|
|
322
|
+
baseFee: 100,
|
|
323
|
+
});
|
|
324
|
+
const buildingFunction = (bldr) =>
|
|
325
|
+
bldr.addAssetSupport(
|
|
326
|
+
new IssuedAssetId(
|
|
327
|
+
"USDC",
|
|
328
|
+
"GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
|
|
329
|
+
),
|
|
330
|
+
);
|
|
331
|
+
const txn = txBuilder
|
|
332
|
+
.sponsoring(sponsorKp, buildingFunction)
|
|
333
|
+
.transfer(sponsorKp.publicKey, new NativeAssetId(), "5")
|
|
334
|
+
.build();
|
|
335
|
+
sponsorKp.sign(txn);
|
|
336
|
+
txnSourceKp.sign(txn);
|
|
337
|
+
|
|
338
|
+
const res = await stellar.submitTransaction(txn);
|
|
339
|
+
expect(res).toBe(true);
|
|
340
|
+
|
|
341
|
+
const sponsorLoaded = (await stellar.server.loadAccount(
|
|
342
|
+
sponsorKp.publicKey,
|
|
343
|
+
)) as any;
|
|
344
|
+
expect(sponsorLoaded.num_sponsoring).toBe(3);
|
|
345
|
+
}, 15000);
|
|
346
|
+
it("should sponsor account modification", async () => {
|
|
347
|
+
const txBuilder = await stellar.transaction({
|
|
348
|
+
sourceAddress: txnSourceKp,
|
|
349
|
+
baseFee: 100,
|
|
350
|
+
});
|
|
351
|
+
const otherKp = new SigningKeypair(Keypair.random());
|
|
352
|
+
|
|
353
|
+
const txn = txBuilder
|
|
354
|
+
.sponsoring(sponsorKp, (bldr) => bldr.addAccountSigner(otherKp, 2))
|
|
355
|
+
.build();
|
|
356
|
+
sponsorKp.sign(txn);
|
|
357
|
+
txnSourceKp.sign(txn);
|
|
358
|
+
|
|
359
|
+
await stellar.submitTransaction(txn);
|
|
360
|
+
const sourceLoaded = (await stellar.server.loadAccount(
|
|
361
|
+
txnSourceKp.publicKey,
|
|
362
|
+
)) as any;
|
|
363
|
+
expect(
|
|
364
|
+
sourceLoaded.signers.find((signer) => signer.key === otherKp.publicKey)
|
|
365
|
+
.weight,
|
|
366
|
+
).toBe(2);
|
|
367
|
+
}, 15000);
|
|
368
|
+
});
|
|
369
|
+
|
|
250
370
|
describe("Asset", () => {
|
|
251
371
|
it("should create an asset", () => {
|
|
252
372
|
const issued = new IssuedAssetId(
|
|
@@ -266,3 +386,80 @@ describe("Asset", () => {
|
|
|
266
386
|
expect(fiat.sep38).toBe("iso4217:USD");
|
|
267
387
|
});
|
|
268
388
|
});
|
|
389
|
+
|
|
390
|
+
describe("Account Modifying", () => {
|
|
391
|
+
it("should modify account ", async () => {
|
|
392
|
+
const wallet = Wallet.TestNet();
|
|
393
|
+
const stellar = wallet.stellar();
|
|
394
|
+
|
|
395
|
+
const sourceKp = new SigningKeypair(Keypair.random());
|
|
396
|
+
const otherKp = new SigningKeypair(Keypair.random());
|
|
397
|
+
await axios.get(
|
|
398
|
+
"https://friendbot.stellar.org/?addr=" + sourceKp.publicKey,
|
|
399
|
+
);
|
|
400
|
+
await axios.get("https://friendbot.stellar.org/?addr=" + otherKp.publicKey);
|
|
401
|
+
|
|
402
|
+
// Add account signer
|
|
403
|
+
let txBuilder = await stellar.transaction({
|
|
404
|
+
sourceAddress: sourceKp,
|
|
405
|
+
baseFee: 1000,
|
|
406
|
+
});
|
|
407
|
+
const tx = txBuilder.addAccountSigner(otherKp, 1).build();
|
|
408
|
+
tx.sign(sourceKp.keypair);
|
|
409
|
+
await stellar.submitTransaction(tx);
|
|
410
|
+
|
|
411
|
+
let resp = await stellar.server.loadAccount(sourceKp.publicKey);
|
|
412
|
+
|
|
413
|
+
expect(
|
|
414
|
+
resp.signers.find((signer) => signer.key === sourceKp.publicKey),
|
|
415
|
+
).toBeTruthy();
|
|
416
|
+
expect(
|
|
417
|
+
resp.signers.find((signer) => signer.key === otherKp.publicKey),
|
|
418
|
+
).toBeTruthy();
|
|
419
|
+
|
|
420
|
+
// Remove account signer
|
|
421
|
+
txBuilder = await stellar.transaction({
|
|
422
|
+
sourceAddress: sourceKp,
|
|
423
|
+
baseFee: 1000,
|
|
424
|
+
});
|
|
425
|
+
const removeTx = txBuilder.removeAccountSigner(otherKp).build();
|
|
426
|
+
removeTx.sign(sourceKp.keypair);
|
|
427
|
+
await stellar.submitTransaction(removeTx);
|
|
428
|
+
|
|
429
|
+
resp = await stellar.server.loadAccount(sourceKp.publicKey);
|
|
430
|
+
expect(
|
|
431
|
+
resp.signers.find((signer) => signer.key === sourceKp.publicKey),
|
|
432
|
+
).toBeTruthy();
|
|
433
|
+
expect(
|
|
434
|
+
resp.signers.find((signer) => signer.key === otherKp.publicKey),
|
|
435
|
+
).toBeFalsy();
|
|
436
|
+
|
|
437
|
+
// Change account thresholds
|
|
438
|
+
txBuilder = await stellar.transaction({
|
|
439
|
+
sourceAddress: sourceKp,
|
|
440
|
+
baseFee: 1000,
|
|
441
|
+
});
|
|
442
|
+
const thresholdTx = txBuilder.setThreshold({ low: 0, high: 1 }).build();
|
|
443
|
+
thresholdTx.sign(sourceKp.keypair);
|
|
444
|
+
await stellar.submitTransaction(thresholdTx);
|
|
445
|
+
|
|
446
|
+
resp = await stellar.server.loadAccount(sourceKp.publicKey);
|
|
447
|
+
expect(resp.thresholds).toEqual({
|
|
448
|
+
low_threshold: 0,
|
|
449
|
+
med_threshold: 0,
|
|
450
|
+
high_threshold: 1,
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// Lock master account
|
|
454
|
+
txBuilder = await stellar.transaction({
|
|
455
|
+
sourceAddress: sourceKp,
|
|
456
|
+
baseFee: 1000,
|
|
457
|
+
});
|
|
458
|
+
const lockTx = txBuilder.lockAccountMasterKey().build();
|
|
459
|
+
lockTx.sign(sourceKp.keypair);
|
|
460
|
+
await stellar.submitTransaction(lockTx);
|
|
461
|
+
|
|
462
|
+
resp = await stellar.server.loadAccount(sourceKp.publicKey);
|
|
463
|
+
expect(resp.signers[0].weight).toBe(0);
|
|
464
|
+
}, 45000);
|
|
465
|
+
});
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { Horizon, MuxedAccount } from "stellar-sdk";
|
|
3
|
+
|
|
4
|
+
import { AccountService, SigningKeypair, Stellar, Wallet } from "../src";
|
|
5
|
+
import { IssuedAssetId, NativeAssetId } from "../src/walletSdk/Asset";
|
|
6
|
+
|
|
7
|
+
describe("Muxed Transactions", () => {
|
|
8
|
+
let wallet: Wallet;
|
|
9
|
+
let stellar: Stellar;
|
|
10
|
+
let accountService: AccountService;
|
|
11
|
+
let testingDistributionKp: SigningKeypair;
|
|
12
|
+
let testingAsset: IssuedAssetId;
|
|
13
|
+
|
|
14
|
+
// Creates testing stellar account with testing TSWT asset
|
|
15
|
+
// in case it doesn't exist just yet
|
|
16
|
+
beforeAll(async () => {
|
|
17
|
+
wallet = Wallet.TestNet();
|
|
18
|
+
stellar = wallet.stellar();
|
|
19
|
+
accountService = stellar.account();
|
|
20
|
+
|
|
21
|
+
// Keys for accounts to issue and receive the new TSWT asset
|
|
22
|
+
var issuingKeys = SigningKeypair.fromSecret(
|
|
23
|
+
"SAJMJSEC44DWU22TJF6RWYLRPPXLY4G3L5PVGC7D2QDUCPJIFCOISNQE",
|
|
24
|
+
);
|
|
25
|
+
var receivingKeys = SigningKeypair.fromSecret(
|
|
26
|
+
"SAOQQ76UQFEYN4QAAAOIO45KNZZNQKSXAUB5GXKI6YOFLEDCWPWTCDM3",
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// The "receiving" account is the distribution account
|
|
30
|
+
testingDistributionKp = receivingKeys;
|
|
31
|
+
|
|
32
|
+
// This is the testing asset we'll use to test sending non-native payments
|
|
33
|
+
testingAsset = new IssuedAssetId("TSWT", issuingKeys.publicKey);
|
|
34
|
+
|
|
35
|
+
let assetAlreadyCreated = false;
|
|
36
|
+
try {
|
|
37
|
+
const receivingAccountInfo = await accountService.getInfo({
|
|
38
|
+
accountAddress: receivingKeys.publicKey,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const tswtAssetBalance = receivingAccountInfo.balances.find(
|
|
42
|
+
(balanceLine) => {
|
|
43
|
+
const { asset_code, balance } =
|
|
44
|
+
balanceLine as Horizon.BalanceLineAsset;
|
|
45
|
+
return asset_code === testingAsset.code && Number(balance) > 1000;
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (tswtAssetBalance) {
|
|
50
|
+
assetAlreadyCreated = true;
|
|
51
|
+
}
|
|
52
|
+
} catch {}
|
|
53
|
+
|
|
54
|
+
if (assetAlreadyCreated) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// If the TSWT asset is not there yet, let's issue and distribute it!
|
|
59
|
+
try {
|
|
60
|
+
// First make sure both issuing and receiving(distribution) accounts
|
|
61
|
+
// are funded
|
|
62
|
+
await axios.get(
|
|
63
|
+
"https://friendbot.stellar.org/?addr=" + issuingKeys.publicKey,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
await axios.get(
|
|
67
|
+
"https://friendbot.stellar.org/?addr=" + receivingKeys.publicKey,
|
|
68
|
+
);
|
|
69
|
+
} catch {}
|
|
70
|
+
|
|
71
|
+
let didFail = false;
|
|
72
|
+
try {
|
|
73
|
+
// Then, the receiving(distribution) account must trust the asset
|
|
74
|
+
const txBuilder = await stellar.transaction({
|
|
75
|
+
sourceAddress: receivingKeys,
|
|
76
|
+
baseFee: 100,
|
|
77
|
+
});
|
|
78
|
+
const addTswtTx = txBuilder.addAssetSupport(testingAsset).build();
|
|
79
|
+
addTswtTx.sign(receivingKeys.keypair);
|
|
80
|
+
await stellar.submitTransaction(addTswtTx);
|
|
81
|
+
|
|
82
|
+
// Finally, the issuing account actually sends a payment using the asset
|
|
83
|
+
// to fund the receiving(distribution) account
|
|
84
|
+
const txBuilder2 = await stellar.transaction({
|
|
85
|
+
sourceAddress: issuingKeys,
|
|
86
|
+
baseFee: 100,
|
|
87
|
+
});
|
|
88
|
+
const fundingPaymentTx = txBuilder2
|
|
89
|
+
.transfer(receivingKeys.publicKey, testingAsset, "100000000")
|
|
90
|
+
.build();
|
|
91
|
+
fundingPaymentTx.sign(issuingKeys.keypair);
|
|
92
|
+
await stellar.submitTransaction(fundingPaymentTx);
|
|
93
|
+
} catch {
|
|
94
|
+
didFail = true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
expect(didFail).toBeFalsy();
|
|
98
|
+
}, 60000);
|
|
99
|
+
|
|
100
|
+
it("should send 'native' payment to valid Muxed account", async () => {
|
|
101
|
+
const baseAccoutKp = SigningKeypair.fromSecret(
|
|
102
|
+
"SDC4SZWQDELBKWPBPBLZSKMWAIPNE27OF6BPJV7F7FCXTKHBACN3KWRO",
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await axios.get(
|
|
107
|
+
"https://friendbot.stellar.org/?addr=" + baseAccoutKp.publicKey,
|
|
108
|
+
);
|
|
109
|
+
} catch {}
|
|
110
|
+
|
|
111
|
+
const baseAccount = await stellar.server.loadAccount(
|
|
112
|
+
baseAccoutKp.publicKey,
|
|
113
|
+
);
|
|
114
|
+
const muxedAccount = new MuxedAccount(baseAccount, "123");
|
|
115
|
+
|
|
116
|
+
const txBuilder = await stellar.transaction({
|
|
117
|
+
sourceAddress: testingDistributionKp,
|
|
118
|
+
baseFee: 100,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const sendNativePaymentTx = txBuilder
|
|
122
|
+
.transfer(muxedAccount.accountId(), new NativeAssetId(), "1")
|
|
123
|
+
.build();
|
|
124
|
+
sendNativePaymentTx.sign(testingDistributionKp.keypair);
|
|
125
|
+
|
|
126
|
+
const response = await stellar.submitTransaction(sendNativePaymentTx);
|
|
127
|
+
|
|
128
|
+
expect(response).toBeTruthy();
|
|
129
|
+
}, 30000);
|
|
130
|
+
|
|
131
|
+
it("should send non-native payment to valid Muxed account", async () => {
|
|
132
|
+
const baseAccoutKp = SigningKeypair.fromSecret(
|
|
133
|
+
"SDFK2E4LSVZJGEWKFIX4SXOLDXQQWMDGNONERVBNFXDVWE2PTEKY6ZSI",
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
await axios.get(
|
|
138
|
+
"https://friendbot.stellar.org/?addr=" + baseAccoutKp.publicKey,
|
|
139
|
+
);
|
|
140
|
+
} catch {}
|
|
141
|
+
|
|
142
|
+
const baseAccount = await stellar.server.loadAccount(
|
|
143
|
+
baseAccoutKp.publicKey,
|
|
144
|
+
);
|
|
145
|
+
const muxedAccount = new MuxedAccount(baseAccount, "456");
|
|
146
|
+
|
|
147
|
+
// First add support for the non-native asset
|
|
148
|
+
const txBuilder1 = await stellar.transaction({
|
|
149
|
+
sourceAddress: baseAccoutKp,
|
|
150
|
+
baseFee: 100,
|
|
151
|
+
});
|
|
152
|
+
const addTswtTx = txBuilder1.addAssetSupport(testingAsset).build();
|
|
153
|
+
addTswtTx.sign(baseAccoutKp.keypair);
|
|
154
|
+
await stellar.submitTransaction(addTswtTx);
|
|
155
|
+
|
|
156
|
+
// Then submit the non-native payment!
|
|
157
|
+
const txBuilder2 = await stellar.transaction({
|
|
158
|
+
sourceAddress: testingDistributionKp,
|
|
159
|
+
baseFee: 100,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const sendNonNativePaymentTx = txBuilder2
|
|
163
|
+
.transfer(muxedAccount.accountId(), testingAsset, "1")
|
|
164
|
+
.build();
|
|
165
|
+
sendNonNativePaymentTx.sign(testingDistributionKp.keypair);
|
|
166
|
+
|
|
167
|
+
const response = await stellar.submitTransaction(sendNonNativePaymentTx);
|
|
168
|
+
|
|
169
|
+
expect(response).toBeTruthy();
|
|
170
|
+
}, 30000);
|
|
171
|
+
|
|
172
|
+
it("should return 'op_no_trust' error for sending unsupported Asset to valid Muxed account", async () => {
|
|
173
|
+
const baseAccoutKp = SigningKeypair.fromSecret(
|
|
174
|
+
"SD4UETXJ2MJCIPSU72DAJDPL2YGDHF2FEIJVNARUGE6MFDPXNS5YHNGK",
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
await axios.get(
|
|
179
|
+
"https://friendbot.stellar.org/?addr=" + baseAccoutKp.publicKey,
|
|
180
|
+
);
|
|
181
|
+
} catch {}
|
|
182
|
+
|
|
183
|
+
const baseAccount = await stellar.server.loadAccount(
|
|
184
|
+
baseAccoutKp.publicKey,
|
|
185
|
+
);
|
|
186
|
+
const muxedAccount = new MuxedAccount(baseAccount, "789");
|
|
187
|
+
|
|
188
|
+
// Try submitting the non-native payment without adding the trustline
|
|
189
|
+
const txBuilder = await stellar.transaction({
|
|
190
|
+
sourceAddress: testingDistributionKp,
|
|
191
|
+
baseFee: 100,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const sendNonNativePaymentTx = txBuilder
|
|
195
|
+
.transfer(muxedAccount.accountId(), testingAsset, "1")
|
|
196
|
+
.build();
|
|
197
|
+
sendNonNativePaymentTx.sign(testingDistributionKp.keypair);
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
await stellar.submitTransaction(sendNonNativePaymentTx);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
expect(error?.response?.status === 400).toBeTruthy();
|
|
203
|
+
|
|
204
|
+
const resultCodeOperations: Array<string> =
|
|
205
|
+
error?.response?.data?.extras?.result_codes?.operations || [];
|
|
206
|
+
|
|
207
|
+
expect(resultCodeOperations).toContain("op_no_trust");
|
|
208
|
+
}
|
|
209
|
+
}, 30000);
|
|
210
|
+
|
|
211
|
+
it("should return error for sending payment to invalid Muxed address", async () => {
|
|
212
|
+
const txBuilder = await stellar.transaction({
|
|
213
|
+
sourceAddress: testingDistributionKp,
|
|
214
|
+
baseFee: 100,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
txBuilder
|
|
219
|
+
.transfer(
|
|
220
|
+
// Invalid Muxed address ending in "XXXXXX"
|
|
221
|
+
"MBE3SABPOQFUSVCNDO5WNSS4N2KD7ZUFIYDRBP6Q3UBXBMYZFYWLSAAAAAAAAAAXXXXXX",
|
|
222
|
+
new NativeAssetId(),
|
|
223
|
+
"1",
|
|
224
|
+
)
|
|
225
|
+
.build();
|
|
226
|
+
} catch (error) {
|
|
227
|
+
const lowercaseError = error.toString().toLowerCase();
|
|
228
|
+
// Catches "Error: destination is invalid" error message
|
|
229
|
+
expect(lowercaseError.match(/destination.*invalid/)).toBeTruthy();
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe("Path Payment", () => {
|
|
235
|
+
let wallet: Wallet;
|
|
236
|
+
let stellar: Stellar;
|
|
237
|
+
let sourceKp;
|
|
238
|
+
let receivingKp;
|
|
239
|
+
const usdcAsset = new IssuedAssetId(
|
|
240
|
+
"USDC",
|
|
241
|
+
"GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
|
|
242
|
+
);
|
|
243
|
+
beforeAll(async () => {
|
|
244
|
+
wallet = Wallet.TestNet();
|
|
245
|
+
stellar = wallet.stellar();
|
|
246
|
+
|
|
247
|
+
// set up accounts
|
|
248
|
+
sourceKp = SigningKeypair.fromSecret(
|
|
249
|
+
"SBTMCNDNPMJFEYUHLCAMFM5C2PWI7ZUI7PFMLI53O62CSSXFSAXEDTS6",
|
|
250
|
+
);
|
|
251
|
+
receivingKp = SigningKeypair.fromSecret(
|
|
252
|
+
"SDU4Z54AAFF3Y3GA6U27QSSYYX5FLDV6KWGFCCWSMSOSFWHSABJFBFOD",
|
|
253
|
+
);
|
|
254
|
+
try {
|
|
255
|
+
await stellar.server.loadAccount(sourceKp.publicKey);
|
|
256
|
+
await stellar.server.loadAccount(receivingKp.publicKey);
|
|
257
|
+
} catch (e) {
|
|
258
|
+
await axios.get(
|
|
259
|
+
"https://friendbot.stellar.org/?addr=" + sourceKp.publicKey,
|
|
260
|
+
);
|
|
261
|
+
await axios.get(
|
|
262
|
+
"https://friendbot.stellar.org/?addr=" + receivingKp.publicKey,
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
// add trustlines if new accounts
|
|
266
|
+
let txBuilder = await stellar.transaction({
|
|
267
|
+
sourceAddress: receivingKp,
|
|
268
|
+
});
|
|
269
|
+
let txn = txBuilder.addAssetSupport(usdcAsset).build();
|
|
270
|
+
receivingKp.sign(txn);
|
|
271
|
+
await stellar.submitTransaction(txn);
|
|
272
|
+
|
|
273
|
+
txBuilder = await stellar.transaction({
|
|
274
|
+
sourceAddress: sourceKp,
|
|
275
|
+
});
|
|
276
|
+
txn = txBuilder.addAssetSupport(usdcAsset).build();
|
|
277
|
+
sourceKp.sign(txn);
|
|
278
|
+
await stellar.submitTransaction(txn);
|
|
279
|
+
}
|
|
280
|
+
}, 20000);
|
|
281
|
+
|
|
282
|
+
it("should use path payment send", async () => {
|
|
283
|
+
const txBuilder = await stellar.transaction({
|
|
284
|
+
sourceAddress: sourceKp,
|
|
285
|
+
});
|
|
286
|
+
const txn = txBuilder
|
|
287
|
+
.pathPay({
|
|
288
|
+
destinationAddress: receivingKp.publicKey,
|
|
289
|
+
sendAsset: new NativeAssetId(),
|
|
290
|
+
destAsset: usdcAsset,
|
|
291
|
+
sendAmount: "5",
|
|
292
|
+
})
|
|
293
|
+
.build();
|
|
294
|
+
sourceKp.sign(txn);
|
|
295
|
+
const success = await stellar.submitTransaction(txn);
|
|
296
|
+
expect(success).toBe(true);
|
|
297
|
+
}, 15000);
|
|
298
|
+
|
|
299
|
+
it("should use path payment receive", async () => {
|
|
300
|
+
const txBuilder = await stellar.transaction({
|
|
301
|
+
sourceAddress: sourceKp,
|
|
302
|
+
});
|
|
303
|
+
const txn = txBuilder
|
|
304
|
+
.pathPay({
|
|
305
|
+
destinationAddress: receivingKp.publicKey,
|
|
306
|
+
sendAsset: new NativeAssetId(),
|
|
307
|
+
destAsset: usdcAsset,
|
|
308
|
+
destAmount: "5",
|
|
309
|
+
})
|
|
310
|
+
.build();
|
|
311
|
+
sourceKp.sign(txn);
|
|
312
|
+
const success = await stellar.submitTransaction(txn);
|
|
313
|
+
expect(success).toBe(true);
|
|
314
|
+
}, 15000);
|
|
315
|
+
|
|
316
|
+
it("should swap", async () => {
|
|
317
|
+
const txBuilder = await stellar.transaction({
|
|
318
|
+
sourceAddress: sourceKp,
|
|
319
|
+
});
|
|
320
|
+
const txn = txBuilder.swap(new NativeAssetId(), usdcAsset, "1").build();
|
|
321
|
+
sourceKp.sign(txn);
|
|
322
|
+
const success = await stellar.submitTransaction(txn);
|
|
323
|
+
expect(success).toBe(true);
|
|
324
|
+
}, 15000);
|
|
325
|
+
});
|
package/test/wallet.test.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "../src/walletSdk/Auth/WalletSigner";
|
|
19
19
|
import { SigningKeypair } from "../src/walletSdk/Horizon/Account";
|
|
20
20
|
import { Sep24 } from "../src/walletSdk/Anchor/Sep24";
|
|
21
|
+
import { DomainSigner } from "../src/walletSdk/Auth/WalletSigner";
|
|
21
22
|
|
|
22
23
|
import { TransactionsResponse } from "../test/fixtures/TransactionsResponse";
|
|
23
24
|
|
|
@@ -1804,6 +1805,29 @@ describe("Anchor", () => {
|
|
|
1804
1805
|
});
|
|
1805
1806
|
});
|
|
1806
1807
|
|
|
1808
|
+
describe("DomainSigner", () => {
|
|
1809
|
+
it("should work", async () => {
|
|
1810
|
+
jest.spyOn(DefaultClient, "post").mockResolvedValue({
|
|
1811
|
+
data: {
|
|
1812
|
+
transaction:
|
|
1813
|
+
"AAAAAgAAAADVJRbxdB+qXZjUMLcrL/VVoS6megW1ReSxIO33pvO61AAAB9AAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAACwAAAAAAAAACAAAAAAAAAAA=",
|
|
1814
|
+
},
|
|
1815
|
+
});
|
|
1816
|
+
const signer = new DomainSigner("example url", {
|
|
1817
|
+
SampleHeader: "sample-header",
|
|
1818
|
+
});
|
|
1819
|
+
|
|
1820
|
+
const txn = await signer.signWithDomainAccount({
|
|
1821
|
+
transactionXDR: "test-xdr",
|
|
1822
|
+
networkPassphrase: "Test SDF Network ; September 2015",
|
|
1823
|
+
accountKp: SigningKeypair.fromSecret(
|
|
1824
|
+
"SBYAW5H46NNDGCECWMWWM32DE4DPNN3RHVMNTR3BXXZX2DJF6LZWBMWZ",
|
|
1825
|
+
),
|
|
1826
|
+
});
|
|
1827
|
+
expect(txn).toBeTruthy();
|
|
1828
|
+
});
|
|
1829
|
+
});
|
|
1830
|
+
|
|
1807
1831
|
describe("Http client", () => {
|
|
1808
1832
|
it("should work with http", async () => {
|
|
1809
1833
|
const accountKp = Keypair.fromSecret(
|