github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/stellar/stellarsvc/anchor_test.go (about) 1 package stellarsvc 2 3 import ( 4 "errors" 5 "fmt" 6 "net/http" 7 "net/url" 8 "strings" 9 "testing" 10 11 "github.com/keybase/client/go/libkb" 12 "github.com/keybase/client/go/protocol/stellar1" 13 "github.com/keybase/stellarnet" 14 "github.com/stellar/go/build" 15 ) 16 17 type anchorTest struct { 18 Name string 19 Asset stellar1.Asset 20 DepositExternalURL string 21 WithdrawExternalURL string 22 DepositMessage string 23 WithdrawMessage string 24 MockTransferGet func(mctx libkb.MetaContext, url, authToken string) (int, []byte, error) 25 } 26 27 var errAnchorTests = []anchorTest{ 28 { 29 Name: "not verified", 30 Asset: stellar1.Asset{ 31 Type: "credit_alphanum4", 32 Code: "EUR", 33 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 34 }, 35 MockTransferGet: mockKeybaseTransferGet, 36 }, 37 { 38 Name: "no transfer server", 39 Asset: stellar1.Asset{ 40 Type: "credit_alphanum4", 41 Code: "EUR", 42 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 43 VerifiedDomain: "keybase.io", 44 }, 45 MockTransferGet: mockKeybaseTransferGet, 46 }, 47 { 48 Name: "requires auth but with different domain", 49 Asset: stellar1.Asset{ 50 Type: "credit_alphanum4", 51 Code: "EUR", 52 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 53 VerifiedDomain: "keybase.io", 54 TransferServer: "https://transfer.keybase.io/transfer", 55 AuthEndpoint: "https://transfer.keycase.io/auth", 56 }, 57 MockTransferGet: mockKeybaseTransferGet, 58 }, 59 { 60 Name: "invalid url", 61 Asset: stellar1.Asset{ 62 Type: "credit_alphanum4", 63 Code: "EUR", 64 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 65 VerifiedDomain: "keybase.io", 66 TransferServer: ":transfer.keybase.io/transfer", 67 }, 68 MockTransferGet: mockKeybaseTransferGet, 69 }, 70 { 71 Name: "different host", 72 Asset: stellar1.Asset{ 73 Type: "credit_alphanum4", 74 Code: "EUR", 75 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 76 VerifiedDomain: "keybase.io", 77 TransferServer: "https://transfer.keybays.io/transfer", 78 }, 79 MockTransferGet: mockKeybaseTransferGet, 80 }, 81 { 82 Name: "http not https", 83 Asset: stellar1.Asset{ 84 Type: "credit_alphanum4", 85 Code: "EUR", 86 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 87 VerifiedDomain: "keybase.io", 88 TransferServer: "http://transfer.keybase.io/transfer", 89 }, 90 MockTransferGet: mockKeybaseTransferGet, 91 }, 92 { 93 Name: "ftp not https", 94 Asset: stellar1.Asset{ 95 Type: "credit_alphanum4", 96 Code: "EUR", 97 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 98 VerifiedDomain: "keybase.io", 99 TransferServer: "ftp://transfer.keybase.io/transfer", 100 }, 101 MockTransferGet: mockKeybaseTransferGet, 102 }, 103 { 104 Name: "has a query", 105 Asset: stellar1.Asset{ 106 Type: "credit_alphanum4", 107 Code: "EUR", 108 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 109 VerifiedDomain: "keybase.io", 110 TransferServer: "https://transfer.keybase.io/transfer?x=123", 111 }, 112 MockTransferGet: mockKeybaseTransferGet, 113 }, 114 { 115 Name: "endpoint not found", 116 Asset: stellar1.Asset{ 117 Type: "credit_alphanum4", 118 Code: "EUR", 119 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 120 VerifiedDomain: "keybase.io", 121 TransferServer: "https://transfer.keybase.io/nope", 122 }, 123 MockTransferGet: mockKeybaseTransferGet, 124 }, 125 { 126 Name: "external url changes domain name", 127 Asset: stellar1.Asset{ 128 Type: "credit_alphanum4", 129 Code: "EUR", 130 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 131 VerifiedDomain: "keybase.io", 132 TransferServer: "https://transfer.keybase.io/transfer", 133 }, 134 DepositExternalURL: "https://portal.anchorusd.com/onboarding?account=GBZX4364PEPQTDICMIQDZ56K4T75QZCR4NBEYKO6PDRJAHZKGUOJPCXB&identifier=b700518e7430513abdbdab96e7ead566", 135 WithdrawExternalURL: "https://portal.anchorusd.com/onboarding?account=GACW7NONV43MZIFHCOKCQJAKSJSISSICFVUJ2C6EZIW5773OU3HD64VI", 136 MockTransferGet: mockKeybaseTransferGet, 137 }, 138 { 139 Name: "wwallet unauthorized", 140 Asset: stellar1.Asset{ 141 Type: "credit_alphanum4", 142 Code: "USD", 143 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 144 VerifiedDomain: "www.thewwallet.com", 145 TransferServer: "https://thewwallet.com/ExtApi", 146 ShowDepositButton: true, 147 }, 148 MockTransferGet: mockWWTransferGet, 149 }, 150 } 151 152 var validAnchorTests = []anchorTest{ 153 { 154 Name: "valid", 155 Asset: stellar1.Asset{ 156 Type: "credit_alphanum4", 157 Code: "EUR", 158 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 159 VerifiedDomain: "www.anchorusd.com", 160 TransferServer: "https://api.anchorusd.com/transfer", 161 WithdrawType: "bank_account", 162 ShowWithdrawButton: true, 163 ShowDepositButton: true, 164 }, 165 DepositExternalURL: "https://portal.anchorusd.com/onboarding?account=GBZX4364PEPQTDICMIQDZ56K4T75QZCR4NBEYKO6PDRJAHZKGUOJPCXB&identifier=b700518e7430513abdbdab96e7ead566", 166 WithdrawExternalURL: "https://portal.anchorusd.com/onboarding?account=GACW7NONV43MZIFHCOKCQJAKSJSISSICFVUJ2C6EZIW5773OU3HD64VI", 167 MockTransferGet: mockAnchorUSDTransferGet, 168 }, 169 { 170 Name: "naobtc", 171 Asset: stellar1.Asset{ 172 Type: "credit_alphanum4", 173 Code: "BTC", 174 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 175 VerifiedDomain: "www.naobtc.com", 176 TransferServer: "https://www.naobtc.com", 177 WithdrawType: "crypto", 178 ShowDepositButton: true, 179 }, 180 DepositMessage: "Deposit request approved by anchor. 19qPSWH6Cytp2zsn4Cntbzz2EMp1fadkRs: 3 confirmations needed. this is long term available address", 181 MockTransferGet: mockNaoBTCTransferGet, 182 }, 183 { 184 Name: "requires auth", 185 Asset: stellar1.Asset{ 186 Type: "credit_alphanum4", 187 Code: "EUR", 188 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 189 VerifiedDomain: "keybase.io", 190 TransferServer: "https://transfer.keybase.io/transfer", 191 AuthEndpoint: "https://transfer.keybase.io/auth", 192 ShowDepositButton: true, 193 ShowWithdrawButton: true, 194 DepositReqAuth: true, 195 WithdrawReqAuth: true, 196 }, 197 MockTransferGet: mockAuthGet, 198 DepositExternalURL: "https://keybase.io/onboarding?account=GBZX4364PEPQTDICMIQDZ56K4T75QZCR4NBEYKO6PDRJAHZKGUOJPCXB&identifier=b700518e7430513abdbdab96e7ead566", 199 WithdrawExternalURL: "https://keybase.io/onboarding?account=GACW7NONV43MZIFHCOKCQJAKSJSISSICFVUJ2C6EZIW5773OU3HD64VI", 200 }, 201 { 202 Name: "sep24", 203 Asset: stellar1.Asset{ 204 Type: "credit_alphanum4", 205 Code: "EUR", 206 Issuer: "GAKBPBDMW6CTRDCXNAPSVJZ6QAN3OBNRG6CWI27FGDQT2ZJJEMDRXPKK", 207 VerifiedDomain: "keybase.io", 208 TransferServer: "https://transfer.keybase.io/transfer", 209 AuthEndpoint: "https://transfer.keybase.io/auth", 210 ShowDepositButton: true, 211 ShowWithdrawButton: true, 212 DepositReqAuth: true, 213 WithdrawReqAuth: true, 214 UseSep24: true, 215 }, 216 MockTransferGet: mockAuthGet, 217 DepositExternalURL: "https://keybase.io/transfer/deposit?account=GACW7NONV43MZIFHCOKCQJAKSJSISSICFVUJ2C6EZIW5773OU3HD64VI", 218 WithdrawExternalURL: "https://keybase.io/transfer/withdraw?account=GACW7NONV43MZIFHCOKCQJAKSJSISSICFVUJ2C6EZIW5773OU3HD64VI", 219 }, 220 } 221 222 func TestAnchorInteractor(t *testing.T) { 223 tc := SetupTest(t, "AnchorInteractor", 1) 224 defer tc.Cleanup() 225 for i, test := range errAnchorTests { 226 accountID, seed := randomStellarKeypair() 227 ai := newAnchorInteractor(accountID, &seed, test.Asset) 228 ai.httpGetClient = test.MockTransferGet 229 _, err := ai.Deposit(tc.MetaContext()) 230 if err == nil { 231 t.Errorf("err test %d [%s]: Deposit returned no error, but expected one", i, test.Name) 232 continue 233 } 234 _, err = ai.Withdraw(tc.MetaContext()) 235 if err == nil { 236 t.Errorf("err test %d [%s]: Withdraw returned no error, but expected one", i, test.Name) 237 continue 238 } 239 } 240 241 for i, test := range validAnchorTests { 242 accountID, seed := randomStellarKeypair() 243 ai := newAnchorInteractor(accountID, &seed, test.Asset) 244 ai.httpGetClient = test.MockTransferGet 245 246 // our test tx auth challenges are on the public network: 247 if test.Asset.AuthEndpoint != "" { 248 stellarnet.SetNetwork(build.PublicNetwork) 249 ai.httpPostClient = mockAuthPost 250 } else { 251 stellarnet.SetNetwork(build.TestNetwork) 252 } 253 254 if test.Asset.ShowDepositButton { 255 res, err := ai.Deposit(tc.MetaContext()) 256 if err != nil { 257 t.Errorf("valid test %d [%s]: Deposit returned an error: %s", i, test.Name, err) 258 continue 259 } 260 if res.ExternalUrl == nil && res.MessageFromAnchor == nil { 261 t.Errorf("valid test %d [%s] deposit: result fields are all nil", i, test.Name) 262 continue 263 } 264 if test.DepositExternalURL != "" && res.ExternalUrl == nil { 265 t.Errorf("valid test %d [%s] deposit: result external url field is nil, expected %s", i, test.Name, test.DepositExternalURL) 266 continue 267 } 268 if res.ExternalUrl != nil { 269 if test.DepositExternalURL != *res.ExternalUrl { 270 t.Errorf("valid test %d [%s] deposit: result external url field %s, expected %s", i, test.Name, *res.ExternalUrl, test.DepositExternalURL) 271 } 272 } 273 if res.MessageFromAnchor != nil { 274 if test.DepositMessage != *res.MessageFromAnchor { 275 t.Errorf("valid test %d [%s] deposit: result message %q, expected %q", i, test.Name, *res.MessageFromAnchor, test.DepositMessage) 276 } 277 } 278 } 279 280 if test.Asset.ShowWithdrawButton { 281 res, err := ai.Withdraw(tc.MetaContext()) 282 if err != nil { 283 t.Errorf("valid test %d [%s]: Withdraw returned an error: %s", i, test.Name, err) 284 continue 285 } 286 if res.ExternalUrl == nil && res.MessageFromAnchor == nil { 287 t.Errorf("valid test %d [%s] withdraw: result fields are all nil", i, test.Name) 288 continue 289 } 290 if test.WithdrawExternalURL != "" && res.ExternalUrl == nil { 291 t.Errorf("valid test %d [%s] withdraw: result external url field is nil, expected %s", i, test.Name, test.WithdrawExternalURL) 292 continue 293 } 294 if res.ExternalUrl != nil { 295 if test.WithdrawExternalURL != *res.ExternalUrl { 296 t.Errorf("valid test %d [%s] withdraw: result external url field %s, expected %s", i, test.Name, *res.ExternalUrl, test.WithdrawExternalURL) 297 } 298 } 299 if res.MessageFromAnchor != nil { 300 if test.WithdrawMessage != *res.MessageFromAnchor { 301 t.Errorf("valid test %d [%s] withdraw: result message %q, expected %q", i, test.Name, *res.MessageFromAnchor, test.WithdrawMessage) 302 } 303 } 304 } 305 } 306 } 307 308 // mockKeybaseTransferGet is an httpGetClient func that returns a stored result 309 // for TRANSFER_SERVER/deposit and TRANSFER_SERVER/withdraw. 310 func mockKeybaseTransferGet(mctx libkb.MetaContext, url, authToken string) (int, []byte, error) { 311 parts := strings.Split(url, "?") 312 switch parts[0] { 313 case "https://transfer.keybase.io/transfer/deposit": 314 return http.StatusForbidden, []byte(depositBody), nil 315 case "https://transfer.keybase.io/transfer/withdraw": 316 return http.StatusForbidden, []byte(withdrawBody), nil 317 default: 318 return 0, nil, errors.New("unknown mocked url") 319 } 320 } 321 322 // mockAnchorUSDTransferGet is an httpGetClient func that returns a stored result 323 // for TRANSFER_SERVER/deposit and TRANSFER_SERVER/withdraw. 324 func mockAnchorUSDTransferGet(mctx libkb.MetaContext, url, authToken string) (int, []byte, error) { 325 parts := strings.Split(url, "?") 326 switch parts[0] { 327 case "https://api.anchorusd.com/transfer/deposit": 328 return http.StatusForbidden, []byte(depositBody), nil 329 case "https://api.anchorusd.com/transfer/withdraw": 330 return http.StatusForbidden, []byte(withdrawBody), nil 331 default: 332 return 0, nil, errors.New("unknown mocked url") 333 } 334 } 335 336 // mockNaoBTCTransferGet is an httpGetClient func that returns a stored result 337 // for TRANSFER_SERVER/deposit and TRANSFER_SERVER/withdraw. 338 func mockNaoBTCTransferGet(mctx libkb.MetaContext, url, authToken string) (int, []byte, error) { 339 parts := strings.Split(url, "?") 340 switch parts[0] { 341 case "https://www.naobtc.com/deposit": 342 return http.StatusOK, []byte(naobtcBody), nil 343 case "https://www.naobtc.com/withdraw": 344 return http.StatusForbidden, []byte(withdrawBody), nil 345 default: 346 return 0, nil, errors.New("unknown mocked url") 347 } 348 } 349 350 func mockWWTransferGet(mctx libkb.MetaContext, url, authToken string) (int, []byte, error) { 351 parts := strings.Split(url, "?") 352 switch parts[0] { 353 case "https://thewwallet.com/ExtApi/deposit": 354 return http.StatusUnauthorized, []byte("Status Code: 401; Unauthorized"), nil 355 default: 356 return 0, nil, errors.New("unknown mocked url") 357 } 358 } 359 360 // mockAuthGet is an httpGetClient func that returns a stored result 361 // for WEB_AUTH_ENDPOINT and TRANSFER_SERVER/deposit and TRANSFER_SERVER/withdraw. 362 func mockAuthGet(mctx libkb.MetaContext, url, authToken string) (int, []byte, error) { 363 parts := strings.Split(url, "?") 364 switch parts[0] { 365 case "https://transfer.keybase.io/transfer/deposit": 366 if authToken == "" { 367 return 0, nil, errors.New("missing token") 368 } 369 return http.StatusForbidden, []byte(authDepositBody), nil 370 case "https://transfer.keybase.io/transfer/withdraw": 371 if authToken == "" { 372 return 0, nil, errors.New("missing token") 373 } 374 return http.StatusForbidden, []byte(authWithdrawBody), nil 375 case "https://transfer.keybase.io/auth": 376 return http.StatusOK, []byte(authChallenge), nil 377 default: 378 return 0, nil, fmt.Errorf("unknown mocked url %q", url) 379 } 380 } 381 382 func mockAuthPost(mctx libkb.MetaContext, url, authToken string, data url.Values) (int, []byte, error) { 383 switch url { 384 case "https://transfer.keybase.io/auth": 385 return 200, []byte(authBody), nil 386 case "https://transfer.keybase.io/transfer/transactions/deposit/interactive": 387 return 200, []byte(sep24DepBody), nil 388 case "https://transfer.keybase.io/transfer/transactions/withdraw/interactive": 389 return 200, []byte(sep24WithdrawBody), nil 390 default: 391 return 0, nil, fmt.Errorf("mockAuthPost: unknown mocked url %q", url) 392 } 393 } 394 395 const depositBody = `{"type":"interactive_customer_info_needed","url":"https://portal.anchorusd.com/onboarding?account=GBZX4364PEPQTDICMIQDZ56K4T75QZCR4NBEYKO6PDRJAHZKGUOJPCXB&identifier=b700518e7430513abdbdab96e7ead566","identifier":"b700518e7430513abdbdab96e7ead566","dimensions":{"width":800,"height":600}}` 396 const withdrawBody = `{ "type": "interactive_customer_info_needed", "url" : "https://portal.anchorusd.com/onboarding?account=GACW7NONV43MZIFHCOKCQJAKSJSISSICFVUJ2C6EZIW5773OU3HD64VI", "id": "82fhs729f63dh0v4" }` 397 const naobtcBody = `{"how": "19qPSWH6Cytp2zsn4Cntbzz2EMp1fadkRs", "eta": 1800, "extra_info": "3 confirmations needed. this is long term available address", "extra_info_cn": "充值需要三次网络确认。此地址长期有效"}` 398 const authDepositBody = `{"type":"interactive_customer_info_needed","url":"https://keybase.io/onboarding?account=GBZX4364PEPQTDICMIQDZ56K4T75QZCR4NBEYKO6PDRJAHZKGUOJPCXB&identifier=b700518e7430513abdbdab96e7ead566","identifier":"b700518e7430513abdbdab96e7ead566","dimensions":{"width":800,"height":600}}` 399 const authWithdrawBody = `{ "type": "interactive_customer_info_needed", "url" : "https://keybase.io/onboarding?account=GACW7NONV43MZIFHCOKCQJAKSJSISSICFVUJ2C6EZIW5773OU3HD64VI", "id": "82fhs729f63dh0v4" }` 400 const authChallenge = `{"transaction":"AAAAAANjzBWOC6YJo49wLshbTPMAmHnZ1I5AESV73e605u3DAAAnEAAAAAAAAAAAAAAAAQAAAABdRIV+AAAAAF1EhqoAAAAAAAAAAQAAAAEAAAAAc35v3HkfCY0CYiA898rk/9hkUeNCTCneeOKQHyo1HJcAAAAKAAAAEFN0ZWxsYXJwb3J0IGF1dGgAAAABAAAAQMCsw7hA+QQnW9t2MfAU92Sqa7eD1udjvaS5BSO9AJFXuELyBmzw+l+GhIry01cM6nz5HKleHf+wDn2jXYYlFKQAAAAAAAAAAbTm7cMAAABAnoRu4cp4cl9UEYqyRIfAIiLhoSU7h77vU9yV2S1RSNZfhc/YaXlMnlLkb9CAeLho1nVMOQnGNzQ55gWJzXXQDQ=="}` 401 const authBody = `{ 402 "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJHQTZVSVhYUEVXWUZJTE5VSVdBQzM3WTRRUEVaTVFWREpIREtWV0ZaSjJLQ1dVQklVNUlYWk5EQSIsImp0aSI6IjE0NGQzNjdiY2IwZTcyY2FiZmRiZGU2MGVhZTBhZDczM2NjNjVkMmE2NTg3MDgzZGFiM2Q2MTZmODg1MTkwMjQiLCJpc3MiOiJodHRwczovL2ZsYXBweS1iaXJkLWRhcHAuZmlyZWJhc2VhcHAuY29tLyIsImlhdCI6MTUzNDI1Nzk5NCwiZXhwIjoxNTM0MzQ0Mzk0fQ.8nbB83Z6vGBgC1X9r3N6oQCFTBzDiITAfCJasRft0z0" 403 }` 404 const sep24DepBody = `{ "type": "interactive_customer_info_needed", "url" : "https://keybase.io/transfer/deposit?account=GACW7NONV43MZIFHCOKCQJAKSJSISSICFVUJ2C6EZIW5773OU3HD64VI", "id": "82fhs729f63dh0v4" }` 405 const sep24WithdrawBody = `{ "type": "interactive_customer_info_needed", "url" : "https://keybase.io/transfer/withdraw?account=GACW7NONV43MZIFHCOKCQJAKSJSISSICFVUJ2C6EZIW5773OU3HD64VI", "id": "82fhs729f63dh0v4" }`